diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj b/aegisub/build/Aegisub/Aegisub.vcxproj
index 3c80048e308e43aff64c5dcd5f82f51ad21cd130..6f2a3f842a2bc54decff685dcbd0ad201867cb28 100644
--- a/aegisub/build/Aegisub/Aegisub.vcxproj
+++ b/aegisub/build/Aegisub/Aegisub.vcxproj
@@ -134,7 +134,6 @@
     <ClInclude Include="$(SrcDir)avisynth_wrap.h" />
     <ClInclude Include="$(SrcDir)base_grid.h" />
     <ClInclude Include="$(SrcDir)block_cache.h" />
-    <ClInclude Include="$(SrcDir)charset_conv.h" />
     <ClInclude Include="$(SrcDir)charset_detect.h" />
     <ClInclude Include="$(SrcDir)colorspace.h" />
     <ClInclude Include="$(SrcDir)colour_button.h" />
@@ -321,7 +320,6 @@
     <ClCompile Include="$(SrcDir)auto4_lua_scriptreader.cpp" />
     <ClCompile Include="$(SrcDir)avisynth_wrap.cpp" />
     <ClCompile Include="$(SrcDir)base_grid.cpp" />
-    <ClCompile Include="$(SrcDir)charset_conv.cpp" />
     <ClCompile Include="$(SrcDir)charset_detect.cpp" />
     <ClCompile Include="$(SrcDir)colorspace.cpp" />
     <ClCompile Include="$(SrcDir)colour_button.cpp" />
diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/Aegisub/Aegisub.vcxproj.filters
index 4beabf456d51734f8df09686a2ffd570de25ada0..da5ea5304fdbe7391db9f1eb5406da90edda3dc2 100644
--- a/aegisub/build/Aegisub/Aegisub.vcxproj.filters
+++ b/aegisub/build/Aegisub/Aegisub.vcxproj.filters
@@ -525,9 +525,6 @@
     <ClInclude Include="$(SrcDir)charset_detect.h">
       <Filter>Features\Import</Filter>
     </ClInclude>
-    <ClInclude Include="$(SrcDir)charset_conv.h">
-      <Filter>Features\Import</Filter>
-    </ClInclude>
     <ClInclude Include="$(SrcDir)help_button.h">
       <Filter>Features\Help</Filter>
     </ClInclude>
@@ -1076,9 +1073,6 @@
     <ClCompile Include="$(SrcDir)subs_grid.cpp">
       <Filter>Main UI\Grid</Filter>
     </ClCompile>
-    <ClCompile Include="$(SrcDir)charset_conv.cpp">
-      <Filter>Features\Import</Filter>
-    </ClCompile>
     <ClCompile Include="$(SrcDir)charset_detect.cpp">
       <Filter>Features\Import</Filter>
     </ClCompile>
diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj b/aegisub/build/libaegisub/libaegisub.vcxproj
index 837691411d4e7106ca92e5d4c903f27e00359e00..0f4655767eb963a88451e03b73baeb30680671c9 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj
@@ -17,20 +17,27 @@
   <!-- Project specific configuration -->
   <ItemDefinitionGroup>
     <ClCompile>
-      <AdditionalIncludeDirectories>$(SrcDir);$(SrcDir)include;$(AegisubContribBase)iconv\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>
+        $(SrcDir);
+        $(SrcDir)include;
+        $(AegisubContribBase)iconv\include;
+        %(AdditionalIncludeDirectories)
+      </AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>
+        NOMINMAX;
+        _WIN32_WINNT=0x0501;
+        %(PreprocessorDefinitions)
+      </PreprocessorDefinitions>
       <PrecompiledHeader>Use</PrecompiledHeader>
       <PrecompiledHeaderFile>lagi_pre.h</PrecompiledHeaderFile>
       <ForcedIncludeFiles>lagi_pre.h</ForcedIncludeFiles>
     </ClCompile>
   </ItemDefinitionGroup>
-
   <!-- Source files -->
   <ItemGroup>
     <ClInclude Include="$(SrcDir)lagi_pre.h" />
     <ClInclude Include="$(SrcDir)config.h" />
     <ClInclude Include="$(SrcDir)common\charset_6937.h" />
-    <ClInclude Include="$(SrcDir)common\charset_ucd.h" />
     <ClInclude Include="$(SrcDir)common\option_visit.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\access.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\charset.h" />
@@ -64,8 +71,13 @@
     <ClInclude Include="$(SrcDir)include\libaegisub\background_runner.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\color.h" />
     <ClInclude Include="$(SrcDir)include\libaegisub\spellchecker.h" />
-    <ClInclude Include="..\..\libaegisub\include\libaegisub\calltip_provider.h" />
-    <ClInclude Include="..\..\libaegisub\include\libaegisub\of_type_adaptor.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\calltip_provider.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\fs.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\of_type_adaptor.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\path.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\fs_fwd.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\dispatch.h" />
+    <ClInclude Include="$(SrcDir)include\libaegisub\split.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="$(SrcDir)windows\lagi_pre.cpp">
@@ -78,7 +90,6 @@
     <ClCompile Include="$(SrcDir)common\charset.cpp" />
     <ClCompile Include="$(SrcDir)common\charset_6937.cpp" />
     <ClCompile Include="$(SrcDir)common\charset_conv.cpp" />
-    <ClCompile Include="$(SrcDir)common\charset_ucd.cpp" />
     <ClCompile Include="$(SrcDir)common\hotkey.cpp" />
     <ClCompile Include="$(SrcDir)common\json.cpp" />
     <ClCompile Include="$(SrcDir)common\keyframe.cpp" />
@@ -96,8 +107,13 @@
     <ClCompile Include="$(SrcDir)ass\dialogue_parser.cpp" />
     <ClCompile Include="$(SrcDir)common\color.cpp" />
     <ClCompile Include="$(SrcDir)common\parser.cpp" />
-    <ClCompile Include="..\..\libaegisub\common\calltip_provider.cpp" />
-    <ClCompile Include="..\..\libaegisub\common\io.cpp" />
+    <ClCompile Include="$(SrcDir)common\calltip_provider.cpp" />
+    <ClCompile Include="$(SrcDir)common\fs.cpp" />
+    <ClCompile Include="$(SrcDir)common\io.cpp" />
+    <ClCompile Include="$(SrcDir)common\path.cpp" />
+    <ClCompile Include="$(SrcDir)windows\fs.cpp" />
+    <ClCompile Include="$(SrcDir)windows\path_win.cpp" />
+    <ClCompile Include="$(SrcDir)common\dispatch.cpp" />
   </ItemGroup>
   <ItemGroup>
     <None Include="$(SrcDir)include\libaegisub\charsets.def" />
diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj.filters b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
index 4539c7db65a8873cd8b47bc3cffc1a20bdd51ac0..75b180c3415ece6f3289cb9c2a3f72d6dc37f928 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj.filters
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
@@ -26,9 +26,6 @@
     <ClInclude Include="$(SrcDir)common\charset_6937.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="$(SrcDir)common\charset_ucd.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="$(SrcDir)common\option_visit.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -134,10 +131,25 @@
     <ClInclude Include="$(SrcDir)common\parser.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\libaegisub\include\libaegisub\of_type_adaptor.h">
+    <ClInclude Include="$(SrcDir)include\libaegisub\of_type_adaptor.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\calltip_provider.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\path.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\fs.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\fs_fwd.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="$(SrcDir)include\libaegisub\dispatch.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\libaegisub\include\libaegisub\calltip_provider.h">
+    <ClInclude Include="$(SrcDir)include\libaegisub\split.h">
       <Filter>Header Files</Filter>
     </ClInclude>
   </ItemGroup>
@@ -160,9 +172,6 @@
     <ClCompile Include="$(SrcDir)windows\charset_conv_win.cpp">
       <Filter>Source Files\Windows</Filter>
     </ClCompile>
-    <ClCompile Include="$(SrcDir)common\charset_ucd.cpp">
-      <Filter>Source Files\Common</Filter>
-    </ClCompile>
     <ClCompile Include="$(SrcDir)windows\log_win.cpp">
       <Filter>Source Files\Windows</Filter>
     </ClCompile>
@@ -220,10 +229,25 @@
     <ClCompile Include="$(SrcDir)common\parser.cpp">
       <Filter>Source Files\Common</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\libaegisub\common\io.cpp">
+    <ClCompile Include="$(SrcDir)common\io.cpp">
+      <Filter>Source Files\Common</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SrcDir)common\calltip_provider.cpp">
+      <Filter>Source Files\Common</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SrcDir)common\path.cpp">
+      <Filter>Source Files\Common</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SrcDir)windows\path_win.cpp">
+      <Filter>Source Files\Windows</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SrcDir)windows\fs.cpp">
+      <Filter>Source Files\Windows</Filter>
+    </ClCompile>
+    <ClCompile Include="$(SrcDir)common\fs.cpp">
       <Filter>Source Files\Common</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\libaegisub\common\calltip_provider.cpp">
+    <ClCompile Include="$(SrcDir)common\dispatch.cpp">
       <Filter>Source Files\Common</Filter>
     </ClCompile>
   </ItemGroup>
@@ -232,4 +256,4 @@
       <Filter>Header Files</Filter>
     </None>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/aegisub/libaegisub/Makefile b/aegisub/libaegisub/Makefile
index 20d6e8ae9c1a55145645d2378903d28178e14f1e..040a439f8779ab15c060187f8cd485e9d36b767b 100644
--- a/aegisub/libaegisub/Makefile
+++ b/aegisub/libaegisub/Makefile
@@ -25,23 +25,27 @@ SRC += \
 	common/charset.cpp \
 	common/charset_6937.cpp \
 	common/charset_conv.cpp \
-	common/charset_ucd.cpp \
 	common/color.cpp \
+	common/dispatch.cpp \
+	common/fs.cpp \
 	common/hotkey.cpp \
 	common/io.cpp \
 	common/json.cpp \
 	common/keyframe.cpp \
+	common/log.cpp \
 	common/mru.cpp \
 	common/option.cpp \
 	common/option_visit.cpp \
 	common/parser.cpp \
-	common/util.cpp \
-	common/log.cpp \
+	common/path.cpp \
 	common/thesaurus.cpp \
+	common/util.cpp \
 	common/vfr.cpp \
-	unix/util.cpp \
 	unix/access.cpp \
-	unix/log.cpp
+	unix/fs.cpp \
+	unix/log.cpp \
+	unix/path.cpp \
+	unix/util.cpp
 
 ifeq (yes, $(BUILD_DARWIN))
 SRC += osx/util.mm
diff --git a/aegisub/libaegisub/common/charset.cpp b/aegisub/libaegisub/common/charset.cpp
index 9dc1f92be3a61f0801feb5f6a57a1598454a0a2e..8ad4df4b6718a811bc1a63af7f72bb9f77eae9c5 100644
--- a/aegisub/libaegisub/common/charset.cpp
+++ b/aegisub/libaegisub/common/charset.cpp
@@ -16,18 +16,118 @@
 /// @brief Character set detection and manipulation utilities.
 /// @ingroup libaegisub
 
-#include "charset_ucd.h"
+#include "libaegisub/charset.h"
 
-namespace agi {
-	namespace charset {
+#include "libaegisub/io.h"
 
-std::string Detect(const std::string &file) {
-	return UCDetect(file).Single();
-}
+#include <fstream>
+#include <string>
+
+#ifndef _WIN32
+#define _X86_ 1
+#endif
+
+#include "../../universalchardet/nscore.h"
+#include "../../universalchardet/nsUniversalDetector.h"
+#include "../../universalchardet/nsMBCSGroupProber.h"
+#include "../../universalchardet/nsCharSetProber.h"
+
+namespace {
+using namespace agi::charset;
+
+class UCDetect : public nsUniversalDetector {
+	/// List of detected character sets
+	CharsetListDetected list;
+
+	void Report(const char* aCharset) {}
+
+public:
+	/// @brief Detect character set of a file using UniversalCharDetect
+	/// @param file File to check
+	UCDetect(agi::fs::path const& file)
+	: nsUniversalDetector(NS_FILTER_ALL)
+	{
+		{
+			std::unique_ptr<std::ifstream> fp(agi::io::Open(file, true));
+
+			// If it's over 100 MB it's either binary or big enough that we won't
+			// be able to do anything useful with it anyway
+			fp->seekg(0, std::ios::end);
+			if (fp->tellg() > 100 * 1024 * 1024) {
+				list.emplace_back(1.f, "binary");
+				return;
+			}
+			fp->seekg(0, std::ios::beg);
+
+			std::streamsize binaryish = 0;
+			std::streamsize bytes = 0;
+
+			while (!mDone && *fp) {
+				char buf[4096];
+				fp->read(buf, sizeof(buf));
+				std::streamsize read = fp->gcount();
+				HandleData(buf, (PRUint32)read);
 
-CharsetListDetected DetectAll(const std::string& file) {
-	return UCDetect(file).List();
+				// A dumb heuristic to detect binary files
+				if (!mDone) {
+					bytes += read;
+					for (std::streamsize i = 0; i < read; ++i) {
+						if ((unsigned char)buf[i] < 32 && (buf[i] != '\r' && buf[i] != '\n' && buf[i] != '\t'))
+							++binaryish;
+					}
+
+					if (binaryish > bytes / 8) {
+						list.emplace_back(1.f, "binary");
+						return;
+					}
+				}
+			}
+		}
+
+		DataEnd();
+
+		if (mDetectedCharset)
+			list.emplace_back(1.f, mDetectedCharset);
+		else {
+			switch (mInputState) {
+			case eHighbyte: {
+				for (PRInt32 i=0; i<NUM_OF_CHARSET_PROBERS; i++) {
+					if (!mCharSetProbers[i]) continue;
+
+					float conf = mCharSetProbers[i]->GetConfidence();
+					if (conf > 0.01f)
+						list.emplace_back(conf, mCharSetProbers[i]->GetCharSetName());
+				}
+
+				break;
+			}
+			case ePureAscii:
+				list.emplace_back(1.f, "US-ASCII");
+				break;
+
+			default:
+				throw UnknownCharset("Unknown character set.");
+			}
+
+			if (list.empty() && (mInputState == eHighbyte))
+				throw UnknownCharset("Unknown character set.");
+
+			typedef std::pair<float, std::string> const& result;
+			sort(begin(list), end(list), [](result lft, result rgt) { return lft.first > rgt.first; });
+		}
+	}
+
+	/// @brief Detect character set of a file using UniversalCharDet
+	CharsetListDetected List() const { return list; }
+};
 }
 
-	} // namespace util
-} // namespace agi
+namespace agi { namespace charset {
+	std::string Detect(agi::fs::path const& file) {
+		return DetectAll(file).front().second;
+	}
+
+	CharsetListDetected DetectAll(agi::fs::path const& file) {
+		return UCDetect(file).List();
+	}
+} }
diff --git a/aegisub/libaegisub/common/charset_ucd.cpp b/aegisub/libaegisub/common/charset_ucd.cpp
deleted file mode 100644
index cc96f11514ff7bf1962f89d4a32e7072fa529c07..0000000000000000000000000000000000000000
--- a/aegisub/libaegisub/common/charset_ucd.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-/// @file charset_ucd.cpp
-/// @brief Character set detection using Universalchardet
-/// @ingroup libaegisub
-
-#include "charset_ucd.h"
-
-#include "libaegisub/io.h"
-#include "libaegisub/scoped_ptr.h"
-
-#include "../../universalchardet/nsCharSetProber.h"
-
-namespace agi {
-	namespace charset {
-
-UCDetect::UCDetect(const std::string &file)
-: nsUniversalDetector(NS_FILTER_ALL)
-{
-	{
-		agi::scoped_ptr<std::ifstream> fp(io::Open(file, true));
-
-		// If it's over 100 MB it's either binary or big enough that we won't
-		// be able to do anything useful with it anyway
-		fp->seekg(0, std::ios::end);
-		if (fp->tellg() > 100 * 1024 * 1024) {
-			list.emplace_back(1.f, "binary");
-			return;
-		}
-		fp->seekg(0, std::ios::beg);
-
-		std::streamsize binaryish = 0;
-		std::streamsize bytes = 0;
-
-		while (!mDone && *fp) {
-			char buf[4096];
-			fp->read(buf, sizeof(buf));
-			std::streamsize read = fp->gcount();
-			HandleData(buf, (PRUint32)read);
-
-			// A dumb heuristic to detect binary files
-			if (!mDone) {
-				bytes += read;
-				for (std::streamsize i = 0; i < read; ++i) {
-					if ((unsigned char)buf[i] < 32 && (buf[i] != '\r' && buf[i] != '\n' && buf[i] != '\t'))
-						++binaryish;
-				}
-
-				if (binaryish > bytes / 8) {
-					list.emplace_back(1.f, "binary");
-					return;
-				}
-			}
-		}
-	}
-
-	DataEnd();
-
-	if (mDetectedCharset)
-		list.emplace_back(1.f, mDetectedCharset);
-	else {
-		switch (mInputState) {
-			case eHighbyte: {
-				for (PRInt32 i=0; i<NUM_OF_CHARSET_PROBERS; i++) {
-					if (!mCharSetProbers[i]) continue;
-
-					float conf = mCharSetProbers[i]->GetConfidence();
-					if (conf > 0.01f)
-						list.emplace_back(conf, mCharSetProbers[i]->GetCharSetName());
-				}
-
-				break;
-			}
-			case ePureAscii:
-				list.emplace_back(1.f, "US-ASCII");
-				break;
-
-			default:
-				throw UnknownCharset("Unknown character set.");
-		}
-
-		if (list.empty() && (mInputState == eHighbyte))
-			throw UnknownCharset("Unknown character set.");
-
-		typedef std::pair<float, std::string> const& result;
-		sort(begin(list), end(list), [](result lft, result rgt) { return lft.first > rgt.first; });
-	}
-}
-
-std::string UCDetect::Single() const {
-	return list.front().second;
-}
-
-	} // namespace util
-} // namespace agi
diff --git a/aegisub/libaegisub/common/charset_ucd.h b/aegisub/libaegisub/common/charset_ucd.h
deleted file mode 100644
index 8b144cfc0ea01b00dd2638bbba62974932ae57cc..0000000000000000000000000000000000000000
--- a/aegisub/libaegisub/common/charset_ucd.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-/// @file charset_ucd.h
-/// @brief Character set detection using Universalchardet
-/// @ingroup libaegisub
-
-#include "libaegisub/charset.h"
-
-#include <string>
-
-#ifndef _WIN32
-#define _X86_ 1
-#endif
-
-#include "../../universalchardet/nscore.h"
-#include "../../universalchardet/nsUniversalDetector.h"
-#include "../../universalchardet/nsMBCSGroupProber.h"
-
-namespace agi {
-	namespace charset {
-
-class UCDetect : public nsUniversalDetector {
-	/// List of detected character sets.
-	CharsetListDetected list;
-
-	/// Stub.
-	void Report(const char* aCharset) {};
-
-public:
-
-	/// @brief Detect character set of a file using UniversalCharDetect
-	/// @param file File to check
-	UCDetect(const std::string &file);
-
-	/// @brief Detect character set of a file using UniversalCharDet
-	CharsetListDetected List() const { return list; }
-
-	/// @brief Return a single character set (highest confidence)
-	/// @return Character set
-	std::string Single() const;
-};
-
-	} // namespace util
-} // namespace agi
diff --git a/aegisub/libaegisub/common/dispatch.cpp b/aegisub/libaegisub/common/dispatch.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb04f6d491a1e2fa7a51fb472cf448601ed82686
--- /dev/null
+++ b/aegisub/libaegisub/common/dispatch.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "libaegisub/dispatch.h"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/strand.hpp>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+namespace {
+	boost::asio::io_service *service;
+	std::function<void (agi::dispatch::Thunk)> invoke_main;
+
+	class MainQueue : public agi::dispatch::Queue {
+		void DoInvoke(agi::dispatch::Thunk thunk) {
+			invoke_main(thunk);
+		}
+	};
+
+	class BackgroundQueue : public agi::dispatch::Queue {
+		void DoInvoke(agi::dispatch::Thunk thunk) {
+			service->post(thunk);
+		}
+	};
+
+	class SerialQueue : public agi::dispatch::Queue {
+		boost::asio::io_service::strand strand;
+
+		void DoInvoke(agi::dispatch::Thunk thunk) {
+			strand.post(thunk);
+		}
+	public:
+		SerialQueue() : strand(*service) { }
+	};
+}
+
+namespace agi { namespace dispatch {
+
+void Init(std::function<void (Thunk)> invoke_main) {
+	static boost::asio::io_service service;
+	static boost::asio::io_service::work work(service);
+	::service = &service;
+	::invoke_main = invoke_main;
+
+	for (unsigned i = 0; i < std::max<unsigned>(1, std::thread::hardware_concurrency()); ++i)
+		std::thread([]{ ::service->run(); }).detach();
+}
+
+void Queue::Async(Thunk thunk) {
+	DoInvoke(thunk);
+}
+
+void Queue::Sync(Thunk thunk) {
+	std::mutex m;
+	std::condition_variable cv;
+	std::unique_lock<std::mutex> l(m);
+	bool done = false;
+	DoInvoke([&]{
+		std::unique_lock<std::mutex> l(m);
+		thunk();
+		done = true;
+		cv.notify_all();
+	});
+	cv.wait(l, [&]{ return done; });
+}
+
+Queue& Main() {
+	static MainQueue q;
+	return q;
+}
+
+Queue& Background() {
+	static BackgroundQueue q;
+	return q;
+}
+
+std::unique_ptr<Queue> Create() {
+	return std::unique_ptr<Queue>(new SerialQueue);
+}
+
+} }
diff --git a/aegisub/libaegisub/common/fs.cpp b/aegisub/libaegisub/common/fs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ecdd30c6e7e9d0e42704438f27ef165fdf4bf9db
--- /dev/null
+++ b/aegisub/libaegisub/common/fs.cpp
@@ -0,0 +1,106 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "libaegisub/fs.h"
+
+#include "libaegisub/access.h"
+#include "libaegisub/exception.h"
+#include "libaegisub/log.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+
+namespace bfs = boost::filesystem;
+namespace ec = boost::system::errc;
+
+// boost::filesystem functions throw a single exception type for all
+// errors, which isn't really what we want, so do some crazy wrapper
+// shit to map error codes to more useful exceptions.
+#define CHECKED_CALL(exp, src_path, dst_path) \
+	boost::system::error_code ec; \
+	exp; \
+	switch (ec.value()) {\
+		case ec::success: break; \
+		case ec::no_such_file_or_directory: throw FileNotFound(src_path); \
+		case ec::is_a_directory: throw NotAFile(src_path); \
+		case ec::not_a_directory: throw NotADirectory(src_path); \
+		case ec::no_space_on_device: throw DriveFull(dst_path); \
+		case ec::permission_denied: \
+			if (!src_path.empty()) \
+				acs::CheckFileRead(src_path); \
+			if (!dst_path.empty()) \
+				acs::CheckFileWrite(dst_path); \
+			throw AccessDenied(src_path); \
+		default: \
+			LOG_D("filesystem") << "Unknown error when calling '" << #exp << "': " << ec << ": " << ec.message(); \
+			throw FileSystemUnknownError(ec.message()); \
+	}
+
+#define CHECKED_CALL_RETURN(exp, src_path) \
+	CHECKED_CALL(auto ret = exp, src_path, agi::fs::path()); \
+	return ret
+
+#define WRAP_BFS(bfs_name, agi_name) \
+	auto agi_name(path const& p) -> decltype(bfs::bfs_name(p)) { \
+		CHECKED_CALL_RETURN(bfs::bfs_name(p, ec), p); \
+	}
+
+#define WRAP_BFS_IGNORE_ERROR(bfs_name, agi_name) \
+	auto agi_name(path const& p) -> decltype(bfs::bfs_name(p)) { \
+		boost::system::error_code ec; \
+		return bfs::bfs_name(p, ec); \
+	}
+
+// sasuga windows.h
+#undef CreateDirectory
+
+namespace agi { namespace fs {
+	WRAP_BFS_IGNORE_ERROR(exists, Exists)
+	WRAP_BFS_IGNORE_ERROR(is_regular_file, FileExists)
+	WRAP_BFS_IGNORE_ERROR(is_directory, DirectoryExists)
+	WRAP_BFS(file_size, SizeImpl)
+	WRAP_BFS(last_write_time, ModifiedTime)
+	WRAP_BFS(create_directories, CreateDirectory)
+	WRAP_BFS(space, Space)
+	WRAP_BFS(remove, Remove)
+	WRAP_BFS(canonical, Canonicalize)
+
+	uintmax_t Size(path const& p) {
+		if (DirectoryExists(p))
+			throw NotAFile(p);
+		return SizeImpl(p);
+	}
+
+	uintmax_t FreeSpace(path const& p) {
+		return Space(p).available;
+	}
+
+	void Rename(const path& from, const path& to) {
+		CHECKED_CALL(bfs::rename(from, to, ec), from, to);
+	}
+
+	void Copy(path const& from, path const& to) {
+		CreateDirectory(to.parent_path());
+		CHECKED_CALL(bfs::copy_file(from, to, bfs::copy_option::overwrite_if_exists, ec), from, to);
+	}
+
+	bool HasExtension(path const& p, std::string const& ext) {
+		if (!p.has_extension()) return ext.empty();
+		return boost::iequals(p.extension().string().substr(1), ext);
+	}
+} }
diff --git a/aegisub/libaegisub/common/hotkey.cpp b/aegisub/libaegisub/common/hotkey.cpp
index efde672276667ba2e5351497372c1b236a4d1d75..b42153ccfb33bb3c87a3a622bbc205e1b7c5a437 100644
--- a/aegisub/libaegisub/common/hotkey.cpp
+++ b/aegisub/libaegisub/common/hotkey.cpp
@@ -18,11 +18,6 @@
 
 #include "../config.h"
 
-#include <algorithm>
-#include <cmath>
-#include <memory>
-#include <tuple>
-
 #include "libaegisub/hotkey.h"
 
 #include "libaegisub/cajun/writer.h"
@@ -31,8 +26,12 @@
 #include "libaegisub/json.h"
 #include "libaegisub/log.h"
 
+#include <algorithm>
 #include <boost/algorithm/string/join.hpp>
 #include <boost/range/adaptor/map.hpp>
+#include <cmath>
+#include <fstream>
+#include <tuple>
 
 namespace agi {
 	namespace hotkey {
@@ -50,12 +49,12 @@ void Hotkey::ComboInsert(Combo const& combo) {
 	cmd_map.insert(make_pair(combo.CmdName(), combo));
 }
 
-Hotkey::Hotkey(const std::string &file, const std::string &default_config)
+Hotkey::Hotkey(agi::fs::path const& file, const std::string &default_config)
 : config_file(file)
 {
 	LOG_D("hotkey/init") << "Generating hotkeys.";
 
-	json::Object object(agi::json_util::file(config_file, default_config));
+	const json::Object object(agi::json_util::file(config_file, default_config));
 	for (auto const& hotkey_context : object)
 		BuildHotkey(hotkey_context.first, hotkey_context.second);
 }
@@ -118,7 +117,7 @@ std::vector<std::string> Hotkey::GetHotkeys(const std::string &context, const st
 	for (std::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) {
 		std::string ctext = it->second.Context();
 		if (ctext == "Always" || ctext == "Default" || ctext == context)
-			ret.push_back(it->second.StrMenu());
+			ret.emplace_back(it->second.StrMenu());
 	}
 
 	sort(ret.begin(), ret.end());
diff --git a/aegisub/libaegisub/common/io.cpp b/aegisub/libaegisub/common/io.cpp
index ebd7bfe46184a99c5e6b20578ebb057ebff6cab9..798b65923ef27190032f007a8556b3a0bb33f58b 100644
--- a/aegisub/libaegisub/common/io.cpp
+++ b/aegisub/libaegisub/common/io.cpp
@@ -16,49 +16,25 @@
 /// @brief Windows IO methods.
 /// @ingroup libaegisub
 
-#include <sys/stat.h>
-#include <errno.h>
-
-#include <iostream>
-#include <fstream>
+#include "libaegisub/io.h"
 
 #include <libaegisub/access.h>
-#include <libaegisub/charset_conv_win.h>
-#include "libaegisub/io.h"
+#include "libaegisub/fs.h"
 #include "libaegisub/log.h"
+#include "libaegisub/path.h"
 #include "libaegisub/util.h"
 
-#ifdef _WIN32
-#define snprintf sprintf_s
-#endif
-
-namespace {
-	std::string make_temp_name(std::string const& filename) {
-		char tmp[1024];
-		snprintf(tmp, sizeof tmp, "_tmp_%lld", (long long)time(0));
-
-		std::string::size_type pos = filename.rfind('.');
-		if (pos == std::string::npos)
-			return filename + tmp;
-
-		return filename.substr(0, pos) + tmp + filename.substr(pos);
-	}
-}
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
 
 namespace agi {
 	namespace io {
 
-using agi::charset::ConvertW;
-
-#ifndef _WIN32
-#define ConvertW
-#endif
-
-std::ifstream* Open(const std::string &file, bool binary) {
+std::ifstream* Open(fs::path const& file, bool binary) {
 	LOG_D("agi/io/open/file") << file;
 	acs::CheckFileRead(file);
 
-	std::ifstream *stream = new std::ifstream(ConvertW(file).c_str(), (binary ? std::ios::binary : std::ios::in));
+	auto stream = new boost::filesystem::ifstream(file, (binary ? std::ios::binary : std::ios::in));
 
 	if (stream->fail()) {
 		delete stream;
@@ -68,34 +44,30 @@ std::ifstream* Open(const std::string &file, bool binary) {
 	return stream;
 }
 
-Save::Save(const std::string& file, bool binary)
+Save::Save(fs::path const& file, bool binary)
 : file_name(file)
-, tmp_name(make_temp_name(file))
+, tmp_name(unique_path(file.parent_path()/(file.stem().string() + "_tmp_%%%%." + file.extension().string())))
 {
 	LOG_D("agi/io/save/file") << file;
-	const std::string pwd = util::DirName(file);
-
-	acs::CheckDirWrite(pwd);
+	acs::CheckDirWrite(file.parent_path());
 
 	try {
 		acs::CheckFileWrite(file);
-	} catch (FileNotFoundError const&) {
-		// If the file doesn't exist we create a 0 byte file, this so so
-		// util::Rename will find it, and to let users know something went
-		// wrong by leaving a 0 byte file.
-		std::ofstream(ConvertW(file).c_str());
+	}
+	catch (fs::FileNotFound const&) {
+		// Not an error
 	}
 
-	fp = new std::ofstream(ConvertW(tmp_name).c_str(), binary ? std::ios::binary : std::ios::out);
+	fp = new boost::filesystem::ofstream(tmp_name, binary ? std::ios::binary : std::ios::out);
 	if (!fp->good()) {
 		delete fp;
-		throw agi::FileNotAccessibleError("Could not create temporary file at: " + tmp_name);
+		throw fs::WriteDenied(tmp_name);
 	}
 }
 
 Save::~Save() {
-	delete fp;
-	util::Rename(tmp_name, file_name);
+	delete fp; // Explicitly delete to unlock file on Windows
+	fs::Rename(tmp_name, file_name);
 }
 
 std::ofstream& Save::Get() {
diff --git a/aegisub/libaegisub/common/json.cpp b/aegisub/libaegisub/common/json.cpp
index 8f9f725f842eff1d48951fe76664ac93809b0009..dfa40e7d594f350e8699a48b909bb1675c4bd9be 100644
--- a/aegisub/libaegisub/common/json.cpp
+++ b/aegisub/libaegisub/common/json.cpp
@@ -18,21 +18,22 @@
 
 #include "../config.h"
 
+#include "libaegisub/json.h"
+
 #include <fstream>
+#include <memory>
 #include <sstream>
 
+#include "libaegisub/fs.h"
 #include "libaegisub/io.h"
-#include "libaegisub/json.h"
 #include "libaegisub/log.h"
-#include "libaegisub/scoped_ptr.h"
-
 
 namespace agi {
 	namespace json_util {
 
 json::UnknownElement parse(std::istream *stream) {
 	try {
-		agi::scoped_ptr<std::istream> stream_deleter(stream);
+		std::unique_ptr<std::istream> stream_deleter(stream);
 
 		json::UnknownElement root;
 		json::Reader::Read(root, *stream);
@@ -46,15 +47,15 @@ json::UnknownElement parse(std::istream *stream) {
 	}
 }
 
-json::UnknownElement file(const std::string &file) {
+json::UnknownElement file(agi::fs::path const& file) {
 	return parse(io::Open(file));
 }
 
-json::UnknownElement file(const std::string &file, const std::string &default_config) {
+json::UnknownElement file(agi::fs::path const& file, const std::string &default_config) {
 	try {
 		return parse(io::Open(file));
 	}
-	catch (FileNotFoundError const&) {
+	catch (fs::FileNotFound const&) {
 		// Not an error
 		return parse(new std::istringstream(default_config));
 	}
diff --git a/aegisub/libaegisub/common/keyframe.cpp b/aegisub/libaegisub/common/keyframe.cpp
index 3dbcda83408eff512460cfdb42ffa9f14537130e..fa98c41cad14e03329b7fb0d69faf464619a7b23 100644
--- a/aegisub/libaegisub/common/keyframe.cpp
+++ b/aegisub/libaegisub/common/keyframe.cpp
@@ -17,16 +17,15 @@
 /// @ingroup libaegisub
 ///
 
-
 #include "../config.h"
 
+#include "libaegisub/keyframe.h"
+
 #include <algorithm>
 #include <fstream>
 
 #include "libaegisub/io.h"
 #include "libaegisub/line_iterator.h"
-#include "libaegisub/keyframe.h"
-#include "libaegisub/vfr.h"
 
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/range/algorithm.hpp>
@@ -77,7 +76,7 @@ char x264(std::string const& line) {
 }
 
 namespace agi { namespace keyframe {
-void Save(std::string const& filename, std::vector<int> const& keyframes) {
+void Save(agi::fs::path const& filename, std::vector<int> const& keyframes) {
 	io::Save file(filename);
 	std::ofstream& of = file.Get();
 	of << "# keyframe format v1" << std::endl;
@@ -85,7 +84,7 @@ void Save(std::string const& filename, std::vector<int> const& keyframes) {
 	boost::copy(keyframes, std::ostream_iterator<int>(of, "\n"));
 }
 
-std::vector<int> Load(std::string const& filename) {
+std::vector<int> Load(agi::fs::path const& filename) {
 	std::unique_ptr<std::ifstream> file(io::Open(filename));
 	std::istream &is(*file);
 
diff --git a/aegisub/libaegisub/common/log.cpp b/aegisub/libaegisub/common/log.cpp
index 016870ed270324d9adad96bb36f78cab4a462b7f..8486a575e47b1f4824c2839d8d3552b3f752f30d 100644
--- a/aegisub/libaegisub/common/log.cpp
+++ b/aegisub/libaegisub/common/log.cpp
@@ -18,21 +18,20 @@
 
 #include "../config.h"
 
-#include <algorithm>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <memory>
-#include <sstream>
+#include "libaegisub/log.h"
 
 #include "libaegisub/cajun/elements.h"
 #include "libaegisub/cajun/writer.h"
 #include "libaegisub/io.h"
-#include "libaegisub/log.h"
 #include "libaegisub/types.h"
 #include "libaegisub/util.h"
 
+#include <algorithm>
 #include <boost/range/algorithm.hpp>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <memory>
 
 namespace agi {
 	namespace log {
@@ -109,7 +108,7 @@ Message::~Message() {
 	agi::log::log->log(sm);
 }
 
-JsonEmitter::JsonEmitter(std::string const& directory, const agi::log::LogSink *log_sink)
+JsonEmitter::JsonEmitter(agi::fs::path const& directory, const agi::log::LogSink *log_sink)
 : directory(directory)
 , log_sink(log_sink)
 {
@@ -146,9 +145,7 @@ JsonEmitter::~JsonEmitter() {
 	timeval_close.push_back((int64_t)time_close.tv_sec);
 	timeval_close.push_back((int64_t)time_close.tv_usec);
 
-	std::stringstream str;
-	str << directory << time_start.tv_sec << ".json";
-	json::Writer::Write(root, io::Save(str.str()).Get());
+	json::Writer::Write(root, io::Save(directory/(std::to_string(time_start.tv_sec) + ".json")).Get());
 }
 
 	} // namespace log
diff --git a/aegisub/libaegisub/common/mru.cpp b/aegisub/libaegisub/common/mru.cpp
index 8f5729c27da684d11a96979da7f65bde771d0d80..6ca4b8b95d1b232e5b9e8fbf8f9af37141cd65dc 100644
--- a/aegisub/libaegisub/common/mru.cpp
+++ b/aegisub/libaegisub/common/mru.cpp
@@ -30,7 +30,7 @@
 
 namespace agi {
 
-MRUManager::MRUManager(std::string const& config, std::string const& default_config, agi::Options *options)
+MRUManager::MRUManager(agi::fs::path const& config, std::string const& default_config, agi::Options *options)
 : config_name(config)
 , options(options)
 {
@@ -60,16 +60,22 @@ MRUManager::MRUListMap &MRUManager::Find(std::string const& key) {
 	return index->second;
 }
 
-void MRUManager::Add(std::string const& key, std::string const& entry) {
+void MRUManager::Add(std::string const& key, agi::fs::path const& entry) {
 	MRUListMap &map = Find(key);
-	map.remove(entry);
-	map.push_front(entry);
-	Prune(key, map);
+	auto it = find(begin(map), end(map), entry);
+	if (it == begin(map) && it != end(map))
+		return;
+	if (it != end(map))
+		map.splice(begin(map), map, it);
+	else {
+		map.push_front(entry);
+		Prune(key, map);
+	}
 
 	Flush();
 }
 
-void MRUManager::Remove(std::string const& key, std::string const& entry) {
+void MRUManager::Remove(std::string const& key, agi::fs::path const& entry) {
 	Find(key).remove(entry);
 
 	Flush();
@@ -79,7 +85,7 @@ const MRUManager::MRUListMap* MRUManager::Get(std::string const& key) {
 	return &Find(key);
 }
 
-std::string const& MRUManager::GetEntry(std::string const& key, const size_t entry) {
+agi::fs::path const& MRUManager::GetEntry(std::string const& key, const size_t entry) {
 	const MRUManager::MRUListMap *const map = Get(key);
 
 	if (entry >= map->size())
@@ -93,7 +99,8 @@ void MRUManager::Flush() {
 
 	for (auto const& mru_map : mru) {
 		json::Array &array = out[mru_map.first];
-		array.insert(array.end(), mru_map.second.begin(), mru_map.second.end());
+		transform(begin(mru_map.second), end(mru_map.second),
+			back_inserter(array), [](agi::fs::path const& p) { return p.string(); });
 	}
 
 	json::Writer::Write(out, io::Save(config_name).Get());
@@ -116,7 +123,8 @@ void MRUManager::Prune(std::string const& key, MRUListMap& map) const {
 /// @param array json::Array of values.
 void MRUManager::Load(std::string const& key, const json::Array& array) {
 	try {
-		copy(array.begin(), array.end(), back_inserter(mru[key]));
+		transform(begin(array), end(array),
+			back_inserter(mru[key]), [](std::string const& s) { return s; });
 	}
 	catch (json::Exception const&) {
 		// Out of date MRU file; just discard the data and skip it
diff --git a/aegisub/libaegisub/common/option.cpp b/aegisub/libaegisub/common/option.cpp
index 14147c11e2c425cea3d03a89589585d0c7932261..f57bd6a911e44b076b9e51067923cfda36ad1ebd 100644
--- a/aegisub/libaegisub/common/option.cpp
+++ b/aegisub/libaegisub/common/option.cpp
@@ -20,14 +20,11 @@
 
 #include "libaegisub/option.h"
 
-#include <cassert>
-#include <memory>
-#include <sstream>
-
 #include "libaegisub/cajun/reader.h"
 #include "libaegisub/cajun/writer.h"
 #include "libaegisub/cajun/elements.h"
 
+#include "libaegisub/fs.h"
 #include "libaegisub/io.h"
 #include "libaegisub/log.h"
 #include "libaegisub/option_value.h"
@@ -35,6 +32,9 @@
 #include "option_visit.h"
 
 #include <boost/range/adaptor/map.hpp>
+#include <cassert>
+#include <memory>
+#include <sstream>
 
 namespace {
 	/// @brief Write an option to a json object
@@ -66,7 +66,7 @@ namespace {
 
 namespace agi {
 
-Options::Options(const std::string &file, const std::string& default_config, const OptionSetting setting)
+Options::Options(agi::fs::path const& file, const std::string& default_config, const OptionSetting setting)
 : config_file(file)
 , setting(setting)
 {
@@ -88,21 +88,17 @@ void Options::ConfigNext(std::istream& stream) {
 }
 
 void Options::ConfigUser() {
-	std::unique_ptr<std::istream> stream;
-
 	try {
-		stream.reset(agi::io::Open(config_file));
-	} catch (const FileNotFoundError&) {
+		std::unique_ptr<std::istream> stream(io::Open(config_file));
+		LoadConfig(*stream, true);
+	}
+	catch (fs::FileNotFound const&) {
 		return;
 	}
-
 	/// @todo Handle other errors such as parsing and notifying the user.
-	LoadConfig(*stream, true);
 }
 
 void Options::LoadConfig(std::istream& stream, bool ignore_errors) {
-	/// @todo Store all previously loaded configs in an array for bug report purposes,
-	///       this is just a temp stub.
 	json::UnknownElement config_root;
 
 	try {
diff --git a/aegisub/libaegisub/common/path.cpp b/aegisub/libaegisub/common/path.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..680495b8ab61a49b242d21279a3053a57e482709
--- /dev/null
+++ b/aegisub/libaegisub/common/path.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file path.cpp
+/// @brief Platform-independent path code
+/// @ingroup libaegisub
+
+#include "../config.h"
+
+#include "libaegisub/path.h"
+
+#include "libaegisub/fs.h"
+#include "libaegisub/util.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/range/distance.hpp>
+
+namespace {
+	template<class T, class U>
+	typename T::const_iterator last_less_than(T const& cont, U const& value) {
+		auto it = cont.upper_bound(value);
+		if (it != cont.begin())
+			--it;
+		return it;
+	}
+}
+
+namespace agi {
+
+Path::Path() {
+	tokens["?user"];
+	tokens["?local"];
+	tokens["?data"];
+	tokens["?temp"];
+	tokens["?dictionary"];
+	tokens["?docs"];
+
+	FillPlatformSpecificPaths();
+
+	tokens["?audio"];
+	tokens["?script"];
+	tokens["?video"];
+}
+
+fs::path Path::Decode(std::string const& path) const {
+	const auto it = last_less_than(tokens, path);
+
+	if (!it->second.empty() && boost::starts_with(path, it->first))
+		return (it->second/path.substr(it->first.size())).make_preferred();
+	return fs::path(path).make_preferred();
+}
+
+fs::path Path::MakeRelative(fs::path const& path, std::string const& token) const {
+	const auto it = tokens.find(token);
+	if (it == tokens.end()) throw agi::InternalError("Bad token: " + token, 0);
+
+	return MakeRelative(path, it->second);
+}
+
+fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const {
+	if (path.empty() || base.empty()) return path;
+
+	const auto str = path.string();
+	if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:"))
+		return path;
+
+	// Paths on different volumes can't be made relative to each other
+	if (path.has_root_name() && path.root_name() != base.root_name())
+		return path.string();
+
+	auto path_it = path.begin();
+	auto ref_it = base.begin();
+	for (; path_it != path.end() && ref_it != base.end() && *path_it == *ref_it; ++path_it, ++ref_it) ;
+
+	agi::fs::path result;
+	for (; ref_it != base.end(); ++ref_it)
+		result /= "..";
+	for (; path_it != path.end(); ++path_it)
+		result /= *path_it;
+
+	return result;
+}
+
+fs::path Path::MakeAbsolute(fs::path path, std::string const& token) const {
+	const auto it = tokens.find(token);
+	if (it == tokens.end()) throw agi::InternalError("Bad token: " + token, 0);
+	if (path.empty()) return path;
+
+	path.make_preferred();
+	const auto str = path.string();
+	if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:"))
+		return path;
+	return (it->second.empty() || path.is_absolute()) ? path : it->second/path;
+}
+
+std::string Path::Encode(fs::path const& path) const {
+	// Find the shortest encoding of path made relative to each token
+	std::string shortest = path.string();
+	size_t length = boost::distance(path);
+	for (auto const& tok : tokens) {
+		if (tok.second.empty()) continue;
+
+		const auto p = MakeRelative(path, tok.first);
+		const size_t d = boost::distance(p);
+		if (d < length) {
+			length = d;
+			shortest = (tok.first/p).string();
+		}
+	}
+
+	return shortest;
+}
+
+void Path::SetToken(std::string const& token_name, fs::path const& token_value) {
+	const auto it = tokens.find(token_name);
+	if (it == tokens.end()) throw agi::InternalError("Bad token: " + token_name, 0);
+
+	if (token_value.empty())
+		it->second = token_value;
+	else if (!token_value.is_absolute())
+		it->second.clear();
+	else {
+		it->second = token_value;
+		it->second.make_preferred();
+		if (fs::FileExists(it->second))
+			it->second = it->second.parent_path();
+	}
+
+	paths.clear();
+	for (auto const& tok : tokens) {
+		if (!tok.second.empty())
+			paths[tok.second] = tok.first;
+	}
+}
+
+}
diff --git a/aegisub/libaegisub/common/thesaurus.cpp b/aegisub/libaegisub/common/thesaurus.cpp
index 956fb144a74481ce47a3b79baf637426b75a1bb8..0c66368c5d9ccbb3e52017755dc11163c72d8791 100644
--- a/aegisub/libaegisub/common/thesaurus.cpp
+++ b/aegisub/libaegisub/common/thesaurus.cpp
@@ -26,16 +26,16 @@
 #include <boost/phoenix/operator/comparison.hpp>
 #include <boost/phoenix/core/argument.hpp>
 
-#include <cstdlib>
+#include <fstream>
 
 using boost::phoenix::placeholders::_1;
 
 namespace agi {
 
-Thesaurus::Thesaurus(std::string const& dat_path, std::string const& idx_path)
+Thesaurus::Thesaurus(agi::fs::path const& dat_path, agi::fs::path const& idx_path)
 : dat(io::Open(dat_path))
 {
-	scoped_ptr<std::ifstream> idx(io::Open(idx_path));
+	std::unique_ptr<std::ifstream> idx(io::Open(idx_path));
 
 	std::string encoding_name;
 	getline(*idx, encoding_name);
diff --git a/aegisub/libaegisub/common/util.cpp b/aegisub/libaegisub/common/util.cpp
index 52ddb0c12fb149db9a94ea243d92be7681b9c912..b1b738e23afd2a7e6fbe70950d728a2d9162576d 100644
--- a/aegisub/libaegisub/common/util.cpp
+++ b/aegisub/libaegisub/common/util.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2011, Amar Takhar <verm@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -11,36 +11,26 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
 
-/// @file util.cpp
-/// @brief Unix utility methods.
-/// @ingroup libaegisub
-
-#include <errno.h>
-
-#include <climits>
-#include <cstdio>
+#include "../config.h"
 
 #include "libaegisub/util.h"
 
-#include <boost/algorithm/string/case_conv.hpp>
-
-namespace agi {
-	namespace util {
-
-void str_lower(std::string &str) {
-	boost::to_lower(str);
-}
+#include <ctime>
 
-int strtoi(std::string const& str) {
-	errno = 0;
-	long l = strtol(str.c_str(), nullptr, 10);
+namespace agi { namespace util {
 
-	if ((errno == ERANGE) || (l < INT_MIN) || (l > INT_MAX))
-		return 0;
+std::string strftime(const char *fmt, const tm *tmptr) {
+	if (!tmptr) {
+		time_t t = time(nullptr);
+		tmptr = localtime(&t);
+	}
 
-	return (int)l;
+	char buff[65536];
+	::strftime(buff, sizeof buff, fmt, tmptr);
+	return buff;
 }
 
-	} // namespace util
-} // namespace agi
+} }
diff --git a/aegisub/libaegisub/common/vfr.cpp b/aegisub/libaegisub/common/vfr.cpp
index 897c26850b19e2f12d3450b9a46fdc733d4e5047..39288741f33efef6be6ec2d932625ac74809a15b 100644
--- a/aegisub/libaegisub/common/vfr.cpp
+++ b/aegisub/libaegisub/common/vfr.cpp
@@ -16,10 +16,13 @@
 /// @brief Framerate handling of all sorts
 /// @ingroup libaegisub video_input
 
+#include "../config.h"
+
 #include "libaegisub/vfr.h"
 
 #include <algorithm>
 #include <cmath>
+#include <fstream>
 #include <functional>
 #include <iterator>
 #include <list>
@@ -199,7 +202,7 @@ Framerate &Framerate::operator=(double fps) {
 	return *this = Framerate(fps);
 }
 
-Framerate::Framerate(std::string const& filename)
+Framerate::Framerate(fs::path const& filename)
 : denominator(default_denominator)
 , numerator(0)
 {
@@ -221,7 +224,7 @@ Framerate::Framerate(std::string const& filename)
 	throw UnknownFormat(line);
 }
 
-void Framerate::Save(std::string const& filename, int length) const {
+void Framerate::Save(fs::path const& filename, int length) const {
 	agi::io::Save file(filename);
 	std::ofstream &out = file.Get();
 
diff --git a/aegisub/libaegisub/include/libaegisub/access.h b/aegisub/libaegisub/include/libaegisub/access.h
index ee5ecb01ef740463a7128b577786df2bc8b09553..f58d2d0fa098f414c691c2a112a3c404b82d7d98 100644
--- a/aegisub/libaegisub/include/libaegisub/access.h
+++ b/aegisub/libaegisub/include/libaegisub/access.h
@@ -17,17 +17,11 @@
 /// @ingroup libaegisub
 
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace agi {
 	namespace acs {
 
-DEFINE_SIMPLE_EXCEPTION_NOINNER(Fatal, FileSystemError, "filesystem/fatal");
-DEFINE_SIMPLE_EXCEPTION_NOINNER(NotAFile, FileSystemError, "filesystem/not_a_file")
-DEFINE_SIMPLE_EXCEPTION_NOINNER(NotADirectory, FileSystemError, "filesystem/not_a_directory")
-
-DEFINE_SIMPLE_EXCEPTION_NOINNER(Read, FileNotAccessibleError, "filesystem/not_accessible/read_permission")
-DEFINE_SIMPLE_EXCEPTION_NOINNER(Write, FileNotAccessibleError, "filesystem/not_accessible/write_permission")
-
 enum Type {
 	FileRead,
 	DirRead,
@@ -35,13 +29,13 @@ enum Type {
 	DirWrite
 };
 
-void Check(const std::string &file, acs::Type);
+void Check(fs::path const& file, acs::Type);
 
-void CheckFileRead(const std::string &file);
-void CheckDirRead(const std::string &dir);
+inline void CheckFileRead(fs::path const& file) { Check(file, acs::FileRead); }
+inline void CheckFileWrite(fs::path const& file) { Check(file, acs::FileWrite); }
 
-void CheckFileWrite(const std::string &file);
-void CheckDirWrite(const std::string &dir);
+inline void CheckDirRead(fs::path const& dir) { Check(dir, acs::DirRead); }
+inline void CheckDirWrite(fs::path const& dir) { Check(dir, acs::DirWrite); }
 
 	} // namespace axs
 } // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/charset.h b/aegisub/libaegisub/include/libaegisub/charset.h
index 971a3ee086214ce65de0b13074660500657aa64a..0c6cdbd172f011349939a4b4e542fdcb0501e628 100644
--- a/aegisub/libaegisub/include/libaegisub/charset.h
+++ b/aegisub/libaegisub/include/libaegisub/charset.h
@@ -16,11 +16,11 @@
 /// @brief Character set detection and manipulation utilities.
 /// @ingroup libaegisub
 
-#include <fstream>
-#include <functional>
+#include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
 #include <string>
 #include <vector>
-#include <libaegisub/exception.h>
 
 namespace agi {
 	/// Character set conversion and detection.
@@ -35,12 +35,12 @@ typedef std::vector<std::pair<float, std::string>> CharsetListDetected;
 /// @brief Return a complete list of detected character sets ordered by precedence.
 /// @param file File to check
 /// @return List of possible charsets sorted by probability
-CharsetListDetected DetectAll(std::string const& file);
+CharsetListDetected DetectAll(agi::fs::path const& file);
 
 /// @brief Returns the character set with the highest confidence
 /// @param file File to check
 /// @return Detected character set.
-std::string Detect(const std::string &file);
+std::string Detect(agi::fs::path const& file);
 
 	} // namespace util
 } // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/charset_conv_win.h b/aegisub/libaegisub/include/libaegisub/charset_conv_win.h
index f7551daa9fee332138a6d9486fc2a2d1a467b338..ad89cf3d8dc77837884086dceb276808a1ad10b6 100644
--- a/aegisub/libaegisub/include/libaegisub/charset_conv_win.h
+++ b/aegisub/libaegisub/include/libaegisub/charset_conv_win.h
@@ -23,5 +23,8 @@ namespace agi {
 		/// Convert a UTF-8 string to a string suitable for use with Win32 API functions
 		std::wstring ConvertW(std::string const& src);
 		std::string ConvertW(std::wstring const& src);
+
+		/// Convert a UTF-16 string to the local charset
+		std::string ConvertLocal(std::wstring const& src);
 	}
 }
diff --git a/aegisub/libaegisub/include/libaegisub/dispatch.h b/aegisub/libaegisub/include/libaegisub/dispatch.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8be32fe3ddce65404e9a4d64eadb61ebca19f0c
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/dispatch.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include <functional>
+#include <memory>
+
+namespace agi {
+	namespace dispatch {
+		typedef std::function<void()> Thunk;
+
+		class Queue {
+			virtual void DoInvoke(Thunk thunk)=0;
+		public:
+			virtual ~Queue() { }
+
+			/// Invoke the thunk on this processing queue, returning immediately
+			void Async(Thunk thunk);
+
+			/// Invoke the thunk on this processing queue, returning only when
+			/// it's complete
+			void Sync(Thunk thunk);
+		};
+
+		/// Initialize the dispatch thread pools
+		/// @param invoke_main A function which invokes the thunk on the GUI thread
+		void Init(std::function<void (Thunk)> invoke_main);
+
+		/// Get the main queue, which runs on the GUI thread
+		Queue& Main();
+
+		/// Get the generic background queue, which runs thunks in parallel
+		Queue& Background();
+
+		/// Create a new serial queue
+		std::unique_ptr<Queue> Create();
+	}
+}
diff --git a/aegisub/libaegisub/include/libaegisub/exception.h b/aegisub/libaegisub/include/libaegisub/exception.h
index cd4f34693c96c3cd907caabe73c222a4709f0dde..2290d80ad624288b0a355364fde480368bf3df5b 100644
--- a/aegisub/libaegisub/include/libaegisub/exception.h
+++ b/aegisub/libaegisub/include/libaegisub/exception.h
@@ -37,10 +37,7 @@
 #include <memory>
 #include <string>
 
-/// @see aegisub.h
 namespace agi {
-
-
 	/// @class Exception
 	/// @brief Base class for all exceptions in Aegisub.
 	///
@@ -90,7 +87,6 @@ namespace agi {
 	/// could not be opened for reading". This is the purpose of the "inner"
 	/// exceptions.
 	class Exception {
-
 		/// The error message
 		std::string message;
 
@@ -98,16 +94,13 @@ namespace agi {
 		std::shared_ptr<Exception> inner;
 
 	protected:
-
 		/// @brief Protected constructor initialising members
 		/// @param msg The error message
 		/// @param inr The inner exception, optional
 		///
 		/// Deriving classes should always use this constructor for initialising
 		/// the base class.
-		Exception(const std::string &msg, const Exception *inr = 0)
-			: message(msg)
-		{
+		Exception(const std::string &msg, const Exception *inr = 0) : message(msg) {
 			if (inr)
 				inner.reset(inr->Copy());
 		}
@@ -120,9 +113,7 @@ namespace agi {
 
 	public:
 		/// @brief Destructor
-		virtual ~Exception()
-		{
-		}
+		virtual ~Exception() { }
 
 		/// @brief Get the outer exception error message
 		/// @return Error message
@@ -147,7 +138,6 @@ namespace agi {
 		/// level are [a-z0-9_].
 		virtual const char * GetName() const = 0;
 
-
 		/// @brief Convert to char array as the error message
 		/// @return The error message
 		operator const char * () { return GetMessage().c_str(); }
@@ -164,16 +154,12 @@ namespace agi {
 		virtual Exception *Copy() const = 0;
 	};
 
-
-
 /// @brief Convenience macro to include the current location in code
 ///
 /// Intended for use in error messages where it can sometimes be convenient to
 /// indicate the exact position the error occurred at.
 #define AG_WHERE " (at " __FILE__ ":" #__LINE__ ")"
 
-
-
 /// @brief Convenience macro for declaring exceptions with no support for inner exception
 /// @param classname   Name of the exception class to declare
 /// @param baseclass   Class to derive from
@@ -231,7 +217,6 @@ namespace agi {
 		classname(const std::string &msg, Exception *inner) : baseclass(msg, inner) { } \
 	};
 
-
 	/// @class agi::UserCancelException
 	/// @extends agi::Exception
 	/// @brief Exception for "user cancel" events
@@ -246,7 +231,6 @@ namespace agi {
 	/// code in question.
 	DEFINE_SIMPLE_EXCEPTION_NOINNER(UserCancelException,Exception,"nonerror/user_cancel")
 
-
 	/// @class agi::InternalError
 	/// @extends agi:Exception
 	/// @brief Errors that should never happen and point to some invalid assumption in the code
@@ -258,42 +242,17 @@ namespace agi {
 	/// and eventually cause an abort().
 	DEFINE_SIMPLE_EXCEPTION(InternalError, Exception, "internal_error")
 
-
-	/// @class agi::FileSystemError
-	/// @extends agi::Exception
-	/// @brief Base class for errors related to the file system
+	/// @class agi::EnvironmentError
+	/// @extends agi:Exception
+	/// @brief The execution environment is broken in some fundamental way
 	///
-	/// This base class can not be instantiated.
-	/// File system errors do not support inner exceptions, as they are always originating
-	/// causes for errors.
-	DEFINE_BASE_EXCEPTION_NOINNER(FileSystemError,Exception)
-
-	/// @class agi::FileNotAccessibleError
-	/// @extends agi::FileSystemError
-	/// @brief A file can't be accessed for some reason
-	DEFINE_SIMPLE_EXCEPTION_NOINNER(FileNotAccessibleError,FileSystemError,"filesystem/not_accessible")
-
-
-	/// @class FileNotFoundError
-	/// @brief A file can't be accessed because there's no file by the given name
-	class FileNotFoundError : public FileNotAccessibleError {
-	public:
-
-		/// @brief Constructor, automatically builds the error message
-		/// @param filename Name of the file that could not be found
-		FileNotFoundError(const std::string &filename) : FileNotAccessibleError(std::string("File not found: ") + filename) { }
-
-		// Not documented, see  agi::Exception class
-		const char * GetName() const { return "filesystem/not_accessible/not_found"; }
-
-		// Not documented, see  agi::Exception class
-		Exception * Copy() const { return new FileNotFoundError(*this); }
-	};
-
+	/// Throw an environment error when a call to the platform API has failed
+	/// in some way that should normally never happen or suggests that the
+	/// runtime environment is too insane to support.
+	DEFINE_SIMPLE_EXCEPTION_NOINNER(EnvironmentError, Exception, "environment_error")
 
 	/// @class agi::InvalidInputException
 	/// @extends agi::Exception
 	/// @brief Some input data were invalid and could not be processed
-	DEFINE_BASE_EXCEPTION(InvalidInputException,Exception)
-
+	DEFINE_BASE_EXCEPTION(InvalidInputException, Exception)
 }
diff --git a/aegisub/libaegisub/include/libaegisub/fs.h b/aegisub/libaegisub/include/libaegisub/fs.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1516e971ac5a116589f874c920b7de38924d7d1
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/fs.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
+#include <boost/filesystem/path.hpp>
+#include <cstdint>
+#include <ctime>
+#include <iterator>
+#include <memory>
+#include <string>
+
+#undef CreateDirectory
+
+namespace agi {
+	namespace fs {
+		/// Define a filesystem error which takes a path or a string
+#define DEFINE_FS_EXCEPTION(type, base, message) \
+		struct type : public base { \
+			type(path const& p) : base(message + p.string()) { } \
+			type(std::string const& s) : base(s) { } \
+			const char *GetName() const { return ""; } \
+			Exception *Copy() const { return new type(*this); } \
+		}
+
+		/// @class agi::FileSystemError
+		/// @extends agi::Exception
+		/// @brief Base class for errors related to the file system
+		///
+		/// This base class can not be instantiated.
+		/// File system errors do not support inner exceptions, as they
+		/// are always originating causes for errors.
+		DEFINE_BASE_EXCEPTION_NOINNER(FileSystemError, Exception)
+
+		/// A file can't be accessed for some reason
+		DEFINE_FS_EXCEPTION(FileNotAccessible, FileSystemError, "File is not accessible: ");
+
+		/// A file can't be accessed because there's no file by the given name
+		DEFINE_FS_EXCEPTION(FileNotFound, FileNotAccessible, "File not found: ");
+
+		/// An error of some unknown type has occured
+		DEFINE_SIMPLE_EXCEPTION_NOINNER(FileSystemUnknownError, FileSystemError, "filesystem/unknown");
+
+		/// The path exists, but isn't a file
+		DEFINE_FS_EXCEPTION(NotAFile, FileNotAccessible, "Path is not a file (and should be): ");
+
+		/// The path exists, but isn't a directory
+		DEFINE_FS_EXCEPTION(NotADirectory, FileNotAccessible, "Path is not a directory (and should be): ");
+
+		/// The given path is too long for the filesystem
+		DEFINE_FS_EXCEPTION(PathTooLog, FileSystemError, "Path is too long: ");
+
+		/// Insufficient free space to complete operation
+		DEFINE_FS_EXCEPTION(DriveFull, FileSystemError, "Insufficient free space to write file: ");
+
+		/// Base class for access denied errors
+		DEFINE_FS_EXCEPTION(AccessDenied, FileNotAccessible, "Access denied to path: ");
+
+		/// Trying to read the file gave an access denied error
+		DEFINE_FS_EXCEPTION(ReadDenied, AccessDenied, "Access denied when trying to read: ");
+
+		/// Trying to write the file gave an access denied error
+		DEFINE_FS_EXCEPTION(WriteDenied, AccessDenied, "Access denied when trying to write: ");
+
+		/// File exists and cannot be overwritten due to being read-only
+		DEFINE_FS_EXCEPTION(ReadOnlyFile, WriteDenied, "File is read-only: ");
+
+		bool Exists(path const& p);
+		bool FileExists(path const& file);
+		bool DirectoryExists(path const& dir);
+
+		/// Get the local-charset encoded shortname for a file
+		///
+		/// This is purely for compatibility with external libraries which do
+		/// not support unicode filenames on Windows. On all other platforms,
+		/// it is a no-op.
+		std::string ShortName(path const& file_path);
+
+		/// Check for amount of free space on a path
+		uintmax_t FreeSpace(path const& dir_path);
+
+		/// Get the size in bytes of the file at path
+		///
+		/// @throws agi::FileNotFound if path does not exist
+		/// @throws agi::acs::NotAFile if path is a directory
+		/// @throws agi::acs::Read if path exists but could not be read
+		uintmax_t Size(path const& file_path);
+
+		/// Get the modification time of the file at path
+		///
+		/// @throws agi::FileNotFound if path does not exist
+		/// @throws agi::acs::NotAFile if path is a directory
+		/// @throws agi::acs::Read if path exists but could not be read
+		time_t ModifiedTime(path const& file_path);
+
+		/// Create a directory and all required intermediate directories
+		/// @throws agi::acs::Write if the directory could not be created.
+		///
+		/// Trying to create a directory which already exists is not an error.
+		bool CreateDirectory(path const& dir_path);
+
+		/// Touch the given path
+		///
+		/// Creates the file if it does not exist, or updates the modified
+		/// time if it does
+		void Touch(path const& file_path);
+
+		/// Rename a file or directory
+		/// @param from Source path
+		/// @param to   Destination path
+		void Rename(path const& from, path const& to);
+
+		/// Copy a file
+		/// @param from Source path
+		/// @param to   Destination path
+		///
+		/// The destination path will be created if it does not exist.
+		void Copy(path const& from, path const& to);
+
+		/// Delete a file
+		/// @param path Path to file to delete
+		/// @throws agi::FileNotAccessibleError if file exists but could not be deleted
+		bool Remove(path const& file);
+
+		/// Check if the file has the given extension
+		/// @param p Path to check
+		/// @param ext Case-insensitive extension, without leading dot
+		bool HasExtension(path const& p, std::string const& ext);
+
+		agi::fs::path Canonicalize(agi::fs::path const& path);
+
+		class DirectoryIterator {
+			struct PrivData;
+			std::shared_ptr<PrivData> privdata;
+			std::string value;
+		public:
+			typedef path value_type;
+			typedef path* pointer;
+			typedef path& reference;
+			typedef size_t difference_type;
+			typedef std::forward_iterator_tag iterator_category;
+
+			bool operator==(DirectoryIterator const&) const;
+			bool operator!=(DirectoryIterator const& rhs) const { return !(*this == rhs); }
+			DirectoryIterator& operator++();
+			std::string const& operator*() const { return value; }
+
+			DirectoryIterator(path const& p, std::string const& filter);
+			DirectoryIterator();
+			~DirectoryIterator();
+
+			template<typename T> void GetAll(T& cont);
+		};
+
+		inline DirectoryIterator& begin(DirectoryIterator &it) { return it; }
+		inline DirectoryIterator end(DirectoryIterator &) { return DirectoryIterator(); }
+
+		template<typename T>
+		inline void DirectoryIterator::GetAll(T& cont) {
+			copy(*this, end(*this), std::back_inserter(cont));
+		}
+	}
+}
diff --git a/aegisub/libaegisub/include/libaegisub/fs_fwd.h b/aegisub/libaegisub/include/libaegisub/fs_fwd.h
new file mode 100644
index 0000000000000000000000000000000000000000..c2d8869648b9462521e3f62e6830d8123cdd2d80
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/fs_fwd.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+namespace boost { namespace filesystem { class path; } }
+namespace agi { namespace fs { typedef boost::filesystem::path path; } }
diff --git a/aegisub/libaegisub/include/libaegisub/hotkey.h b/aegisub/libaegisub/include/libaegisub/hotkey.h
index 6fd0dcff9791a7097ca11e74d3449070c39833e7..eaaa73dead6aa634e5f799b3d4138f755613a468 100644
--- a/aegisub/libaegisub/include/libaegisub/hotkey.h
+++ b/aegisub/libaegisub/include/libaegisub/hotkey.h
@@ -16,11 +16,13 @@
 /// @brief Hotkey handler
 /// @ingroup hotkey menu event window
 
+#include <boost/filesystem/path.hpp>
 #include <map>
 #include <memory>
 #include <string>
 #include <vector>
 
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/signal.h>
 
 namespace json {
@@ -75,9 +77,9 @@ public:
 	/// Map to hold Combo instances
 	typedef std::multimap<std::string, Combo> HotkeyMap;
 private:
-	HotkeyMap str_map;             ///< String representation -> Combo
-	HotkeyMap cmd_map;             ///< Command name -> Combo
-	const std::string config_file; ///< Default user config location.
+	HotkeyMap str_map;               ///< String representation -> Combo
+	HotkeyMap cmd_map;               ///< Command name -> Combo
+	const agi::fs::path config_file; ///< Default user config location.
 
 	/// Build hotkey map.
 	/// @param context Context being parsed.
@@ -97,7 +99,7 @@ public:
 	/// Constructor
 	/// @param file           Location of user config file.
 	/// @param default_config Default config.
-	Hotkey(const std::string &file, const std::string &default_config);
+	Hotkey(agi::fs::path const& file, const std::string &default_config);
 
 	/// Scan for a matching key.
 	/// @param context  Context requested.
diff --git a/aegisub/libaegisub/include/libaegisub/io.h b/aegisub/libaegisub/include/libaegisub/io.h
index ca9c5e02c6c0b09696f02f8d786565c55c82e61c..b23d73a29bd9c2b98b6b01c59acd76bdf7883dbd 100644
--- a/aegisub/libaegisub/include/libaegisub/io.h
+++ b/aegisub/libaegisub/include/libaegisub/io.h
@@ -16,10 +16,11 @@
 /// @brief Public interface for IO methods.
 /// @ingroup libaegisub
 
-#include <string>
-#include <fstream>
-
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
+#include <boost/filesystem/path.hpp>
+#include <iosfwd>
 
 namespace agi {
 	namespace io {
@@ -27,15 +28,15 @@ namespace agi {
 DEFINE_BASE_EXCEPTION_NOINNER(IOError, Exception)
 DEFINE_SIMPLE_EXCEPTION_NOINNER(IOFatal, IOError, "io/fatal")
 
-std::ifstream* Open(const std::string &file, bool binary = false);
+std::ifstream* Open(fs::path const& file, bool binary = false);
 
 class Save {
 	std::ofstream *fp;
-	const std::string file_name;
-	const std::string tmp_name;
+	const fs::path file_name;
+	const fs::path tmp_name;
 
 public:
-	Save(const std::string& file, bool binary = false);
+	Save(fs::path const& file, bool binary = false);
 	~Save();
 	std::ofstream& Get();
 };
diff --git a/aegisub/libaegisub/include/libaegisub/json.h b/aegisub/libaegisub/include/libaegisub/json.h
index 04adc8d2235d101dbcdffdba103fa0b7fe063af0..e132120fb037d7636477e674002edd47b905341e 100644
--- a/aegisub/libaegisub/include/libaegisub/json.h
+++ b/aegisub/libaegisub/include/libaegisub/json.h
@@ -18,6 +18,7 @@
 
 #include <libaegisub/cajun/reader.h>
 #include <libaegisub/cajun/elements.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace agi {
 	namespace json_util {
@@ -30,14 +31,13 @@ json::UnknownElement parse(std::istream *stream);
 /// Parse a JSON file.
 /// @param file Path JSON to file
 /// @return json::UnknownElement
-json::UnknownElement file(const std::string &file);
+json::UnknownElement file(agi::fs::path const& file);
 
 /// Parse a json stream, with default handler.
 /// @param file Path to JSON file.
 /// @param Default config file to load incase of nonexistent file
 /// @return json::UnknownElement
-json::UnknownElement file(const std::string &file, const std::string &default_config);
-
+json::UnknownElement file(agi::fs::path const& file, const std::string &default_config);
 
 	} // namespace json_util
 } // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/keyframe.h b/aegisub/libaegisub/include/libaegisub/keyframe.h
index 55523e4907f294ac776ffdb30d2b0603dc55ec0e..812b984691f0334d2787b488474955a9f7c3ea4f 100644
--- a/aegisub/libaegisub/include/libaegisub/keyframe.h
+++ b/aegisub/libaegisub/include/libaegisub/keyframe.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "exception.h"
+#include "fs_fwd.h"
 
 namespace agi {
 	namespace vfr { class Framerate; }
@@ -27,11 +28,12 @@ namespace agi {
 		/// @brief Load a keyframe file
 		/// @param filename File to load
 		/// @return List of frame numbers which are keyframes
-		std::vector<int> Load(std::string const& filename);
+		std::vector<int> Load(agi::fs::path const& filename);
+
 		/// @brief Save keyframes to a file
 		/// @param filename File to save to
 		/// @param keyframes List of keyframes to save
-		void Save(std::string const& filename, std::vector<int> const& keyframes);
+		void Save(agi::fs::path const& filename, std::vector<int> const& keyframes);
 
 		DEFINE_SIMPLE_EXCEPTION_NOINNER(Error, Exception, "keyframe/error")
 	}
diff --git a/aegisub/libaegisub/include/libaegisub/line_iterator.h b/aegisub/libaegisub/include/libaegisub/line_iterator.h
index f8c96aa5a613f5030c0e9da3014009ef64be05d5..f149df6a609a7519fc90ea49c516f7c2f1e4b092 100644
--- a/aegisub/libaegisub/include/libaegisub/line_iterator.h
+++ b/aegisub/libaegisub/include/libaegisub/line_iterator.h
@@ -146,6 +146,13 @@ public:
 	}
 };
 
+// Enable range-based for
+template<typename T>
+line_iterator<T>& begin(line_iterator<T>& it) { return it; }
+
+template<typename T>
+line_iterator<T> end(line_iterator<T>&) { return agi::line_iterator<T>(); }
+
 template<class OutputType>
 void line_iterator<OutputType>::getline(std::string &str) {
 	union {
diff --git a/aegisub/libaegisub/include/libaegisub/log.h b/aegisub/libaegisub/include/libaegisub/log.h
index ae5e2b60238e6fb5010f0baeb5a9880b2701670d..197f4539a7948179718a8750e25234f9908233a1 100644
--- a/aegisub/libaegisub/include/libaegisub/log.h
+++ b/aegisub/libaegisub/include/libaegisub/log.h
@@ -16,12 +16,17 @@
 /// @brief Logging
 /// @ingroup libaegisub
 
-#include <cstdint>
+#include <libaegisub/fs_fwd.h>
+#include <libaegisub/types.h>
 
+#include <boost/filesystem/path.hpp>
+#include <cstdint>
 #include <cstdio>
 #include <ctime>
 #include <deque>
 #include <memory>
+#include <vector>
+
 #ifdef __DEPRECATED // Dodge GCC warnings
 # undef __DEPRECATED
 # include <strstream>
@@ -29,8 +34,6 @@
 #else
 # include <strstream>
 #endif
-#include <vector>
-#include <libaegisub/types.h>
 
 // These macros below aren't a perm solution, it will depend on how annoying they are through
 // actual usage, and also depends on msvc support.
@@ -144,7 +147,7 @@ class JsonEmitter : public Emitter {
 	agi_timeval time_start;
 
 	/// Directory to write the log file in
-	std::string directory;
+	agi::fs::path directory;
 
 	/// Parent sink to get messages from
 	const agi::log::LogSink *log_sink;
@@ -152,7 +155,7 @@ public:
 	/// Constructor
 	/// @param directory Directory to write the log file in
 	/// @param log_sink Parent sink to get messages from
-	JsonEmitter(std::string const& directory, const agi::log::LogSink *log_sink);
+	JsonEmitter(agi::fs::path const& directory, const agi::log::LogSink *log_sink);
 	/// Destructor
 	~JsonEmitter();
 
diff --git a/aegisub/libaegisub/include/libaegisub/mru.h b/aegisub/libaegisub/include/libaegisub/mru.h
index 62a2f76e93e4bc35230d45dba874e2ec82f3a4db..bccb4701c71cb8f3e24fb3c834a30af062252a7e 100644
--- a/aegisub/libaegisub/include/libaegisub/mru.h
+++ b/aegisub/libaegisub/include/libaegisub/mru.h
@@ -16,12 +16,13 @@
 /// @brief Public interface for MRU (Most Recently Used) lists.
 /// @ingroup libaegisub
 
+#include <boost/filesystem/path.hpp>
 #include <deque>
-#include <fstream>
 #include <list>
 #include <map>
 
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace json {
 	class UnknownElement;
@@ -49,11 +50,11 @@ DEFINE_SIMPLE_EXCEPTION_NOINNER(MRUErrorIndexOutOfRange, MRUError, "mru/invalid"
 class MRUManager {
 public:
 	/// @brief Map for time->value pairs.
-	typedef std::list<std::string> MRUListMap;
+	typedef std::list<agi::fs::path> MRUListMap;
 
 	/// @brief Constructor
 	/// @param config File to load MRU values from
-	MRUManager(std::string const& config, std::string const& default_config, agi::Options *options = 0);
+	MRUManager(agi::fs::path const& config, std::string const& default_config, agi::Options *options = 0);
 
 	/// Destructor
 	~MRUManager();
@@ -62,13 +63,13 @@ public:
 	/// @param key List name
 	/// @param entry Entry to add
 	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
-	void Add(std::string const& key, std::string const& entry);
+	void Add(std::string const& key, agi::fs::path const& entry);
 
 	/// @brief Remove entry from the list.
 	/// @param key List name
 	/// @param entry Entry to add
 	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
-	void Remove(std::string const& key, std::string const& entry);
+	void Remove(std::string const& key, agi::fs::path const& entry);
 
 	/// @brief Return list
 	/// @param key List name
@@ -79,14 +80,14 @@ public:
 	/// @param key List name
 	/// @param entry 0-base position of entry
 	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
-	std::string const& GetEntry(std::string const& key, const size_t entry);
+	agi::fs::path const& GetEntry(std::string const& key, const size_t entry);
 
 	/// Write MRU lists to disk.
 	void Flush();
 
 private:
 	/// Internal name of the config file, set during object construction.
-	const std::string config_name;
+	const agi::fs::path config_name;
 
 	/// User preferences object for maximum number of items to list
 	agi::Options *const options;
diff --git a/aegisub/libaegisub/include/libaegisub/option.h b/aegisub/libaegisub/include/libaegisub/option.h
index f1c486f5d266eda91889f91ff3056e43bea42182..974ae9824330c2e3d1ad1eee274453ea75616756 100644
--- a/aegisub/libaegisub/include/libaegisub/option.h
+++ b/aegisub/libaegisub/include/libaegisub/option.h
@@ -18,10 +18,12 @@
 
 #pragma once
 
-#include <fstream>
+#include <boost/filesystem/path.hpp>
+#include <iosfwd>
 #include <map>
 
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace json {
 	class UnknownElement;
@@ -60,7 +62,7 @@ private:
 	OptionValueMap values;
 
 	/// User config (file that will be written to disk)
-	const std::string config_file;
+	const agi::fs::path config_file;
 
 	/// Settings.
 	const OptionSetting setting;
@@ -74,7 +76,7 @@ public:
 	/// @brief Constructor
 	/// @param file User config that will be loaded from and written back to.
 	/// @param default_config Default configuration.
-	Options(const std::string &file, const std::string &default_config, const OptionSetting setting=NONE);
+	Options(agi::fs::path const& file, const std::string &default_config, const OptionSetting setting=NONE);
 
 	/// Destructor
 	~Options();
diff --git a/aegisub/libaegisub/include/libaegisub/path.h b/aegisub/libaegisub/include/libaegisub/path.h
new file mode 100644
index 0000000000000000000000000000000000000000..cbb10f5297cb2258f65e63007055bea54bf2fce6
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/path.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file path.h
+/// @brief Common paths.
+/// @ingroup libaegisub
+
+#include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
+#include <boost/filesystem/path.hpp>
+#include <map>
+
+namespace agi {
+/// Class for handling everything path-related in Aegisub
+class Path {
+	/// Token -> Path map
+	std::map<std::string, fs::path> tokens;
+
+	/// Path -> Token map
+	std::map<fs::path, std::string> paths;
+
+	/// Platform-specific code to fill in the default paths, called in the constructor
+	void FillPlatformSpecificPaths();
+
+public:
+	/// Constructor
+	Path();
+
+	/// Decode and normalize a path which may begin with a registered token
+	/// @param path Path which is either already absolute or begins with a token
+	/// @return Absolute path
+	fs::path Decode(std::string const& path) const;
+
+	/// If path is relative, make it absolute relative to the token's path
+	/// @param path A possibly relative path
+	/// @param token Token containing base path for resolving relative paths
+	/// @return Absolute path if `path` is absolute or `token` is set, `path` otherwise
+	/// @throws InternalError if `token` is not a valid token name
+	fs::path MakeAbsolute(fs::path path, std::string const& token) const;
+
+	/// If `token` is set, make `path` relative to it
+	/// @param path An absolute path
+	/// @param token Token name to make `path` relative to
+	/// @return A path relative to `token`'s value if `token` is set, `path` otherwise
+	/// @throws InternalError if `token` is not a valid token name
+	fs::path MakeRelative(fs::path const& path, std::string const& token) const;
+	fs::path MakeRelative(fs::path const& path, const char *token) const { return MakeRelative(path, std::string(token)); }
+
+	/// Make `path` relative to `base`, if possible
+	/// @param path An absolute path
+	/// @param base Base path to make `path` relative to
+	/// @return A path relative to `base`'s value if possible, or `path` otherwise
+	fs::path MakeRelative(fs::path const& path, fs::path const& base) const;
+
+	/// Encode an absolute path to begin with a token if there are any applicable
+	/// @param path Absolute path to encode
+	/// @return path untouched, or with some portion of the beginning replaced with a token
+	std::string Encode(fs::path const& path) const;
+
+	/// Set a prefix token to use for encoding and decoding paths
+	/// @param token_name A single word token beginning with '?'
+	/// @param token_value An absolute path to a directory or file
+	/// @throws InternalError if `token` is not a valid token name
+	void SetToken(std::string const& token_name, fs::path const& token_value);
+};
+
+}
diff --git a/aegisub/libaegisub/include/libaegisub/split.h b/aegisub/libaegisub/include/libaegisub/split.h
new file mode 100644
index 0000000000000000000000000000000000000000..43cf0284cb9542c610b259d92eb8c6cc662e6453
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/split.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include <boost/algorithm/string/finder.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+namespace agi {
+	typedef boost::iterator_range<std::string::const_iterator> StringRange;
+
+	template<typename Str, typename Char>
+	boost::split_iterator<typename Str::const_iterator> Split(Str const& str, Char delim) {
+		return boost::make_split_iterator(str, boost::token_finder([=](Char c) { return c == delim; }));
+	}
+
+	inline std::string str(StringRange const& r) {
+		return std::string(r.begin(), r.end());
+	}
+}
+
+namespace boost {
+	namespace algorithm {
+		template<typename Iterator>
+		split_iterator<Iterator> begin(split_iterator<Iterator> it) {
+			return it;
+		}
+
+		template<typename Iterator>
+		split_iterator<Iterator> end(split_iterator<Iterator>) {
+			return split_iterator<Iterator>();
+		}
+	}
+}
diff --git a/aegisub/libaegisub/include/libaegisub/thesaurus.h b/aegisub/libaegisub/include/libaegisub/thesaurus.h
index 5b551513c094b7b24063bef551bdd070bff8e788..88b2c43606696d1139c3f551154fc4be45d3a7e0 100644
--- a/aegisub/libaegisub/include/libaegisub/thesaurus.h
+++ b/aegisub/libaegisub/include/libaegisub/thesaurus.h
@@ -16,10 +16,11 @@
 /// @brief MyThes-compatible thesaurus implementation
 /// @ingroup libaegisub thesaurus
 
-#include <libaegisub/scoped_ptr.h>
+#include "fs_fwd.h"
 
 #include <iosfwd>
 #include <map>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -31,9 +32,9 @@ class Thesaurus {
 	/// Map of word -> byte position in the data file
 	std::map<std::string, int> offsets;
 	/// Read handle to the data file
-	scoped_ptr<std::ifstream> dat;
+	std::unique_ptr<std::istream> dat;
 	/// Converter from the data file's charset to UTF-8
-	scoped_ptr<charset::IconvWrapper> conv;
+	std::unique_ptr<charset::IconvWrapper> conv;
 
 public:
 	/// A pair of a word and synonyms for that word
@@ -42,7 +43,7 @@ public:
 	/// Constructor
 	/// @param dat_path Path to data file
 	/// @param idx_path Path to index file
-	Thesaurus(std::string const& dat_path, std::string const& idx_path);
+	Thesaurus(agi::fs::path const& dat_path, agi::fs::path const& idx_path);
 	~Thesaurus();
 
 	/// Look up synonyms for a word
diff --git a/aegisub/libaegisub/include/libaegisub/util.h b/aegisub/libaegisub/include/libaegisub/util.h
index d59020d321e0ba0715e683b5f3f36ef89c056a1c..f622dfa95979da3ce01f26115ddd0ba56557d156 100644
--- a/aegisub/libaegisub/include/libaegisub/util.h
+++ b/aegisub/libaegisub/include/libaegisub/util.h
@@ -16,57 +16,31 @@
 /// @brief Public interface for general utilities.
 /// @ingroup libaegisub
 
+#include <algorithm>
 #include <cstdint>
-
 #include <string>
-#include <algorithm>
 
 #include <libaegisub/types.h>
 
+struct tm;
+
 namespace agi {
 	namespace util {
-	/// Whether the path is a file or directory.
-	enum PathType {
-		TypeFile,		///< File
-		TypeDir			///< Directory
-	};
-
 	/// Clamp `b` to the range [`a`,`c`]
 	template<typename T> inline T mid(T a, T b, T c) { return std::max(a, std::min(b, c)); }
 
-	/// Get the parent directory of a path.
-	/// @param path Path to process.
-	const std::string DirName(const std::string& path);
-
-	/// Rename a file.
-	/// @param from Source.
-	/// @param to   Destination.
-	void Rename(const std::string& from, const std::string& to);
-
-	/// Delete a file
-	/// @param path Path to file to delete
-	/// @throws agi::FileNotAccessibleError if file exists but could not be deleted
-	void Remove(std::string const& path);
-
 	/// Get time suitable for logging mechanisms.
 	/// @param tv timeval
 	void time_log(agi_timeval &tv);
 
-	/// Make all alphabetic characters lowercase.
-	/// @param str Input string
-	void str_lower(std::string &str);
-
-	/// Convert a string to Integer.
-	/// @param str Input string
-	int strtoi(std::string const& str);
-
 	bool try_parse(std::string const& str, double *out);
 	bool try_parse(std::string const& str, int *out);
 
-	/// Check for amount of free space on a Path.
-	/// @param path[in] Path to check
-	/// @param type     PathType (default is TypeDir)
-	uint64_t freespace(std::string const& path, PathType type=TypeDir);
+	/// strftime, but on std::string rather than a fixed buffer
+	/// @param fmt strftime format string
+	/// @param tmptr Time to format, or nullptr for current time
+	/// @return The strftime-formatted string
+	std::string strftime(const char *fmt, const tm *tmptr = nullptr);
 
 	struct delete_ptr {
 		template<class T>
diff --git a/aegisub/libaegisub/include/libaegisub/util_osx.h b/aegisub/libaegisub/include/libaegisub/util_osx.h
index 07f34efb0db91da502215f7de836c291b258328c..5ec61a027f63ea5e501203f447c8e9e9b409b5e9 100644
--- a/aegisub/libaegisub/include/libaegisub/util_osx.h
+++ b/aegisub/libaegisub/include/libaegisub/util_osx.h
@@ -28,6 +28,8 @@
 /// When linking with this library, be sure to add '-framework CoreFoundation'
 /// to the GCC commandline.
 
+#ifdef __APPLE__
+
 #include <string>
 
 namespace agi {
@@ -91,3 +93,5 @@ std::string OSX_GetBundleAuxillaryExecutablePath(std::string const& executableNa
 void OSX_OpenLocation(std::string const& location);
     } // namespace io
 } // namespace agi
+
+#endif
diff --git a/aegisub/libaegisub/include/libaegisub/util_win.h b/aegisub/libaegisub/include/libaegisub/util_win.h
index 72837d84f896253a62655447805ad466ed788af7..2a90ba780549bfe4bc0feac3f0cc0ee59a3df993 100644
--- a/aegisub/libaegisub/include/libaegisub/util_win.h
+++ b/aegisub/libaegisub/include/libaegisub/util_win.h
@@ -23,9 +23,6 @@
 
 namespace agi {
 	namespace util {
-
-	std::string ErrorString(DWORD error);
-
-
+		std::string ErrorString(DWORD error);
 	} // namespace util
 } // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/vfr.h b/aegisub/libaegisub/include/libaegisub/vfr.h
index dbe99bff7966e602c3aea3aca156494afc99a47e..4a9947c4ec4de8059e959df2a14667566a7e3018 100644
--- a/aegisub/libaegisub/include/libaegisub/vfr.h
+++ b/aegisub/libaegisub/include/libaegisub/vfr.h
@@ -24,6 +24,7 @@
 #include <cstdint>
 
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace agi {
 	/// Framerate handling.
@@ -92,7 +93,7 @@ public:
 	/// not the same thing as CFR X. When timecodes are loaded from a file,
 	/// mkvmerge-style rounding is applied, while setting a constant frame rate
 	/// uses truncation.
-	Framerate(std::string const& filename);
+	Framerate(fs::path const& filename);
 
 	/// @brief CFR constructor
 	/// @param fps Frames per second or 0 for unloaded
@@ -195,7 +196,7 @@ public:
 	/// CFR, but saving CFR timecodes is a bit silly). Extra timecodes generated
 	/// to hit length with v2 timecodes will monotonically increase but may not
 	/// be otherwise sensible.
-	void Save(std::string const& file, int length = -1) const;
+	void Save(fs::path const& file, int length = -1) const;
 
 	/// Is this frame rate possibly variable?
 	bool IsVFR() const {return timecodes.size() > 1; }
diff --git a/aegisub/libaegisub/lagi_pre.h b/aegisub/libaegisub/lagi_pre.h
index 829dca4fce377d904cc5f52ac00c45c06429daae..16776817b3c1f21458720f08e1c64df64a786952 100644
--- a/aegisub/libaegisub/lagi_pre.h
+++ b/aegisub/libaegisub/lagi_pre.h
@@ -1,5 +1,7 @@
 #include "config.h"
 
+#define WIN32_LEAN_AND_MEAN
+
 // Common C
 #include <cassert>
 #include <cerrno>
diff --git a/aegisub/libaegisub/unix/access.cpp b/aegisub/libaegisub/unix/access.cpp
index f2529fcfe7b7b525d1ee498ba86e60e57aa9a306..8562e2cc5ae0f1e4788f0a0afbfced0f5282a617 100644
--- a/aegisub/libaegisub/unix/access.cpp
+++ b/aegisub/libaegisub/unix/access.cpp
@@ -20,59 +20,30 @@
 
 #include "libaegisub/access.h"
 
+#include "libaegisub/fs.h"
+
 #include <sys/stat.h>
 #include <errno.h>
-
-#include <iostream>
-#include <fstream>
-
 #include <unistd.h>
 
-#include "libaegisub/util.h"
+#include <boost/filesystem/path.hpp>
 
 namespace agi {
 	namespace acs {
 
-
-void CheckFileRead(const std::string &file) {
-	Check(file, acs::FileRead);
-}
-
-
-void CheckFileWrite(const std::string &file) {
-	Check(file, acs::FileWrite);
-}
-
-
-void CheckDirRead(const std::string &dir) {
-	Check(dir, acs::DirRead);
-}
-
-
-void CheckDirWrite(const std::string &dir) {
-	Check(dir, acs::DirWrite);
-}
-
-
-void Check(const std::string &file, acs::Type type) {
+void Check(agi::fs::path const& file, acs::Type type) {
 	struct stat file_stat;
-	int file_status;
 
-	file_status = stat(file.c_str(), &file_stat);
+	int file_status = stat(file.c_str(), &file_stat);
 
 	if (file_status != 0) {
 		switch (errno) {
 			case ENOENT:
-				throw FileNotFoundError("File or path not found.");
-			break;
-
+				throw fs::FileNotFound(file);
 			case EACCES:
-				throw Read("Access Denied to file, path or path component.");
-			break;
-
+				throw fs::ReadDenied(file);
 			case EIO:
-				throw Fatal("Fatal I/O error occurred.");
-			break;
+				throw fs::FileSystemUnknownError("Fatal I/O error in 'stat' on path: " + file.string());
 		}
 	}
 
@@ -80,24 +51,24 @@ void Check(const std::string &file, acs::Type type) {
 		case FileRead:
 		case FileWrite:
 			if ((file_stat.st_mode & S_IFREG) == 0)
-				throw NotAFile("Not a file.");
+				throw fs::NotAFile(file);
 		break;
 
 		case DirRead:
 		case DirWrite:
 			if ((file_stat.st_mode & S_IFDIR) == 0)
-				throw NotADirectory("Not a directory.");
+				throw fs::NotADirectory(file);
 		break;
 	}
 
 	file_status = access(file.c_str(), R_OK);
 	if (file_status != 0)
-		throw Read("File or directory is not readable.");
+		throw fs::ReadDenied(file);
 
 	if (type == DirWrite || type == FileWrite) {
 		file_status = access(file.c_str(), W_OK);
 		if (file_status != 0)
-			throw Write("File or directory is not writable.");
+			throw fs::WriteDenied(file);
 	}
 }
 
diff --git a/aegisub/libaegisub/unix/fs.cpp b/aegisub/libaegisub/unix/fs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..63c22450e850c08a4e858cc0cbfa03947c9d5895
--- /dev/null
+++ b/aegisub/libaegisub/unix/fs.cpp
@@ -0,0 +1,92 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "libaegisub/fs.h"
+
+#include <boost/filesystem.hpp>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <sys/time.h>
+
+namespace bfs = boost::filesystem;
+
+namespace agi { namespace fs {
+std::string ShortName(path const& p) {
+	return p.string();
+}
+
+void Touch(path const& file) {
+	CreateDirectory(file.parent_path());
+
+	int fd = open(file.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0644);
+	if (fd >= 0) {
+		futimes(fd, nullptr);
+		close(fd);
+	}
+}
+
+struct DirectoryIterator::PrivData {
+	boost::system::error_code ec;
+	bfs::directory_iterator it;
+	std::string filter;
+	PrivData(path const& p, std::string const& filter) : it(p, ec), filter(filter) { }
+
+	bool bad() const {
+		return
+			it == bfs::directory_iterator() ||
+			(!filter.empty() && fnmatch(filter.c_str(), it->path().filename().c_str(), 0));
+	}
+};
+
+DirectoryIterator::DirectoryIterator() { }
+DirectoryIterator::DirectoryIterator(path const& p, std::string const& filter)
+: privdata(new PrivData(p, filter))
+{
+	if (privdata->it == bfs::directory_iterator())
+		privdata.reset();
+	else if (privdata->bad())
+		++*this;
+	else
+		value = privdata->it->path().filename().string();
+}
+
+bool DirectoryIterator::operator==(DirectoryIterator const& rhs) const {
+	return privdata.get() == rhs.privdata.get();
+}
+
+DirectoryIterator& DirectoryIterator::operator++() {
+	if (!privdata) return *this;
+
+	++privdata->it;
+
+	while (privdata->bad()) {
+		if (privdata->it == bfs::directory_iterator()) {
+			privdata.reset();
+			return *this;
+		}
+		++privdata->it;
+	}
+
+	value = privdata->it->path().filename().string();
+
+	return *this;
+}
+
+DirectoryIterator::~DirectoryIterator() { }
+
+} }
diff --git a/aegisub/libaegisub/unix/log.cpp b/aegisub/libaegisub/unix/log.cpp
index 5d43807b0e78a1b4da573f893a66d17a7761c315..50d86821db927558644fcd9228baf975dffbcc23 100644
--- a/aegisub/libaegisub/unix/log.cpp
+++ b/aegisub/libaegisub/unix/log.cpp
@@ -44,8 +44,8 @@ void EmitSTDOUT::log(SinkMessage *sm) {
 		sm->file,
 		sm->func,
 		sm->line,
-		(int)sm->len,
-		sm->message);
+		(int)sm->message.size(),
+		sm->message.c_str());
 	if (!isatty(fileno(stdout)))
 		fflush(stdout);
 }
diff --git a/aegisub/libaegisub/unix/path.cpp b/aegisub/libaegisub/unix/path.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..994f5595c28bd667eb51f322ff365b54f8971169
--- /dev/null
+++ b/aegisub/libaegisub/unix/path.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include <libaegisub/path.h>
+
+#include <libaegisub/util_osx.h>
+
+#include <boost/filesystem.hpp>
+#include <pwd.h>
+
+namespace {
+std::string home_dir() {
+	const char *env = getenv("HOME");
+	if (env) return env;
+
+	if ((env = getenv("USER")) || (env = getenv("LOGNAME"))) {
+		if (passwd *user_info = getpwnam(env))
+			return user_info->pw_dir;
+	}
+
+	throw agi::EnvironmentError("Could not get home directory. Make sure HOME is set.");
+}
+
+std::string data_dir() {
+#ifndef __APPLE__
+	return P_DATA;
+#else
+	return agi::util::OSX_GetBundleSharedSupportDirectory();
+#endif
+}
+
+}
+
+namespace agi {
+
+void Path::FillPlatformSpecificPaths() {
+	agi::fs::path home = home_dir();
+	SetToken("?user", home/".aegisub");
+	SetToken("?local", home/".aegisub");
+	SetToken("?data", data_dir());
+	SetToken("?temp", boost::filesystem::temp_directory_path());
+	SetToken("?dictionary", "/usr/share/hunspell");
+	SetToken("?docs", P_DOC);
+}
+
+}
diff --git a/aegisub/libaegisub/unix/util.cpp b/aegisub/libaegisub/unix/util.cpp
index ea556ec6e1ffdc7d638c21c8c3504c7f89c9a98f..4511637b04dfc737ce6dcd1e815fb1c3a7af6292 100644
--- a/aegisub/libaegisub/unix/util.cpp
+++ b/aegisub/libaegisub/unix/util.cpp
@@ -20,72 +20,10 @@
 
 #include "libaegisub/util.h"
 
-#include "libaegisub/access.h"
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <sys/statvfs.h>
-#ifdef HAVE_SYS_TIME_H
-#include <sys/time.h>
-#else
-#include <time.h>
-#endif
-
-#include <string>
-#include <fstream>
-
-#include <string.h>
-
-namespace agi {
-	namespace util {
-
-
-const std::string DirName(const std::string& path) {
-    if (path.find('/') == std::string::npos) {
-		return ".";
-	}
-
-	return path.substr(0, path.rfind("/")+1);
-}
-
-void Rename(const std::string& from, const std::string& to) {
-	acs::CheckFileWrite(from);
-
-	try {
-		acs::CheckFileWrite(to);
-	} catch (FileNotFoundError const&) {
-		acs::CheckDirWrite(DirName(to));
-	}
-
-	rename(from.c_str(), to.c_str());
-}
-
-void Remove(std::string const& path) {
-	if (!remove(path.c_str()) && errno != ENOENT)
-		throw agi::FileNotAccessibleError("Can not remove file: " + path);
-}
+namespace agi { namespace util {
 
 void time_log(timeval &tv) {
 	gettimeofday(&tv, (struct timezone *)NULL);
 }
 
-uint64_t freespace(std::string const& path, PathType type) {
-	struct statvfs fs;
-	std::string check(path);
-
-	if (type == TypeFile)
-		check.assign(DirName(path));
-
-	acs::CheckDirRead(check);
-
-	if ((statvfs(check.c_str(), &fs)) == 0) {
-		return (uint64_t)fs.f_bsize * fs.f_bavail;
-	} else {
-		/// @todo We need a collective set of exceptions for ENOTDIR, EIO etc.
-		throw("Failed getting free space");
-	}
-}
-
-
-	} // namespace io
-} // namespace agi
+} }
diff --git a/aegisub/libaegisub/windows/access.cpp b/aegisub/libaegisub/windows/access.cpp
index 394eeece9d489b771c45024e486c763bc27dc3e1..62181d01bb6fe85b3cae459f126921171dcc1e0a 100644
--- a/aegisub/libaegisub/windows/access.cpp
+++ b/aegisub/libaegisub/windows/access.cpp
@@ -16,18 +16,19 @@
 /// @brief Windows access methods.
 /// @ingroup libaegisub windows
 
-#include <windows.h>
-
-#include <iostream>
-#include <fstream>
 
 #include <libaegisub/access.h>
 
-#include <libaegisub/charset_conv_win.h>
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
 #include <libaegisub/util.h>
 #include <libaegisub/util_win.h>
 
+#include <boost/filesystem.hpp>
+#include <boost/format.hpp>
+
+#include <windows.h>
+
 namespace {
 	bool check_permission(bool is_read, SECURITY_DESCRIPTOR *sd, HANDLE client_token) {
 		DWORD access_check = is_read ? FILE_READ_DATA : FILE_APPEND_DATA | FILE_WRITE_DATA;
@@ -48,43 +49,23 @@ namespace {
 namespace agi {
 	namespace acs {
 
-void CheckFileRead(const std::string &file) {
-	Check(file, acs::FileRead);
-}
-
-void CheckFileWrite(const std::string &file) {
-	Check(file, acs::FileWrite);
-}
-
-void CheckDirRead(const std::string &dir) {
-	Check(dir, acs::DirRead);
-}
-
-void CheckDirWrite(const std::string &dir) {
-	Check(dir, acs::DirWrite);
-}
-
 /*
 This function is still a proof of concept, it's probably rife with bugs, below
 is a short (and incomplete) todo
  * "Basic" checks (Read/Write/File/Dir) checks for FAT32 filesystems which
    requires detecting the filesystem being used.
 */
-void Check(const std::string &file, acs::Type type) {
-	std::wstring wfile = agi::charset::ConvertW(file);
-
-	DWORD file_attr = GetFileAttributes(wfile.c_str());
+void Check(fs::path const& file, acs::Type type) {
+	DWORD file_attr = GetFileAttributes(file.c_str());
 	if ((file_attr & INVALID_FILE_ATTRIBUTES) == INVALID_FILE_ATTRIBUTES) {
 		switch (GetLastError()) {
 			case ERROR_FILE_NOT_FOUND:
 			case ERROR_PATH_NOT_FOUND:
-				throw FileNotFoundError(file);
-
+				throw fs::FileNotFound(file);
 			case ERROR_ACCESS_DENIED:
-				throw Read("Access denied to file or path component");
-
+				throw fs::ReadDenied(file);
 			default:
-				throw Fatal("Fatal I/O error occurred.");
+				throw fs::FileSystemUnknownError(str(boost::format("Unexpected error when getting attributes for \"%s\": %s") % file % util::ErrorString(GetLastError())));
 		}
 	}
 
@@ -92,25 +73,25 @@ void Check(const std::string &file, acs::Type type) {
 		case FileRead:
 		case FileWrite:
 			if ((file_attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
-				throw NotAFile(file + " is not a file");
+				throw fs::NotAFile(file);
 			break;
 		case DirRead:
 		case DirWrite:
 			if ((file_attr & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
-				throw NotADirectory(file + " is not a directory");
+				throw fs::NotADirectory(file);
 			break;
 	}
 
 	SECURITY_INFORMATION info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
 	DWORD len = 0;
-	GetFileSecurity(wfile.c_str(), info, nullptr, 0, &len);
+	GetFileSecurity(file.c_str(), info, nullptr, 0, &len);
 	if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
 		LOG_W("acs/check") << "GetFileSecurity: fatal: " << util::ErrorString(GetLastError());
 
 	std::vector<uint8_t> sd_buff(len);
 	SECURITY_DESCRIPTOR *sd = (SECURITY_DESCRIPTOR *)&sd_buff[0];
 
-	if (!GetFileSecurity(wfile.c_str(), info, sd, len, &len))
+	if (!GetFileSecurity(file.c_str(), info, sd, len, &len))
 		LOG_W("acs/check") << "GetFileSecurity failed: " << util::ErrorString(GetLastError());
 
 	ImpersonateSelf(SecurityImpersonation);
@@ -119,9 +100,9 @@ void Check(const std::string &file, acs::Type type) {
 		LOG_W("acs/check") << "OpenThreadToken failed: " << util::ErrorString(GetLastError());
 
 	if (!check_permission(true, sd, client_token))
-		throw Read("File or directory is not readable");
+		throw fs::ReadDenied(file);
 	if ((type == DirWrite || type == FileWrite) && !check_permission(false, sd, client_token))
-		throw Write("File or directory is not writable");
+		throw fs::WriteDenied(file);
 }
 
 	} // namespace Access
diff --git a/aegisub/libaegisub/windows/charset_conv_win.cpp b/aegisub/libaegisub/windows/charset_conv_win.cpp
index ebfe9340dfff1c6ba76e397827eb34df213f1f4b..67385001a2501cde05c0d0a67c17f58e5e152289 100644
--- a/aegisub/libaegisub/windows/charset_conv_win.cpp
+++ b/aegisub/libaegisub/windows/charset_conv_win.cpp
@@ -18,6 +18,18 @@
 
 #include <libaegisub/charset_conv_win.h>
 
+namespace {
+std::string from_w(agi::charset::IconvWrapper &w32Conv, std::wstring const& source) {
+	std::string dest;
+	size_t srcLen = source.size() * sizeof(wchar_t);
+	const char* src = reinterpret_cast<const char *>(source.c_str());
+	size_t len = w32Conv.RequiredBufferSize(src, srcLen);
+	dest.resize(len);
+	w32Conv.Convert(src, srcLen, &dest[0], len);
+	return dest;
+}
+}
+
 namespace agi {
 	namespace charset {
 
@@ -33,14 +45,12 @@ std::wstring ConvertW(std::string const& source) {
 
 std::string ConvertW(std::wstring const& source) {
 	static IconvWrapper w32Conv("utf-16le", "utf-8", false);
+	return from_w(w32Conv, source);
+}
 
-	std::string dest;
-	size_t srcLen = source.size() * sizeof(wchar_t);
-	const char* src = reinterpret_cast<const char *>(source.c_str());
-	size_t len = w32Conv.RequiredBufferSize(src, srcLen);
-	dest.resize(len);
-	w32Conv.Convert(src, srcLen, &dest[0], len);
-	return dest;
+std::string ConvertLocal(std::wstring const& source) {
+	static IconvWrapper w32Conv("utf-16le", "char", false);
+	return from_w(w32Conv, source);
 }
 
 	}
diff --git a/aegisub/libaegisub/windows/fs.cpp b/aegisub/libaegisub/windows/fs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b075775e5be3a852c1023dcee5488e5f06e6c7d5
--- /dev/null
+++ b/aegisub/libaegisub/windows/fs.cpp
@@ -0,0 +1,99 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "libaegisub/fs.h"
+
+#include "libaegisub/access.h"
+#include "libaegisub/charset_conv_win.h"
+#include "libaegisub/exception.h"
+#include "libaegisub/scoped_ptr.h"
+#include "libaegisub/util_win.h"
+
+using agi::charset::ConvertW;
+using agi::charset::ConvertLocal;
+
+#include <boost/filesystem.hpp>
+namespace bfs = boost::filesystem;
+
+#undef CreateDirectory
+
+namespace agi { namespace fs {
+std::string ShortName(path const& p) {
+	std::wstring out(MAX_PATH + 1, 0);
+	DWORD len = GetShortPathName(p.c_str(), &out[0], out.size());
+	if (!len)
+		return p.string();
+	out.resize(len);
+	return ConvertLocal(out);
+}
+
+void Touch(path const& file) {
+	CreateDirectory(file.parent_path());
+
+	SYSTEMTIME st;
+	FILETIME ft;
+	GetSystemTime(&st);
+	if(!SystemTimeToFileTime(&st, &ft))
+		throw EnvironmentError("SystemTimeToFileTime failed with error: " + util::ErrorString(GetLastError()));
+
+	scoped_holder<HANDLE, BOOL (__stdcall *)(HANDLE)>
+		h(CreateFile(file.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr), CloseHandle);
+	// error handling etc.
+	if (!SetFileTime(h, nullptr, nullptr, &ft))
+		throw EnvironmentError("SetFileTime failed with error: " + util::ErrorString(GetLastError()));
+}
+
+struct DirectoryIterator::PrivData {
+	scoped_holder<HANDLE, BOOL (__stdcall *)(HANDLE)> h;
+	PrivData() : h(INVALID_HANDLE_VALUE, FindClose) { }
+};
+
+DirectoryIterator::DirectoryIterator() { }
+DirectoryIterator::DirectoryIterator(path const& p, std::string const& filter)
+: privdata(new PrivData)
+{
+	WIN32_FIND_DATA data;
+	privdata->h = FindFirstFileEx((p/(filter.empty() ? "*.*" : filter)).c_str(), FindExInfoBasic, &data, FindExSearchNameMatch, nullptr, 0);
+	if (privdata->h == INVALID_HANDLE_VALUE) {
+		privdata.reset();
+		return;
+	}
+
+	value = ConvertW(data.cFileName);
+	while (value[0] == '.' && (value[1] == 0 || value[1] == '.'))
+		++*this;
+}
+
+bool DirectoryIterator::operator==(DirectoryIterator const& rhs) const {
+	return privdata.get() == rhs.privdata.get();
+}
+
+DirectoryIterator& DirectoryIterator::operator++() {
+	WIN32_FIND_DATA data;
+	if (FindNextFile(privdata->h, &data))
+		value = ConvertW(data.cFileName);
+	else {
+		privdata.reset();
+		value.clear();
+	}
+	return *this;
+}
+
+DirectoryIterator::~DirectoryIterator() { }
+
+} }
diff --git a/aegisub/libaegisub/windows/path_win.cpp b/aegisub/libaegisub/windows/path_win.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f5f5e5a3bd91c126479c9cb94927647494354f72
--- /dev/null
+++ b/aegisub/libaegisub/windows/path_win.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+/// @file path.cpp
+/// @brief Windows-specific path code
+/// @ingroup libaegisub windows
+
+#include <libaegisub/path.h>
+
+#include <libaegisub/util_win.h>
+
+#include <boost/filesystem.hpp>
+
+namespace {
+#include <Shlobj.h>
+#include <Shellapi.h>
+
+agi::fs::path WinGetFolderPath(int folder) {
+	wchar_t path[MAX_PATH+1] = {0};
+	if (FAILED(SHGetFolderPathW(0, folder, 0, 0, path)))
+		throw agi::EnvironmentError("SHGetFolderPath failed. This should not happen.");
+	return path;
+}
+}
+
+namespace agi {
+
+void Path::FillPlatformSpecificPaths() {
+	tokens["?temp"] = boost::filesystem::temp_directory_path();
+
+	SetToken("?user", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub");
+	SetToken("?local", WinGetFolderPath(CSIDL_LOCAL_APPDATA)/"Aegisub");
+
+	/// @todo error checking
+	int argc;
+	LPWSTR *argv = CommandLineToArgvW(L"", &argc);
+	SetToken("?data", argv[0]);
+	LocalFree(argv);
+
+	SetToken("?dictionary", Decode("?data/dictionaries"));
+	//SetToken("?docs", Decode("?data/docs"));
+}
+
+}
diff --git a/aegisub/libaegisub/windows/util_win.cpp b/aegisub/libaegisub/windows/util_win.cpp
index 995089546e00bd6d4ea25271051f8f58a2999021..b416cb0899891aa20ba756f1580c46dab4f6153e 100644
--- a/aegisub/libaegisub/windows/util_win.cpp
+++ b/aegisub/libaegisub/windows/util_win.cpp
@@ -16,55 +16,18 @@
 /// @brief Windows utility methods.
 /// @ingroup libaegisub windows
 
-#include <stdarg.h>
-#include <stdio.h>
+#include "libaegisub/util_win.h"
 
 #include <string>
-#include <fstream>
-
-#include <windows.h>
 
-#include "libaegisub/access.h"
 #include "libaegisub/charset_conv_win.h"
 #include "libaegisub/types.h"
-#include "libaegisub/util.h"
-#include "libaegisub/util_win.h"
 
 namespace agi {
 	namespace util {
 
 using agi::charset::ConvertW;
 
-const std::string DirName(const std::string& path) {
-	std::string::size_type pos = path.rfind('/');
-
-	if (pos == std::string::npos) pos = path.rfind('\\');
-	if (pos == std::string::npos) return ".";
-
-	return path.substr(0, pos+1);
-}
-
-void Rename(const std::string& from, const std::string& to) {
-	acs::CheckFileWrite(from);
-
-	try {
-		acs::CheckFileWrite(to);
-	} catch (FileNotFoundError const&) {
-		acs::CheckDirWrite(DirName(to));
-	}
-
-	if (!MoveFileEx(ConvertW(from).c_str(), ConvertW(to).c_str(), MOVEFILE_REPLACE_EXISTING))
-		throw agi::FileNotAccessibleError("Can not overwrite file: " + ErrorString(GetLastError()));
-}
-
-void Remove(std::string const& path) {
-	if (!DeleteFile(ConvertW(path).c_str())) {
-		DWORD err = GetLastError();
-		if (err != ERROR_FILE_NOT_FOUND)
-			throw agi::FileNotAccessibleError("Can not remove file: " + ErrorString(err));
-	}
-}
-
 std::string ErrorString(DWORD error) {
 	LPWSTR lpstr = nullptr;
 
@@ -116,19 +79,5 @@ void time_log(agi_timeval &tv) {
 	tv.tv_usec = (long)(tmpres % 1000000UL);
 }
 
-uint64_t freespace(std::string const& path, PathType type) {
-	if (type == TypeFile)
-		return freespace(DirName(path));
-
-	ULARGE_INTEGER bytes_available;
-	if (GetDiskFreeSpaceEx(ConvertW(path).c_str(), &bytes_available, 0, 0))
-		return bytes_available.QuadPart;
-
-	acs::CheckDirRead(path);
-
-	/// @todo GetLastError -> Exception mapping
-	throw "Unknown error getting free space";
-}
-
 	} // namespace io
 } // namespace agi
diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile
index ad9f0d5ac2b9e8792502ef41e8f49d5ff0708850..dccf25d722ec36e9f7e1409e7c06836bcefb5c24 100644
--- a/aegisub/src/Makefile
+++ b/aegisub/src/Makefile
@@ -157,7 +157,6 @@ SRC += \
 	auto4_base.cpp \
 	avisynth_wrap.cpp \
 	base_grid.cpp \
-	charset_conv.cpp \
 	charset_detect.cpp \
 	colorspace.cpp \
 	colour_button.cpp \
diff --git a/aegisub/src/aegisublocale.cpp b/aegisub/src/aegisublocale.cpp
index f6957b2745d7c3398084e9899a0ce444007a17b9..4bcb1ccb879e4e271d05b9d6df58c703d9610ead 100644
--- a/aegisub/src/aegisublocale.cpp
+++ b/aegisub/src/aegisublocale.cpp
@@ -36,18 +36,16 @@
 
 #include "aegisublocale.h"
 
+#include "standard_paths.h"
+
 #include <algorithm>
+#include <boost/filesystem/path.hpp>
 #include <clocale>
 #include <functional>
 
-#include <wx/dir.h>
-#include <wx/filename.h>
 #include <wx/intl.h>
-#include <wx/stdpaths.h>
 #include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
 
-#include "standard_paths.h"
-
 #ifndef AEGISUB_CATALOG
 #define AEGISUB_CATALOG "aegisub"
 #endif
@@ -56,7 +54,7 @@ wxTranslations *AegisubLocale::GetTranslations() {
 	wxTranslations *translations = wxTranslations::Get();
 	if (!translations) {
 		wxTranslations::Set(translations = new wxTranslations);
-		wxFileTranslationsLoader::AddCatalogLookupPathPrefix(StandardPaths::DecodePath("?data/locale/"));
+		wxFileTranslationsLoader::AddCatalogLookupPathPrefix(StandardPaths::DecodePath("?data/locale/").wstring());
 	}
 	return translations;
 }
diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h
index 125b081bfc8a329a90733ad01cfbc94087ad1477..d50e486753950298a34400594336a79fb5c0563a 100644
--- a/aegisub/src/agi_pre.h
+++ b/aegisub/src/agi_pre.h
@@ -74,7 +74,6 @@
 #include <boost/range/adaptor/filtered.hpp>
 #include <boost/range/adaptor/indirected.hpp>
 #include <boost/range/adaptor/reversed.hpp>
-#include <boost/range/algorithm_ext.hpp>
 
 // wxWidgets headers
 #include <wx/wxprec.h> // Leave this first.
diff --git a/aegisub/src/ass_attachment.cpp b/aegisub/src/ass_attachment.cpp
index 1fb02a66362547fbfd266f6cbacf7a93e71cc919..7b40d597eae45d91dc3ff779373ecc0a25a7c26b 100644
--- a/aegisub/src/ass_attachment.cpp
+++ b/aegisub/src/ass_attachment.cpp
@@ -34,26 +34,33 @@
 
 #include "config.h"
 
-#include <wx/filename.h>
-
-#include <istream>
-
 #include "ass_attachment.h"
 
-#include "compat.h"
-
 #include <libaegisub/io.h>
-#include <libaegisub/scoped_ptr.h>
 
-AssAttachment::AssAttachment(wxString const& name, AssEntryGroup group)
+#include <boost/algorithm/string/predicate.hpp>
+#include <fstream>
+
+AssAttachment::AssAttachment(std::string const& name, AssEntryGroup group)
 : data(new std::vector<char>)
 , filename(name)
 , group(group)
 {
-	wxFileName fname(filename);
-	wxString ext = fname.GetExt().Lower();
-	if (ext == "ttf")
-		filename = fname.GetName() + "_0." + ext;
+}
+
+AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group)
+: data(new std::vector<char>)
+, filename(name.filename().string())
+, group(group)
+{
+	if (boost::iends_with(filename, ".ttf"))
+		filename = filename.substr(0, filename.size() - 4) + "_0" + filename.substr(filename.size() - 4);
+
+	std::unique_ptr<std::istream> file(agi::io::Open(name, true));
+	file->seekg(0, std::ios::end);
+	data->resize(file->tellg());
+	file->seekg(0, std::ios::beg);
+	file->read(&(*data)[0], data->size());
 }
 
 AssEntry *AssAttachment::Clone() const {
@@ -62,46 +69,27 @@ AssEntry *AssAttachment::Clone() const {
 	return clone;
 }
 
-const wxString AssAttachment::GetEntryData() const {
-	size_t pos = 0;
+const std::string AssAttachment::GetEntryData() const {
 	size_t size = data->size();
 	size_t written = 0;
-	unsigned char src[3];
-	unsigned char dst[4];
-
-	// Write header
-	wxString entryData = (group == ENTRY_FONT ? "fontname: " : "filename: ") + filename + "\r\n";
-
-	// Read three bytes
-	while (pos < size) {
-		// Number to read
-		size_t read = size - pos;
-		if (read > 3) read = 3;
-
-		// Read source
-		src[0] = (*data)[pos];
-		if (read >= 2) src[1] = (*data)[pos+1];
-		else src[1] = 0;
-		if (read == 3) src[2] = (*data)[pos+2];
-		else src[2] = 0;
-		pos += read;
-
-		// Codify
+
+	std::string entryData = (group == ENTRY_FONT ? "fontname: " : "filename: ") + filename + "\r\n";
+	entryData.reserve(size * 4 / 3 + size / 80 * 2 + entryData.size() + 2);
+
+	for (size_t pos = 0; pos < size; pos += 3) {
+		unsigned char src[3] = { '\0', '\0', '\0' };
+		memcpy(src, &(*data)[pos], std::min<size_t>(3u, size - pos));
+
+		unsigned char dst[4];
 		dst[0] = src[0] >> 2;
 		dst[1] = ((src[0] & 0x3) << 4) | ((src[1] & 0xF0) >> 4);
 		dst[2] = ((src[1] & 0xF) << 2) | ((src[2] & 0xC0) >> 6);
 		dst[3] = src[2] & 0x3F;
 
-		// Number to write
-		size_t toWrite = read+1;
-
-		// Convert to text
-		for (size_t i=0;i<toWrite;i++) {
-			entryData += dst[i]+33;
-			written++;
+		for (size_t i = 0; i < std::min<size_t>(size - pos + 1, 4u); ++i) {
+			entryData += dst[i] + 33;
 
-			// Line break
-			if (written == 80 && pos < size) {
+			if (++written == 80 && pos + 3 < size) {
 				written = 0;
 				entryData += "\r\n";
 			}
@@ -111,39 +99,28 @@ const wxString AssAttachment::GetEntryData() const {
 	return entryData;
 }
 
-void AssAttachment::Extract(wxString const& filename) const {
-	agi::io::Save(from_wx(filename), true).Get().write(&(*data)[0], data->size());
-}
-
-void AssAttachment::Import(wxString const& filename) {
-	agi::scoped_ptr<std::istream> file(agi::io::Open(from_wx(filename), true));
-	file->seekg(0, std::ios::end);
-	data->resize(file->tellg());
-	file->seekg(0, std::ios::beg);
-	file->read(&(*data)[0], data->size());
+void AssAttachment::Extract(agi::fs::path const& filename) const {
+	agi::io::Save(filename, true).Get().write(&(*data)[0], data->size());
 }
 
-wxString AssAttachment::GetFileName(bool raw) const {
-	if (raw || filename.Right(4).Lower() != ".ttf") return filename;
+std::string AssAttachment::GetFileName(bool raw) const {
+	if (raw || !boost::iends_with(filename, ".ttf")) return filename;
 
 	// Remove stuff after last underscore if it's a font
-	wxString::size_type last_under = filename.rfind('_');
-	if (last_under == wxString::npos)
+	std::string::size_type last_under = filename.rfind('_');
+	if (last_under == std::string::npos)
 		return filename;
 
-	return filename.Left(last_under) + ".ttf";
+	return filename.substr(0, last_under) + ".ttf";
 }
 
 void AssAttachment::Finish() {
-	// Source and dest buffers
 	unsigned char src[4];
 	unsigned char dst[3];
 
 	data->reserve(buffer.size() * 3 / 4);
 
-	// Read buffer
 	for(size_t pos = 0; pos + 1 < buffer.size(); ) {
-		// Find characters left
 		size_t read = std::min<size_t>(buffer.size() - pos, 4);
 
 		// Move 4 bytes from buffer to src
@@ -157,11 +134,9 @@ void AssAttachment::Finish() {
 		dst[1] = ((src[1] & 0xF) << 4) | (src[2] >> 2);
 		dst[2] = ((src[2] & 0x3) << 6) | (src[3]);
 
-		// Push into vector
 		copy(dst, dst + read - 1, back_inserter(*data));
 	}
 
-	// Clear buffer
 	buffer.clear();
-	buffer.Shrink();
+	buffer.shrink_to_fit();
 }
diff --git a/aegisub/src/ass_attachment.h b/aegisub/src/ass_attachment.h
index d9702c3d7a68db57f921992638cd2b6fc0d3be83..06ac842accd093a75585ee8765dc93358d21c367 100644
--- a/aegisub/src/ass_attachment.h
+++ b/aegisub/src/ass_attachment.h
@@ -32,21 +32,23 @@
 /// @ingroup subs_storage
 ///
 
+#include "ass_entry.h"
+
+#include <libaegisub/fs_fwd.h>
+
 #include <memory>
 #include <vector>
 
-#include "ass_entry.h"
-
 /// @class AssAttachment
 class AssAttachment : public AssEntry {
 	/// Decoded file data
 	std::shared_ptr<std::vector<char>> data;
 
 	/// Encoded data which has been read from the script but not yet decoded
-	wxString buffer;
+	std::vector<char> buffer;
 
 	/// Name of the attached file, with SSA font mangling if it is a ttf
-	wxString filename;
+	std::string filename;
 
 	AssEntryGroup group;
 
@@ -56,25 +58,22 @@ public:
 
 	/// Add a line of data (without newline) read from a subtitle file to the
 	/// buffer waiting to be decoded
-	void AddData(wxString const& data) { buffer += data; }
+	void AddData(std::string const& data) { buffer.insert(buffer.end(), data.begin(), data.end()); }
 	/// Decode all data passed with AddData
 	void Finish();
 
 	/// Extract the contents of this attachment to a file
 	/// @param filename Path to save the attachment to
-	void Extract(wxString const& filename) const;
-
-	/// Import the contents of a file as an attachment
-	/// @param filename Path to import
-	void Import(wxString const& filename);
+	void Extract(agi::fs::path const& filename) const;
 
 	/// Get the name of the attached file
 	/// @param raw If false, remove the SSA filename mangling
-	wxString GetFileName(bool raw=false) const;
+	std::string GetFileName(bool raw=false) const;
 
-	const wxString GetEntryData() const override;
+	const std::string GetEntryData() const override;
 	AssEntryGroup Group() const override { return group; }
 	AssEntry *Clone() const override;
 
-	AssAttachment(wxString const& name, AssEntryGroup group);
+	AssAttachment(std::string const& name, AssEntryGroup group);
+	AssAttachment(agi::fs::path const& name, AssEntryGroup group);
 };
diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp
index ea6182596ebda93453c30b73d45b699a42b62d3a..418724962e82b542ffbc98b80c300d540b6b517b 100644
--- a/aegisub/src/ass_dialogue.cpp
+++ b/aegisub/src/ass_dialogue.cpp
@@ -34,27 +34,24 @@
 #include "config.h"
 
 #include "ass_dialogue.h"
-#include "compat.h"
 #include "subtitle_format.h"
 #include "utils.h"
 
 #include <libaegisub/of_type_adaptor.h>
+#include <libaegisub/split.h>
 
+#include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
 #include <boost/lexical_cast.hpp>
-#include <boost/tokenizer.hpp>
-
-#include <wx/regex.h>
-#include <wx/tokenzr.h>
+#include <boost/spirit/include/karma_generate.hpp>
+#include <boost/spirit/include/karma_int.hpp>
 
 using namespace boost::adaptors;
 
 static int next_id = 0;
 
-std::size_t hash_value(wxString const& s) {
-	return wxStringHash()(s);
-}
-
 AssDialogue::AssDialogue()
 : Id(++next_id)
 , Comment(false)
@@ -80,105 +77,91 @@ AssDialogue::AssDialogue(AssDialogue const& that)
 	memmove(Margin, that.Margin, sizeof Margin);
 }
 
-AssDialogue::AssDialogue(wxString const& data)
+AssDialogue::AssDialogue(std::string const& data)
 : Id(++next_id)
 {
-	if (!Parse(data))
-		throw SubtitleFormatParseError(from_wx("Failed parsing line: " + data), 0);
+	Parse(data);
 }
 
 AssDialogue::~AssDialogue () {
 }
 
-bool AssDialogue::Parse(wxString const& rawData) {
-	size_t pos = 0;
-	wxString temp;
+class tokenizer {
+	agi::StringRange str;
+	boost::split_iterator<agi::StringRange::const_iterator> pos;
+
+public:
+	tokenizer(agi::StringRange const& str) : str(str) , pos(agi::Split(str, ',')) { }
+
+	agi::StringRange next_tok() {
+		if (pos.eof())
+			throw SubtitleFormatParseError("Failed parsing line: " + std::string(str.begin(), str.end()), 0);
+		return *pos++;
+	}
+
+	std::string next_str() { return agi::str(next_tok()); }
+	std::string next_str_trim() { return agi::str(boost::trim_copy(next_tok())); }
+};
 
-	// Get type
-	if (rawData.StartsWith("Dialogue:")) {
+void AssDialogue::Parse(std::string const& raw) {
+	agi::StringRange str;
+	if (boost::starts_with(raw, "Dialogue:")) {
 		Comment = false;
-		pos = 10;
+		str = agi::StringRange(raw.begin() + 10, raw.end());
 	}
-	else if (rawData.StartsWith("Comment:")) {
+	else if (boost::starts_with(raw, "Comment:")) {
 		Comment = true;
-		pos = 9;
+		str = agi::StringRange(raw.begin() + 9, raw.end());
 	}
-	else return false;
+	else
+		throw SubtitleFormatParseError("Failed parsing line: " + raw, 0);
 
-	wxStringTokenizer tkn(rawData.Mid(pos),",",wxTOKEN_RET_EMPTY_ALL);
-	if (!tkn.HasMoreTokens()) return false;
+	tokenizer tkn(str);
 
 	// Get first token and see if it has "Marked=" in it
-	temp = tkn.GetNextToken().Trim(false).Trim(true);
-	bool ssa = temp.Lower().StartsWith("marked=");
+	auto tmp = tkn.next_str_trim();
+	bool ssa = boost::istarts_with(tmp, "marked=");
 
 	// Get layer number
 	if (ssa)
 		Layer = 0;
-	else {
-		long templ;
-		temp.ToLong(&templ);
-		Layer = templ;
-	}
-
-	// Get start time
-	if (!tkn.HasMoreTokens()) return false;
-	Start = tkn.GetNextToken();
-
-	// Get end time
-	if (!tkn.HasMoreTokens()) return false;
-	End = tkn.GetNextToken();
-
-	// Get style
-	if (!tkn.HasMoreTokens()) return false;
-	Style = tkn.GetNextToken().Trim(true).Trim(false);
-
-	// Get actor
-	if (!tkn.HasMoreTokens()) return false;
-	Actor = tkn.GetNextToken().Trim(true).Trim(false);
-
-	// Get margins
-	for (int i = 0; i < 3; ++i) {
-		if (!tkn.HasMoreTokens()) return false;
-		SetMarginString(tkn.GetNextToken().Trim(false).Trim(true), i);
-	}
-
-	if (!tkn.HasMoreTokens()) return false;
-	Effect = tkn.GetNextToken().Trim(true).Trim(false);
-
-	// Get text
-	Text = rawData.Mid(pos + tkn.GetPosition());
-
-	return true;
+	else
+		Layer = boost::lexical_cast<int>(tmp);
+
+	Start = tkn.next_str_trim();
+	End = tkn.next_str_trim();
+	Style = tkn.next_str_trim();
+	Actor = tkn.next_str_trim();
+	for (int& margin : Margin)
+		margin = mid(0, boost::lexical_cast<int>(tkn.next_str()), 9999);
+	Effect = tkn.next_str_trim();
+	Text = std::string(tkn.next_tok().begin(), str.end());
 }
 
-static void append_int(wxString &str, int v) {
-	str += std::to_wstring(v);
+void append_int(std::string &str, int v) {
+	boost::spirit::karma::generate(back_inserter(str), boost::spirit::karma::int_, v);
 	str += ',';
 }
 
-static void append_str(wxString &out, wxString const& str) {
+void append_str(std::string &out, std::string const& str) {
 	out += str;
 	out += ',';
 }
 
-static void append_unsafe_str(wxString &out, wxString const& str) {
+void append_unsafe_str(std::string &out, std::string const& str) {
 	if (str.find(',') == str.npos)
 		out += str;
-	else {
-		wxString c = str;
-		c.Replace(wxS(","), wxS(";"));
-		out += c;
-	}
+	else
+		out += boost::replace_all_copy(str, ",", ";");
 	out += ',';
 }
 
-wxString AssDialogue::GetData(bool ssa) const {
-	wxString str = Comment ? wxS("Comment: ") : wxS("Dialogue: ");
+std::string AssDialogue::GetData(bool ssa) const {
+	std::string str = Comment ? "Comment: " : "Dialogue: ";
 	str.reserve(51 + Style.get().size() + Actor.get().size() + Effect.get().size() + Text.get().size());
 
 	if (ssa)
-		append_str(str, wxS("Marked=0"));
+		append_str(str, "Marked=0");
 	else
 		append_int(str, Layer);
 	append_str(str, Start.GetAssFormated());
@@ -190,20 +173,19 @@ wxString AssDialogue::GetData(bool ssa) const {
 	append_unsafe_str(str, Effect);
 	str += Text.get();
 
-	// Make sure that final has no line breaks
 	if (str.find('\n') != str.npos || str.find('\r') != str.npos) {
-		str.Replace("\n", "");
-		str.Replace("\r", "");
+		boost::replace_all(str, "\n", "");
+		boost::replace_all(str, "\r", "");
 	}
 
 	return str;
 }
 
-const wxString AssDialogue::GetEntryData() const {
+const std::string AssDialogue::GetEntryData() const {
 	return GetData(false);
 }
 
-wxString AssDialogue::GetSSAText() const {
+std::string AssDialogue::GetSSAText() const {
 	return GetData(true);
 }
 
@@ -217,7 +199,7 @@ std::auto_ptr<boost::ptr_vector<AssDialogueBlock>> AssDialogue::ParseTags() cons
 	}
 
 	int drawingLevel = 0;
-	std::string text(from_wx(Text.get()));
+	std::string const& text(Text.get());
 
 	for (size_t len = text.size(), cur = 0; cur < len; ) {
 		// Overrides block
@@ -284,32 +266,17 @@ void AssDialogue::StripTags() {
 static std::string get_text(AssDialogueBlock &d) { return d.GetText(); }
 void AssDialogue::UpdateText(boost::ptr_vector<AssDialogueBlock>& blocks) {
 	if (blocks.empty()) return;
-	Text = to_wx(join(blocks | transformed(get_text), ""));
+	Text = join(blocks | transformed(get_text), "");
 }
 
-void AssDialogue::SetMarginString(wxString const& origvalue, int which) {
+void AssDialogue::SetMarginString(std::string const& origvalue, int which) {
 	if (which < 0 || which > 2) throw InvalidMarginIdError();
-
-	// Make it numeric
-	wxString strvalue = origvalue;
-	if (!strvalue.IsNumber()) {
-		strvalue.clear();
-		for (size_t i = 0; i < origvalue.Length(); ++i) {
-			if (origvalue.Mid(i, 1).IsNumber()) {
-				strvalue += origvalue.Mid(i, 1);
-			}
-		}
-	}
-
-	// Get value
-	long value = 0;
-	strvalue.ToLong(&value);
-	Margin[which] = mid<int>(0, value, 9999);
+	Margin[which] = mid<int>(0, atoi(origvalue.c_str()), 9999);
 }
 
-wxString AssDialogue::GetMarginString(int which) const {
+std::string AssDialogue::GetMarginString(int which) const {
 	if (which < 0 || which > 2) throw InvalidMarginIdError();
-	return wxString::Format("%d", Margin[which]);
+	return std::to_string(Margin[which]);
 }
 
 bool AssDialogue::CollidesWith(const AssDialogue *target) const {
@@ -318,10 +285,9 @@ bool AssDialogue::CollidesWith(const AssDialogue *target) const {
 }
 
 static std::string get_text_p(AssDialogueBlock *d) { return d->GetText(); }
-wxString AssDialogue::GetStrippedText() const {
-	wxString ret;
+std::string AssDialogue::GetStrippedText() const {
 	boost::ptr_vector<AssDialogueBlock> blocks(ParseTags());
-	return to_wx(join(blocks | agi::of_type<AssDialogueBlockPlain>() | transformed(get_text_p), ""));
+	return join(blocks | agi::of_type<AssDialogueBlockPlain>() | transformed(get_text_p), "");
 }
 
 AssEntry *AssDialogue::Clone() const {
@@ -336,10 +302,9 @@ void AssDialogueBlockDrawing::TransformCoords(int mx, int my, double x, double y
 	bool is_x = true;
 	std::string final;
 
-	boost::char_separator<char> sep(" ");
-	for (auto const& cur : boost::tokenizer<boost::char_separator<char>>(text, sep)) {
+	for (auto const& cur : agi::Split(text, ' ')) {
 		if (std::all_of(begin(cur), end(cur), isdigit)) {
-			int val = boost::lexical_cast<int>(cur);
+			int val = boost::lexical_cast<int>(agi::str(cur));
 			if (is_x)
 				val = (int)((val + mx) * x + .5);
 			else
diff --git a/aegisub/src/ass_dialogue.h b/aegisub/src/ass_dialogue.h
index 89bd17f38804c9dda14bb779d41d31c7e2d1a2ef..4ee3a16900f3e6153095dfd44ba514ea1155880e 100644
--- a/aegisub/src/ass_dialogue.h
+++ b/aegisub/src/ass_dialogue.h
@@ -50,8 +50,6 @@ enum AssBlockType {
 	BLOCK_DRAWING
 };
 
-std::size_t hash_value(wxString const& s);
-
 /// @class AssDialogueBlock
 /// @brief AssDialogue Blocks
 ///
@@ -126,7 +124,11 @@ public:
 };
 
 class AssDialogue : public AssEntry {
-	wxString GetData(bool ssa) const;
+	std::string GetData(bool ssa) const;
+
+	/// @brief Parse raw ASS data into everything else
+	/// @param data ASS line
+	void Parse(std::string const& data);
 public:
 	/// Unique ID of this line. Copies of the line for Undo/Redo purposes
 	/// preserve the unique ID, so that the equivalent lines can be found in
@@ -144,21 +146,16 @@ public:
 	/// Ending time
 	AssTime End;
 	/// Style name
-	boost::flyweight<wxString> Style;
+	boost::flyweight<std::string> Style;
 	/// Actor name
-	boost::flyweight<wxString> Actor;
+	boost::flyweight<std::string> Actor;
 	/// Effect name
-	boost::flyweight<wxString> Effect;
+	boost::flyweight<std::string> Effect;
 	/// Raw text data
-	boost::flyweight<wxString> Text;
+	boost::flyweight<std::string> Text;
 
 	AssEntryGroup Group() const override { return ENTRY_DIALOGUE; }
 
-	/// @brief Parse raw ASS data into everything else
-	/// @param data ASS line
-	/// @return Did it successfully parse?
-	bool Parse(wxString const& data);
-
 	/// Parse text as ASS and return block information
 	std::auto_ptr<boost::ptr_vector<AssDialogueBlock>> ParseTags() const;
 
@@ -166,23 +163,23 @@ public:
 	void StripTags();
 	/// Strip a specific ASS tag from the text
 	/// Get text without tags
-	wxString GetStrippedText() const;
+	std::string GetStrippedText() const;
 
 	/// Update the text of the line from parsed blocks
 	void UpdateText(boost::ptr_vector<AssDialogueBlock>& blocks);
-	const wxString GetEntryData() const override;
+	const std::string GetEntryData() const override;
 
 	template<int which>
-	void SetMarginString(wxString const& value) { SetMarginString(value, which);}
+	void SetMarginString(std::string const& value) { SetMarginString(value, which);}
 	/// @brief Set a margin
 	/// @param value New value of the margin
 	/// @param which 0 = left, 1 = right, 2 = vertical
-	void SetMarginString(wxString const& value, int which);
+	void SetMarginString(std::string const& value, int which);
 	/// @brief Get a margin
 	/// @param which 0 = left, 1 = right, 2 = vertical
-	wxString GetMarginString(int which) const;
+	std::string GetMarginString(int which) const;
 	/// Get the line as SSA rather than ASS
-	wxString GetSSAText() const override;
+	std::string GetSSAText() const override;
 	/// Does this line collide with the passed line?
 	bool CollidesWith(const AssDialogue *target) const;
 
@@ -190,7 +187,7 @@ public:
 
 	AssDialogue();
 	AssDialogue(AssDialogue const&);
-	AssDialogue(wxString const& data);
+	AssDialogue(std::string const& data);
 	~AssDialogue();
 };
 
diff --git a/aegisub/src/ass_entry.cpp b/aegisub/src/ass_entry.cpp
index fd3f6f33490754f30d80758307b00b5ef819e902..913f6e8bc1e96694efea3f798675aef215421753 100644
--- a/aegisub/src/ass_entry.cpp
+++ b/aegisub/src/ass_entry.cpp
@@ -23,8 +23,8 @@
 
 #include "ass_entry.h"
 
-wxString const& AssEntry::GroupHeader(bool ssa) const {
-	static wxString ass_headers[] = {
+std::string const& AssEntry::GroupHeader(bool ssa) const {
+	static std::string ass_headers[] = {
 		"[Script Info]",
 		"[V4+ Styles]",
 		"[Fonts]",
@@ -33,7 +33,7 @@ wxString const& AssEntry::GroupHeader(bool ssa) const {
 		""
 	};
 
-	static wxString ssa_headers[] = {
+	static std::string ssa_headers[] = {
 		"[Script Info]",
 		"[V4 Styles]",
 		"[Fonts]",
diff --git a/aegisub/src/ass_entry.h b/aegisub/src/ass_entry.h
index deff83e0a015cb7a92663441d2c142b79ac81b78..1871a6271619fd595b6e3f3744ffea77149887f1 100644
--- a/aegisub/src/ass_entry.h
+++ b/aegisub/src/ass_entry.h
@@ -34,9 +34,8 @@
 
 #pragma once
 
-#include <wx/string.h>
-
 #include <boost/intrusive/list_hook.hpp>
+#include <string>
 
 enum AssEntryGroup {
 	ENTRY_INFO = 0,
@@ -58,11 +57,11 @@ public:
 	virtual AssEntryGroup Group() const=0;
 
 	/// ASS or SSA Section header for this entry's group
-	wxString const& GroupHeader(bool ssa=false) const;
+	std::string const& GroupHeader(bool ssa=false) const;
 
 	/// @brief Get this line's raw entry data in ASS format
-	virtual const wxString GetEntryData() const=0;
+	virtual const std::string GetEntryData() const=0;
 
 	/// Get this line in SSA format
-	virtual wxString GetSSAText() const { return GetEntryData(); }
+	virtual std::string GetSSAText() const { return GetEntryData(); }
 };
diff --git a/aegisub/src/ass_export_filter.cpp b/aegisub/src/ass_export_filter.cpp
index 98716b01c543f8ffbd55b36485d13fc83a19f284..c0147a88515aa602a0c655623ef465167dd73ce0 100644
--- a/aegisub/src/ass_export_filter.cpp
+++ b/aegisub/src/ass_export_filter.cpp
@@ -34,12 +34,19 @@
 
 #include "config.h"
 
-#include <algorithm>
-
 #include "ass_export_filter.h"
+
 #include "utils.h"
 
-AssExportFilter::AssExportFilter(wxString const& name, wxString const& description, int priority)
+#include <algorithm>
+#include <boost/format.hpp>
+
+static FilterList& filters() {
+	static FilterList instance;
+	return instance;
+}
+
+AssExportFilter::AssExportFilter(std::string const& name, std::string const& description, int priority)
 : name(name)
 , priority(priority)
 , description(description)
@@ -47,52 +54,44 @@ AssExportFilter::AssExportFilter(wxString const& name, wxString const& descripti
 }
 
 void AssExportFilterChain::Register(AssExportFilter *filter) {
-	// Remove pipes from name
-	filter->name.Replace("|", "");
-
 	int filter_copy = 1;
-	wxString name = filter->name;
+	std::string name = filter->name;
 	// Find a unique name
 	while (GetFilter(name))
-		name = wxString::Format("%s (%d)", filter->name, filter_copy++);
+		name = str(boost::format("%s (%d)") % filter->name % filter_copy++);
 
 	filter->name = name;
 
 	// Look for place to insert
-	FilterList::iterator begin = filters()->begin();
-	FilterList::iterator end = filters()->end();
+	auto begin(filters().begin()), end(filters().end());
 	while (begin != end && (*begin)->priority >= filter->priority) ++begin;
-	filters()->insert(begin, filter);
+	filters().insert(begin, filter);
 }
 
 void AssExportFilterChain::Unregister(AssExportFilter *filter) {
-	auto it = remove(begin(*filters()), end(*filters()), filter);
-	if (it == end(*filters()))
+	auto it = remove(begin(filters()), end(filters()), filter);
+	if (it == end(filters()))
 		throw wxString::Format("Unregister export filter: name \"%s\" is not registered.", filter->name);
 
-	filters()->pop_back();
+	filters().pop_back();
 }
 
-FilterList *AssExportFilterChain::filters() {
-	static FilterList instance;
-	return &instance;
-}
 
 const FilterList *AssExportFilterChain::GetFilterList() {
-	return filters();
+	return &filters();
 }
 
 void AssExportFilterChain::Clear() {
-	while (filters()->size() > 0) {
-		AssExportFilter *f = filters()->back();
+	while (filters().size() > 0) {
+		AssExportFilter *f = filters().back();
 		delete f;
-		if (filters()->size() && filters()->back() == f)
-			filters()->pop_back();
+		if (filters().size() && filters().back() == f)
+			filters().pop_back();
 	}
 }
 
-AssExportFilter *AssExportFilterChain::GetFilter(wxString const& name) {
-	for (auto filter : *filters()) {
+AssExportFilter *AssExportFilterChain::GetFilter(std::string const& name) {
+	for (auto filter : filters()) {
 		if (filter->name == name)
 			return filter;
 	}
diff --git a/aegisub/src/ass_export_filter.h b/aegisub/src/ass_export_filter.h
index a36744462c44964455aaf4d0f5190d80e6e9f1df..0483adb98baed1a8275498d01531c6f641bf7c24 100644
--- a/aegisub/src/ass_export_filter.h
+++ b/aegisub/src/ass_export_filter.h
@@ -35,20 +35,18 @@
 #pragma once
 
 #include <memory>
+#include <string>
 #include <vector>
 
-#include <wx/string.h>
-#include <wx/window.h>
-
 class AssFile;
 class AssExportFilter;
+class wxWindow;
 
 namespace agi { struct Context; }
 
 typedef std::vector<AssExportFilter*> FilterList;
 
 class AssExportFilterChain {
-	static FilterList *filters();
 public:
 	/// Register an export filter
 	static void Register(AssExportFilter *filter);
@@ -57,7 +55,7 @@ public:
 	/// Unregister and delete all export filters
 	static void Clear();
 	/// Get a filter by name or nullptr if it doesn't exist
-	static AssExportFilter *GetFilter(wxString const& name);
+	static AssExportFilter *GetFilter(std::string const& name);
 
 	/// Get the list of registered filters
 	static const FilterList *GetFilterList();
@@ -69,20 +67,20 @@ class AssExportFilter {
 	friend class AssExportFilterChain;
 
 	/// This filter's name
-	wxString name;
+	std::string name;
 
 	/// Higher priority = run earlier
 	int priority;
 
 	/// User-visible description of this filter
-	wxString description;
+	std::string description;
 
 public:
-	AssExportFilter(wxString const& name, wxString const& description, int priority = 0);
+	AssExportFilter(std::string const& name, std::string const& description, int priority = 0);
 	virtual ~AssExportFilter() { };
 
-	wxString const& GetName() const { return name; }
-	wxString const& GetDescription() const { return description; }
+	std::string const& GetName() const { return name; }
+	std::string const& GetDescription() const { return description; }
 
 	/// Process subtitles
 	/// @param subs Subtitles to process
diff --git a/aegisub/src/ass_exporter.cpp b/aegisub/src/ass_exporter.cpp
index de2e277edb13d10dec619736bf7a41e71330abb9..894e4ab42567761aaee1ab7e1dd2f89570416b4e 100644
--- a/aegisub/src/ass_exporter.cpp
+++ b/aegisub/src/ass_exporter.cpp
@@ -34,14 +34,16 @@
 
 #include "config.h"
 
-#include "ass_export_filter.h"
 #include "ass_exporter.h"
+
+#include "ass_export_filter.h"
 #include "ass_file.h"
+#include "compat.h"
 #include "include/aegisub/context.h"
 
-#include <libaegisub/scoped_ptr.h>
-
 #include <algorithm>
+#include <memory>
+#include <wx/sizer.h>
 
 static inline FilterList::const_iterator filter_list_begin() {
 	return AssExportFilterChain::GetFilterList()->begin();
@@ -62,7 +64,7 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
 	for (auto filter : *AssExportFilterChain::GetFilterList()) {
 		// Make sure to construct static box sizer first, so it won't overlap
 		// the controls on wxMac.
-		wxSizer *box = new wxStaticBoxSizer(wxVERTICAL, parent, filter->GetName());
+		wxSizer *box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter->GetName()));
 		wxWindow *window = filter->GetConfigDialogWindow(parent, c);
 		if (window) {
 			box->Add(window, 0, wxEXPAND, 0);
@@ -76,18 +78,18 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
 	}
 }
 
-void AssExporter::AddFilter(wxString const& name) {
+void AssExporter::AddFilter(std::string const& name) {
 	AssExportFilter *filter = AssExportFilterChain::GetFilter(name);
 
-	if (!filter) throw wxString::Format("Filter not found: %s", name);
+	if (!filter) throw "Filter not found: " + name;
 
 	filters.push_back(filter);
 }
 
-wxArrayString AssExporter::GetAllFilterNames() const {
-	wxArrayString names;
+std::vector<std::string> AssExporter::GetAllFilterNames() const {
+	std::vector<std::string> names;
 	transform(filter_list_begin(), filter_list_end(),
-		std::back_inserter(names), std::mem_fun(&AssExportFilter::GetName));
+		back_inserter(names), std::mem_fun(&AssExportFilter::GetName));
 	return names;
 }
 
@@ -102,19 +104,19 @@ AssFile *AssExporter::ExportTransform(wxWindow *export_dialog, bool copy) {
 	return subs;
 }
 
-void AssExporter::Export(wxString const& filename, wxString const& charset, wxWindow *export_dialog) {
-	agi::scoped_ptr<AssFile> subs(ExportTransform(export_dialog, true));
+void AssExporter::Export(agi::fs::path const& filename, std::string const& charset, wxWindow *export_dialog) {
+	std::unique_ptr<AssFile> subs(ExportTransform(export_dialog, true));
 	subs->Save(filename, false, false, charset);
 }
 
-wxSizer *AssExporter::GetSettingsSizer(wxString const& name) {
+wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {
 	auto pos = Sizers.find(name);
 	return pos == Sizers.end() ? nullptr : pos->second;
 }
 
-wxString const& AssExporter::GetDescription(wxString const& name) const {
+std::string const& AssExporter::GetDescription(std::string const& name) const {
 	AssExportFilter *filter = AssExportFilterChain::GetFilter(name);
 	if (filter)
 		return filter->GetDescription();
-	throw wxString::Format("Filter not found: %s", name);
+	throw "Filter not found: " + name;
 }
diff --git a/aegisub/src/ass_exporter.h b/aegisub/src/ass_exporter.h
index 86764e5d2d95a4f2799fbd0e5062292a516e6ade..0b9671d148418002d4a1bcc0cd0e64ed32d593d5 100644
--- a/aegisub/src/ass_exporter.h
+++ b/aegisub/src/ass_exporter.h
@@ -32,16 +32,17 @@
 /// @ingroup export
 ///
 
-#include <wx/arrstr.h>
-#include <wx/sizer.h>
-#include <wx/string.h>
+#include <libaegisub/fs_fwd.h>
 
 #include <map>
+#include <string>
 #include <vector>
 
 class AssExportFilter;
 class AssFile;
 namespace agi { struct Context; }
+class wxSizer;
+class wxWindow;
 
 typedef std::vector<AssExportFilter*> FilterList;
 
@@ -49,7 +50,7 @@ class AssExporter {
 	typedef FilterList::const_iterator filter_iterator;
 
 	/// Sizers for configuration panels
-	std::map<wxString, wxSizer*> Sizers;
+	std::map<std::string, wxSizer*> Sizers;
 
 	/// Filters which will be applied to the subtitles
 	FilterList filters;
@@ -65,11 +66,11 @@ public:
 	AssExporter(agi::Context *c);
 
 	/// Get the names of all registered export filters
-	wxArrayString GetAllFilterNames() const;
+	std::vector<std::string> GetAllFilterNames() const;
 
 	/// Add the named filter to the list of filters to be run
-	/// @throws wxString if filter is not found
-	void AddFilter(wxString const& name);
+	/// @throws std::string if filter is not found
+	void AddFilter(std::string const& name);
 
 	/// Run all added export filters
 	/// @param parent_window Parent window the filters should use when opening dialogs
@@ -81,7 +82,7 @@ public:
 	/// @param file Target filename
 	/// @param charset Target charset
 	/// @param parent_window Parent window the filters should use when opening dialogs
-	void Export(wxString const& file, wxString const& charset, wxWindow *parent_window= 0);
+	void Export(agi::fs::path const& file, std::string const& charset, wxWindow *parent_window= 0);
 
 	/// Add configuration panels for all registered filters to the target sizer
 	/// @param parent Parent window for controls
@@ -89,9 +90,9 @@ public:
 	void DrawSettings(wxWindow *parent, wxSizer *target_sizer);
 
 	/// Get the sizer created by DrawSettings for a specific filter
-	wxSizer *GetSettingsSizer(wxString const& name);
+	wxSizer *GetSettingsSizer(std::string const& name);
 
 	/// Get the description of the named export filter
-	/// @throws wxString if filter is not found
-	wxString const& GetDescription(wxString const& name) const;
+	/// @throws std::string if filter is not found
+	std::string const& GetDescription(std::string const& name) const;
 };
diff --git a/aegisub/src/ass_file.cpp b/aegisub/src/ass_file.cpp
index 4bdc90ffefa6becd61ba1a3c06c7963fdc089573..75974a67b58a2426f1ea75ca7d35740ad65898d3 100644
--- a/aegisub/src/ass_file.cpp
+++ b/aegisub/src/ass_file.cpp
@@ -35,21 +35,10 @@
 
 #include "ass_file.h"
 
-#include <algorithm>
-#include <boost/algorithm/string/predicate.hpp>
-#include <fstream>
-#include <inttypes.h>
-#include <list>
-
-#include <wx/filename.h>
-#include <wx/log.h>
-#include <wx/msgdlg.h>
-
 #include "ass_attachment.h"
 #include "ass_dialogue.h"
 #include "ass_info.h"
 #include "ass_style.h"
-#include "compat.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "subtitle_format.h"
@@ -57,7 +46,17 @@
 #include "text_file_writer.h"
 #include "utils.h"
 
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
 #include <libaegisub/of_type_adaptor.h>
+#include <libaegisub/util.h>
+
+#include <algorithm>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+#include <boost/range/algorithm_ext/push_back.hpp>
+#include <list>
 
 namespace std {
 	template<>
@@ -73,16 +72,18 @@ AssFile::AssFile ()
 }
 
 AssFile::~AssFile() {
-	background_delete_clear(Line);
+	auto copy = new EntryList;
+	copy->swap(Line);
+	agi::dispatch::Background().Async([=]{ delete copy; });
 }
 
 /// @brief Load generic subs
-void AssFile::Load(const wxString &_filename, wxString const& charset) {
-	const SubtitleFormat *reader = SubtitleFormat::GetReader(_filename);
+void AssFile::Load(agi::fs::path const& filename, std::string const& charset) {
+	const SubtitleFormat *reader = SubtitleFormat::GetReader(filename);
 
 	try {
 		AssFile temp;
-		reader->ReadFile(&temp, _filename, charset);
+		reader->ReadFile(&temp, filename, charset);
 
 		bool found_style = false;
 		bool found_dialogue = false;
@@ -109,7 +110,7 @@ void AssFile::Load(const wxString &_filename, wxString const& charset) {
 
 	// Set general data
 	loaded = true;
-	filename = _filename;
+	this->filename = filename;
 
 	// Add comments and set vars
 	SetScriptInfo("ScriptType", "v4.00+");
@@ -124,7 +125,7 @@ void AssFile::Load(const wxString &_filename, wxString const& charset) {
 	FileOpen(filename);
 }
 
-void AssFile::Save(wxString filename, bool setfilename, bool addToRecent, wxString encoding) {
+void AssFile::Save(agi::fs::path const& filename, bool setfilename, bool addToRecent, std::string const& encoding) {
 	const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
 	if (!writer)
 		throw "Unknown file type.";
@@ -132,47 +133,38 @@ void AssFile::Save(wxString filename, bool setfilename, bool addToRecent, wxStri
 	if (setfilename) {
 		autosavedCommitId = savedCommitId = commitId;
 		this->filename = filename;
-		StandardPaths::SetPathValue("?script", wxFileName(filename).GetPath());
+		StandardPaths::SetPathValue("?script", filename.parent_path());
 	}
 
 	FileSave();
 
 	writer->WriteFile(this, filename, encoding);
 
-	if (addToRecent) {
+	if (addToRecent)
 		AddToRecent(filename);
-	}
 }
 
-wxString AssFile::AutoSave() {
+agi::fs::path AssFile::AutoSave() {
 	if (!loaded || commitId == autosavedCommitId)
 		return "";
 
-	wxFileName origfile(filename);
-	wxString path = to_wx(OPT_GET("Path/Auto/Save")->GetString());
-	if (!path)
-		path = origfile.GetPath();
-	path = StandardPaths::DecodePath(path + "/");
+	auto path = StandardPaths::DecodePath(OPT_GET("Path/Auto/Save")->GetString());
+	if (path.empty())
+		path = filename.parent_path();
 
-	wxFileName dstpath(path);
-	if (!dstpath.DirExists())
-		wxMkdir(path);
+	agi::fs::CreateDirectory(path);
 
-	wxString name = origfile.GetName();
-	if (!name)
+	auto name = filename.filename();
+	if (name.empty())
 		name = "Untitled";
-	dstpath.SetFullName(wxString::Format("%s.%s.AUTOSAVE.ass", name, wxDateTime::Now().Format("%Y-%m-%d-%H-%M-%S")));
 
-	Save(dstpath.GetFullPath(), false, false);
+	path /= str(boost::format("%s.%s.AUTOSAVE.ass") % name % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
 
-	autosavedCommitId = commitId;
+	Save(path, false, false);
 
-	return dstpath.GetFullPath();
-}
+	autosavedCommitId = commitId;
 
-static void write_line(wxString const& line, std::vector<char>& dst) {
-	wxCharBuffer buffer = (line + "\r\n").utf8_str();
-	copy(buffer.data(), buffer.data() + buffer.length(), back_inserter(dst));
+	return path;
 }
 
 void AssFile::SaveMemory(std::vector<char> &dst) {
@@ -190,9 +182,9 @@ void AssFile::SaveMemory(std::vector<char> &dst) {
 	for (auto const& line : Line) {
 		if (group != line.Group()) {
 			group = line.Group();
-			write_line(line.GroupHeader(), dst);
+			boost::push_back(dst, line.GroupHeader() + "\r\n");
 		}
-		write_line(line.GetEntryData(), dst);
+		boost::push_back(dst, line.GetEntryData() + "\r\n");
 	}
 }
 
@@ -205,28 +197,15 @@ bool AssFile::CanSave() const {
 	}
 }
 
-void AssFile::Clear() {
-	background_delete_clear(Line);
-
-	loaded = false;
-	filename.clear();
-	UndoStack.clear();
-	RedoStack.clear();
-	undoDescription.clear();
-}
-
 void AssFile::LoadDefault(bool defline) {
-	Clear();
-
-	// Write headers
 	Line.push_back(*new AssInfo("Title", "Default Aegisub file"));
 	Line.push_back(*new AssInfo("ScriptType", "v4.00+"));
 	Line.push_back(*new AssInfo("WrapStyle", "0"));
 	Line.push_back(*new AssInfo("ScaledBorderAndShadow", "yes"));
 	Line.push_back(*new AssInfo("Collisions", "Normal"));
 	if (!OPT_GET("Subtitle/Default Resolution/Auto")->GetBool()) {
-		Line.push_back(*new AssInfo("PlayResX", wxString::Format("%" PRId64, OPT_GET("Subtitle/Default Resolution/Width")->GetInt())));
-		Line.push_back(*new AssInfo("PlayResY", wxString::Format("%" PRId64, OPT_GET("Subtitle/Default Resolution/Height")->GetInt())));
+		Line.push_back(*new AssInfo("PlayResX", std::to_string(OPT_GET("Subtitle/Default Resolution/Width")->GetInt())));
+		Line.push_back(*new AssInfo("PlayResY", std::to_string(OPT_GET("Subtitle/Default Resolution/Height")->GetInt())));
 	}
 	Line.push_back(*new AssInfo("YCbCr Matrix", "None"));
 
@@ -283,40 +262,32 @@ void AssFile::InsertLine(AssEntry *entry) {
 	Line.push_front(*entry);
 }
 
-void AssFile::InsertAttachment(wxString filename) {
+void AssFile::InsertAttachment(agi::fs::path const& filename) {
 	AssEntryGroup group = ENTRY_GRAPHIC;
 
-	wxString ext = filename.Right(4).Lower();
+	auto ext = boost::to_lower_copy(filename.extension().string());
 	if (ext == ".ttf" || ext == ".ttc" || ext == ".pfb")
 		group = ENTRY_FONT;
 
-	std::unique_ptr<AssAttachment> newAttach(new AssAttachment(wxFileName(filename).GetFullName(), group));
-	newAttach->Import(filename);
-
-	InsertLine(newAttach.release());
+	InsertLine(new AssAttachment(filename, group));
 }
 
-wxString AssFile::GetScriptInfo(wxString key) const {
-	key.MakeLower();
-
+std::string AssFile::GetScriptInfo(std::string const& key) const {
 	for (const auto info : Line | agi::of_type<AssInfo>()) {
-		if (key == info->Key().Lower())
+		if (boost::iequals(key, info->Key()))
 			return info->Value();
 	}
 
 	return "";
 }
 
-int AssFile::GetScriptInfoAsInt(wxString const& key) const {
-	long temp = 0;
-	GetScriptInfo(key).ToLong(&temp);
-	return temp;
+int AssFile::GetScriptInfoAsInt(std::string const& key) const {
+	return atoi(GetScriptInfo(key).c_str());
 }
 
-void AssFile::SetScriptInfo(wxString const& key, wxString const& value) {
-	wxString lower_key = key.Lower();
+void AssFile::SetScriptInfo(std::string const& key, std::string const& value) {
 	for (auto info : Line | agi::of_type<AssInfo>()) {
-		if (lower_key == info->Key().Lower()) {
+		if (boost::iequals(key, info->Key())) {
 			if (value.empty())
 				delete info;
 			else
@@ -366,10 +337,9 @@ AssStyle *AssFile::GetStyle(std::string const& name) {
 	return nullptr;
 }
 
-void AssFile::AddToRecent(wxString const& file) const {
-	config::mru->Add("Subtitle", from_wx(file));
-	wxFileName filepath(file);
-	OPT_SET("Path/Last/Subtitles")->SetString(from_wx(filepath.GetPath()));
+void AssFile::AddToRecent(agi::fs::path const& file) const {
+	config::mru->Add("Subtitle", file);
+	OPT_SET("Path/Last/Subtitles")->SetString(file.parent_path().string());
 }
 
 int AssFile::Commit(wxString const& desc, int type, int amendId, AssEntry *single_line) {
diff --git a/aegisub/src/ass_file.h b/aegisub/src/ass_file.h
index c56528122dc7e638e36cd8b61ed4ee0ae0c74ce9..3fcba72d0463e06e3f9988ab9dca23b1e031e543 100644
--- a/aegisub/src/ass_file.h
+++ b/aegisub/src/ass_file.h
@@ -33,10 +33,13 @@
 ///
 
 #include <boost/container/list.hpp>
+#include <boost/filesystem/path.hpp>
 #include <boost/intrusive/list.hpp>
 #include <set>
 #include <vector>
+#include <wx/string.h>
 
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/signal.h>
 
 #include "ass_entry.h"
@@ -63,7 +66,7 @@ class AssFile {
 	/// A set of changes has been committed to the file (AssFile::CommitType)
 	agi::signal::Signal<int, std::set<const AssEntry*> const&> AnnounceCommit;
 	/// A new file has been opened (filename)
-	agi::signal::Signal<wxString> FileOpen;
+	agi::signal::Signal<agi::fs::path> FileOpen;
 	/// The file is about to be saved
 	/// This signal is intended for adding metadata such as video filename,
 	/// frame number, etc. Ideally this would all be done immediately rather
@@ -74,7 +77,7 @@ public:
 	/// The lines in the file
 	EntryList Line;
 	/// The filename of this file, if any
-	wxString filename;
+	agi::fs::path filename;
 	/// Is the file loaded?
 	bool loaded;
 
@@ -84,9 +87,7 @@ public:
 	~AssFile();
 
 	/// Does the file have unsaved changes?
-	bool IsModified() const {return commitId != savedCommitId; };
-	/// Clear the file
-	void Clear();
+	bool IsModified() const { return commitId != savedCommitId; };
 
 	/// @brief Load default file
 	/// @param defline Add a blank line to the file
@@ -94,7 +95,7 @@ public:
 	/// Add a line to the file at the end of the appropriate section
 	void InsertLine(AssEntry *line);
 	/// Attach a file to the ass file
-	void InsertAttachment(wxString filename);
+	void InsertAttachment(agi::fs::path const& filename);
 	/// Get the names of all of the styles available
 	std::vector<std::string> GetStyles() const;
 	/// @brief Get a style by name
@@ -107,24 +108,24 @@ public:
 	/// @brief Load from a file
 	/// @param file File name
 	/// @param charset Character set of file or empty to autodetect
-	void Load(const wxString &file, wxString const& charset="");
+	void Load(agi::fs::path const& file, std::string const& charset="");
 
 	/// @brief Save to a file
 	/// @param file Path to save to
 	/// @param setfilename Should the filename be changed to the passed path?
 	/// @param addToRecent Should the file be added to the MRU list?
 	/// @param encoding Encoding to use, or empty to let the writer decide (which usually means "App/Save Charset")
-	void Save(wxString file,bool setfilename=false,bool addToRecent=true,const wxString encoding="");
+	void Save(agi::fs::path const& file, bool setfilename=false, bool addToRecent=true, std::string const& encoding="");
 
 	/// @brief Autosave the file if there have been any chances since the last autosave
 	/// @return File name used or empty if no save was performed
-	wxString AutoSave();
+	agi::fs::path AutoSave();
 
 	/// @brief Save to a memory buffer. Used for subtitle providers which support it
 	/// @param[out] dst Destination vector
 	void SaveMemory(std::vector<char> &dst);
 	/// Add file name to the MRU list
-	void AddToRecent(wxString const& file) const;
+	void AddToRecent(agi::fs::path const& file) const;
 	/// Can the file be saved in its current format?
 	bool CanSave() const;
 
@@ -133,11 +134,11 @@ public:
 	/// @param[in] h Height
 	void GetResolution(int &w,int &h) const;
 	/// Get the value in a [Script Info] key as int, or 0 if it is not present
-	int GetScriptInfoAsInt(wxString const& key) const;
+	int GetScriptInfoAsInt(std::string const& key) const;
 	/// Get the value in a [Script Info] key as string.
-	wxString GetScriptInfo(wxString key) const;
+	std::string GetScriptInfo(std::string const& key) const;
 	/// Set the value of a [Script Info] key. Adds it if it doesn't exist.
-	void SetScriptInfo(wxString const& key, wxString const& value);
+	void SetScriptInfo(std::string const& key, std::string const& value);
 
 	/// Type of changes made in a commit
 	enum CommitType {
diff --git a/aegisub/src/ass_info.h b/aegisub/src/ass_info.h
index ff80d9f27dff4a78df34f320a928d34da1f8aeaa..7b49c314c522d647f52b475fff31b8b710113ed4 100644
--- a/aegisub/src/ass_info.h
+++ b/aegisub/src/ass_info.h
@@ -16,21 +16,22 @@
 
 #include "ass_entry.h"
 
+#include <boost/algorithm/string/predicate.hpp>
+
 class AssInfo : public AssEntry {
-	wxString key;
-	wxString value;
+	std::string key;
+	std::string value;
 
 public:
 	AssInfo(AssInfo const& o) : key(o.key), value(o.value) { }
-	AssInfo(wxString const& line) : key(line.BeforeFirst(':').Trim()), value(line.AfterFirst(':').Trim(false)) { }
-	AssInfo(wxString const& key, wxString const& value) : key(key), value(value) { }
+	AssInfo(std::string const& key, std::string const& value) : key(key), value(value) { }
 
 	AssEntry *Clone() const override { return new AssInfo(*this); }
 	AssEntryGroup Group() const override { return ENTRY_INFO; }
-	const wxString GetEntryData() const override { return key + ": " + value; }
-	wxString GetSSAText() const override { return key.Lower() == "scripttype: v4.00+" ? "ScriptType: v4.00" : GetEntryData(); }
+	const std::string GetEntryData() const override { return key + ": " + value; }
+	std::string GetSSAText() const override { return boost::iequals(key, "scripttype: v4.00+") ? "ScriptType: v4.00" : GetEntryData(); }
 
-	wxString Key() const { return key; }
-	wxString Value() const { return value; }
-	void SetValue(wxString const& new_value) { value = new_value; }
+	std::string Key() const { return key; }
+	std::string Value() const { return value; }
+	void SetValue(std::string const& new_value) { value = new_value; }
 };
diff --git a/aegisub/src/ass_karaoke.cpp b/aegisub/src/ass_karaoke.cpp
index 0f34fed15b216edb2224fa4c3513070ddec9ca3d..a199dce53ba152dbf1709bad2967ca718643c95b 100644
--- a/aegisub/src/ass_karaoke.cpp
+++ b/aegisub/src/ass_karaoke.cpp
@@ -25,7 +25,6 @@
 
 #include "ass_dialogue.h"
 #include "ass_file.h"
-#include "compat.h"
 #include "include/aegisub/context.h"
 #include "selection_controller.h"
 
@@ -161,12 +160,12 @@ void AssKaraoke::ParseSyllables(AssDialogue *line, Syllable &syl) {
 	syls.push_back(syl);
 }
 
-wxString AssKaraoke::GetText() const {
-	wxString text;
+std::string AssKaraoke::GetText() const {
+	std::string text;
 	text.reserve(size() * 10);
 
 	for (auto const& syl : syls)
-		text += to_wx(syl.GetText(true));
+		text += syl.GetText(true);
 
 	return text;
 }
@@ -301,7 +300,7 @@ void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c
 
 			new_line->Start = syl.start_time;
 			new_line->End = syl.start_time + syl.duration;
-			new_line->Text = to_wx(syl.GetText(false));
+			new_line->Text = syl.GetText(false);
 
 			c->ass->Line.insert(it, *new_line);
 
diff --git a/aegisub/src/ass_karaoke.h b/aegisub/src/ass_karaoke.h
index 2c200327f514e51c7be80db8ab54499c559919b7..95275053b431ae6fbbee1583673265f01b3ade52 100644
--- a/aegisub/src/ass_karaoke.h
+++ b/aegisub/src/ass_karaoke.h
@@ -25,8 +25,6 @@
 #include <string>
 #include <vector>
 
-#include <wx/string.h>
-
 #include <libaegisub/signal.h>
 
 namespace agi { struct Context; }
@@ -84,7 +82,7 @@ public:
 	size_t size() const { return syls.size(); }
 
 	/// Get the line's text with k tags
-	wxString GetText() const;
+	std::string GetText() const;
 
 	/// Get the karaoke tag type used, with leading slash
 	/// @returns "\k", "\kf", or "\ko"
diff --git a/aegisub/src/ass_parser.cpp b/aegisub/src/ass_parser.cpp
index 5bbc266f028a46c6089b4227f9aa22f466b739a9..84755b1019c0752af1bf56c988b0741b33b1d872 100644
--- a/aegisub/src/ass_parser.cpp
+++ b/aegisub/src/ass_parser.cpp
@@ -24,8 +24,9 @@
 #include "subtitle_format.h"
 
 #include <algorithm>
-
-#include <wx/log.h>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
 
 AssParser::AssParser(AssFile *target, int version)
 : target(target)
@@ -39,8 +40,8 @@ AssParser::AssParser(AssFile *target, int version)
 AssParser::~AssParser() {
 }
 
-void AssParser::ParseAttachmentLine(wxString const& data) {
-	bool is_filename = data.StartsWith("fontname: ") || data.StartsWith("filename: ");
+void AssParser::ParseAttachmentLine(std::string const& data) {
+	bool is_filename = boost::starts_with(data, "fontname: ") || boost::starts_with(data, "filename: ");
 
 	bool valid_data = data.size() > 0 && data.size() <= 80;
 	for (auto byte : data) {
@@ -60,59 +61,59 @@ void AssParser::ParseAttachmentLine(wxString const& data) {
 		attach->AddData(data);
 
 		// Done building
-		if (data.Length() < 80) {
+		if (data.size() < 80) {
 			attach->Finish();
 			InsertLine(attach.release());
 		}
 	}
 }
 
-void AssParser::ParseScriptInfoLine(wxString const& data) {
-	if (data.StartsWith(";")) {
+void AssParser::ParseScriptInfoLine(std::string const& data) {
+	if (boost::starts_with(data, ";")) {
 		// Skip stupid comments added by other programs
 		// Of course, we'll add our own in place later... ;)
 		return;
 	}
 
-	if (data.StartsWith("ScriptType:")) {
-		wxString versionString = data.Mid(11).Trim(true).Trim(false).Lower();
-		int trueVersion;
-		if (versionString == "v4.00")
-			trueVersion = 0;
-		else if (versionString == "v4.00+")
-			trueVersion = 1;
+	if (boost::starts_with(data, "ScriptType:")) {
+		std::string version_str = data.substr(11);
+		boost::trim(version_str);
+		boost::to_lower(version_str);
+		if (version_str == "v4.00")
+			version = 0;
+		else if (version_str == "v4.00+")
+			version = 1;
 		else
 			throw SubtitleFormatParseError("Unknown SSA file format version", 0);
-		if (trueVersion != version) {
-			wxLogMessage("Warning: File has the wrong extension.");
-			version = trueVersion;
-		}
 	}
 
-	InsertLine(new AssInfo(data));
+	size_t pos = data.find(':');
+	if (pos == data.npos) return;
+
+	InsertLine(new AssInfo(data.substr(0, pos), boost::trim_left_copy(data.substr(pos + 1))));
 }
 
-void AssParser::ParseEventLine(wxString const& data) {
-	if (data.StartsWith("Dialogue:") || data.StartsWith("Comment:"))
+void AssParser::ParseEventLine(std::string const& data) {
+	if (boost::starts_with(data, "Dialogue:") || boost::starts_with(data, "Comment:"))
 		InsertLine(new AssDialogue(data));
 }
 
-void AssParser::ParseStyleLine(wxString const& data) {
-	if (data.StartsWith("Style:"))
+void AssParser::ParseStyleLine(std::string const& data) {
+	if (boost::starts_with(data, "Style:"))
 		InsertLine(new AssStyle(data, version));
 }
 
-void AssParser::ParseFontLine(wxString const& data) {
-	if (data.StartsWith("fontname: "))
-		attach.reset(new AssAttachment(data.Mid(10), ENTRY_FONT));
+void AssParser::ParseFontLine(std::string const& data) {
+	if (boost::starts_with(data, "fontname: "))
+		attach.reset(new AssAttachment(data.substr(10), ENTRY_FONT));
 }
 
-void AssParser::ParseGraphicsLine(wxString const& data) {
-	if (data.StartsWith("filename: "))
-		attach.reset(new AssAttachment(data.Mid(10), ENTRY_GRAPHIC));
+void AssParser::ParseGraphicsLine(std::string const& data) {
+	if (boost::starts_with(data, "filename: "))
+		attach.reset(new AssAttachment(data.substr(10), ENTRY_GRAPHIC));
 }
 
-void AssParser::AddLine(wxString const& data) {
+void AssParser::AddLine(std::string const& data) {
 	// Special-case for attachments since a line could theoretically be both a
 	// valid attachment data line and a valid section header, and if an
 	// attachment is in progress it needs to be treated as that
@@ -124,10 +125,10 @@ void AssParser::AddLine(wxString const& data) {
 	if (data.empty()) return;
 
 	// Section header
-	if (data[0] == '[' && data.Last() == ']') {
+	if (data[0] == '[' && data.back() == ']') {
 		// Ugly hacks to allow intermixed v4 and v4+ style sections
-		const wxString low = data.Lower();
-		wxString header = data;
+		const std::string low = boost::to_lower_copy(data);
+		std::string header = data;
 		if (low == "[v4 styles]") {
 			header = "[V4+ Styles]";
 			version = 0;
diff --git a/aegisub/src/ass_parser.h b/aegisub/src/ass_parser.h
index eabb2dbd628e26088bd27846d35b3885a456fbd8..fb0a813ae17980f9c5ee085f7eb776192df87335 100644
--- a/aegisub/src/ass_parser.h
+++ b/aegisub/src/ass_parser.h
@@ -16,8 +16,6 @@
 #include <map>
 #include <memory>
 
-#include <wx/string.h>
-
 #include "ass_entry.h"
 
 class AssAttachment;
@@ -27,20 +25,21 @@ class AssParser {
 	AssFile *target;
 	int version;
 	std::unique_ptr<AssAttachment> attach;
-	void (AssParser::*state)(wxString const&);
+	void (AssParser::*state)(std::string const&);
 	std::array<AssEntry*, ENTRY_GROUP_MAX> insertion_positions;
 
 	void InsertLine(AssEntry *entry);
 
-	void ParseAttachmentLine(wxString const& data);
-	void ParseEventLine(wxString const& data);
-	void ParseStyleLine(wxString const& data);
-	void ParseScriptInfoLine(wxString const& data);
-	void ParseFontLine(wxString const& data);
-	void ParseGraphicsLine(wxString const& data);
-	void UnknownLine(wxString const&) { }
+	void ParseAttachmentLine(std::string const& data);
+	void ParseEventLine(std::string const& data);
+	void ParseStyleLine(std::string const& data);
+	void ParseScriptInfoLine(std::string const& data);
+	void ParseFontLine(std::string const& data);
+	void ParseGraphicsLine(std::string const& data);
+	void UnknownLine(std::string const&) { }
 public:
 	AssParser(AssFile *target, int version);
 	~AssParser();
-	void AddLine(wxString const& data);
+
+	void AddLine(std::string const& data);
 };
diff --git a/aegisub/src/ass_style.cpp b/aegisub/src/ass_style.cpp
index 085107c485a40b329f269411b115891922168248..843a8648762f7868220ec6b0be9775227dde7999 100644
--- a/aegisub/src/ass_style.cpp
+++ b/aegisub/src/ass_style.cpp
@@ -35,13 +35,15 @@
 #include "config.h"
 
 #include "ass_style.h"
-#include "compat.h"
+
 #include "subtitle_format.h"
 #include "utils.h"
 
+#include <libaegisub/split.h>
+
 #include <algorithm>
-#include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/trim.hpp>
+#include <boost/format.hpp>
 #include <boost/lexical_cast.hpp>
 #include <cctype>
 
@@ -74,42 +76,28 @@ AssStyle::AssStyle()
 
 namespace {
 class parser {
-	typedef boost::iterator_range<std::string::const_iterator> string_range;
-	string_range str;
-	std::vector<string_range> tkns;
-	size_t tkn_idx;
+	boost::split_iterator<agi::StringRange::const_iterator> pos;
 
-	string_range next_tok() {
-		if (tkn_idx >= tkns.size())
+	std::string next_tok() {
+		if (pos.eof())
 			throw SubtitleFormatParseError("Malformed style: not enough fields", 0);
-		return trim_copy(tkns[tkn_idx++]);
+		return agi::str(trim_copy(*pos++));
 	}
 
 public:
-	parser(std::string const& str)
-	: tkn_idx(0)
-	{
-		auto pos = find(str.begin(), str.end(), ':');
-		if (pos != str.end()) {
-			this->str = string_range(pos + 1, str.end());
-			split(tkns, this->str, [](char c) { return c == ','; });
-		}
+	parser(std::string const& str) {
+		auto colon = find(str.begin(), str.end(), ':');
+		if (colon != str.end())
+			pos = agi::Split(agi::StringRange(colon + 1, str.end()), ',');
 	}
 
 	void check_done() const {
-		if (tkn_idx != tkns.size())
+		if (!pos.eof())
 			throw SubtitleFormatParseError("Malformed style: too many fields", 0);
 	}
 
-	std::string next_str() {
-		auto tkn = next_tok();
-		return std::string(begin(tkn), end(tkn));
-	}
-
-	agi::Color next_color() {
-		auto tkn = next_tok();
-		return std::string(begin(tkn), end(tkn));
-	}
+	std::string next_str() { return next_tok(); }
+	agi::Color next_color() { return next_tok(); }
 
 	int next_int() {
 		try {
@@ -130,13 +118,13 @@ public:
 	}
 
 	void skip_token() {
-		++tkn_idx;
+		if (!pos.eof())
+			++pos;
 	}
 };
 }
 
-AssStyle::AssStyle(wxString const& rawData, int version) {
-	std::string str(from_wx(rawData));
+AssStyle::AssStyle(std::string const& str, int version) {
 	parser p(str);
 
 	name = p.next_str();
@@ -211,28 +199,28 @@ void AssStyle::UpdateData() {
 	replace(name.begin(), name.end(), ',', ';');
 	replace(font.begin(), font.end(), ',', ';');
 
-	data = wxString::Format("Style: %s,%s,%g,%s,%s,%s,%s,%d,%d,%d,%d,%g,%g,%g,%g,%d,%g,%g,%i,%i,%i,%i,%i",
-		to_wx(name), to_wx(font), fontsize,
-		primary.GetAssStyleFormatted(),
-		secondary.GetAssStyleFormatted(),
-		outline.GetAssStyleFormatted(),
-		shadow.GetAssStyleFormatted(),
-		(bold? -1 : 0), (italic ? -1 : 0),
-		(underline?-1:0),(strikeout?-1:0),
-		scalex,scaley,spacing,angle,
-		borderstyle,outline_w,shadow_w,alignment,
-		Margin[0],Margin[1],Margin[2],encoding);
+	data = str(boost::format("Style: %s,%s,%g,%s,%s,%s,%s,%d,%d,%d,%d,%g,%g,%g,%g,%d,%g,%g,%i,%i,%i,%i,%i")
+		% name % font % fontsize
+		% primary.GetAssStyleFormatted()
+		% secondary.GetAssStyleFormatted()
+		% outline.GetAssStyleFormatted()
+		% shadow.GetAssStyleFormatted()
+		% (bold? -1 : 0) % (italic ? -1 : 0)
+		% (underline ? -1 : 0) % (strikeout ? -1 : 0)
+		% scalex % scaley % spacing % angle
+		% borderstyle % outline_w % shadow_w % alignment
+		% Margin[0] % Margin[1] % Margin[2] % encoding);
 }
 
-wxString AssStyle::GetSSAText() const {
-	return wxString::Format("Style: %s,%s,%g,%s,%s,0,%s,%d,%d,%d,%g,%g,%d,%d,%d,%d,0,%i",
-		to_wx(name), to_wx(font), fontsize,
-		primary.GetSsaFormatted(),
-		secondary.GetSsaFormatted(),
-		shadow.GetSsaFormatted(),
-		(bold? -1 : 0), (italic ? -1 : 0),
-		borderstyle,outline_w,shadow_w,AssToSsa(alignment),
-		Margin[0],Margin[1],Margin[2],encoding);
+std::string AssStyle::GetSSAText() const {
+	return str(boost::format("Style: %s,%s,%g,%s,%s,0,%s,%d,%d,%d,%g,%g,%d,%d,%d,%d,0,%i")
+		% name % font % fontsize
+		% primary.GetSsaFormatted()
+		% secondary.GetSsaFormatted()
+		% shadow.GetSsaFormatted()
+		% (bold? -1 : 0) % (italic ? -1 : 0)
+		% borderstyle % outline_w % shadow_w % AssToSsa(alignment)
+		% Margin[0] % Margin[1] % Margin[2] % encoding);
 }
 
 AssEntry *AssStyle::Clone() const {
diff --git a/aegisub/src/ass_style.h b/aegisub/src/ass_style.h
index 2c3e9a8db10b6d4d3e9cb5ff1b7d57ccc540102c..bd2c3ded714e367d376ecfc6e1281e257e3bc15d 100644
--- a/aegisub/src/ass_style.h
+++ b/aegisub/src/ass_style.h
@@ -40,7 +40,7 @@
 #include <wx/arrstr.h>
 
 class AssStyle : public AssEntry {
-	wxString data;
+	std::string data;
 
 public:
 	std::string name;   ///< Name of the style; must be case-insensitively unique within a file despite being case-sensitive
@@ -75,10 +75,10 @@ public:
 	static void GetEncodings(wxArrayString &encodingStrings);
 
 	AssStyle();
-	AssStyle(wxString const& data, int version=1);
+	AssStyle(std::string const& data, int version=1);
 
-	const wxString GetEntryData() const override { return data; }
-	wxString GetSSAText() const override;
+	const std::string GetEntryData() const override { return data; }
+	std::string GetSSAText() const override;
 	AssEntryGroup Group() const override { return ENTRY_STYLE; }
 	AssEntry *Clone() const override;
 
diff --git a/aegisub/src/ass_style_storage.cpp b/aegisub/src/ass_style_storage.cpp
index ac0da6853c453f01c6672634374ea577b62c5803..f3c4f8ef9aa9f75c1e36a23205eb2c235cb1a8cb 100644
--- a/aegisub/src/ass_style_storage.cpp
+++ b/aegisub/src/ass_style_storage.cpp
@@ -42,8 +42,10 @@
 #include "text_file_writer.h"
 #include "utils.h"
 
+#include <libaegisub/fs.h>
+
 #include <boost/algorithm/string/predicate.hpp>
-#include <functional>
+#include <boost/filesystem.hpp>
 
 AssStyleStorage::~AssStyleStorage() {
 	delete_clear(style);
@@ -52,16 +54,14 @@ AssStyleStorage::~AssStyleStorage() {
 void AssStyleStorage::Save() const {
 	if (storage_name.empty()) return;
 
-	wxString dirname = StandardPaths::DecodePath("?user/catalog/");
-	if (!wxDirExists(dirname) && !wxMkdir(dirname))
-		throw "Failed creating directory for style catalogs";
+	agi::fs::CreateDirectory(StandardPaths::DecodePath("?user/catalog/"));
 
 	TextFileWriter file(StandardPaths::DecodePath("?user/catalog/" + storage_name + ".sty"), "UTF-8");
 	for (const AssStyle *cur : style)
 		file.WriteLineToFile(cur->GetEntryData());
 }
 
-void AssStyleStorage::Load(wxString const& name) {
+void AssStyleStorage::Load(std::string const& name) {
 	storage_name = name;
 	Clear();
 
@@ -69,17 +69,14 @@ void AssStyleStorage::Load(wxString const& name) {
 		TextFileReader file(StandardPaths::DecodePath("?user/catalog/" + name + ".sty"), "UTF-8");
 
 		while (file.HasMoreLines()) {
-			wxString data = file.ReadLineFromFile();
-			if (data.StartsWith("Style:")) {
-				try {
-					style.push_back(new AssStyle(data));
-				} catch(...) {
-					/* just ignore invalid lines for now */
-				}
+			try {
+				style.push_back(new AssStyle(file.ReadLineFromFile()));
+			} catch(...) {
+				/* just ignore invalid lines for now */
 			}
 		}
 	}
-	catch (agi::FileNotAccessibleError const&) {
+	catch (agi::fs::FileNotAccessible const&) {
 		// Just treat a missing file as an empty file
 	}
 }
diff --git a/aegisub/src/ass_style_storage.h b/aegisub/src/ass_style_storage.h
index 1015e26650d9d41f0472651488cbaf5f93ba62b5..d6dab605e4ebbcb4f703aa1512ddb3b93dff23f1 100644
--- a/aegisub/src/ass_style_storage.h
+++ b/aegisub/src/ass_style_storage.h
@@ -36,12 +36,10 @@
 #include <string>
 #include <vector>
 
-#include <wx/string.h>
-
 class AssStyle;
 
 class AssStyleStorage {
-	wxString storage_name;
+	std::string storage_name;
 	std::deque<AssStyle*> style;
 
 public:
@@ -77,5 +75,5 @@ public:
 
 	/// Load stored styles from a file
 	/// @param name Catalog name (note: not file name)
-	void Load(wxString const& name);
+	void Load(std::string const& name);
 };
diff --git a/aegisub/src/ass_time.cpp b/aegisub/src/ass_time.cpp
index 7c38b6634de82c1ea5373095c149413b741163ed..801df240b9fe01c745de243282e4e1e427ebb3cb 100644
--- a/aegisub/src/ass_time.cpp
+++ b/aegisub/src/ass_time.cpp
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -36,61 +23,64 @@
 
 #include "ass_time.h"
 
-#include <algorithm>
-#include <cmath>
+#include "utils.h"
 
-#include <wx/tokenzr.h>
+#include <libaegisub/util.h>
 
-#include "utils.h"
+#include <algorithm>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/format.hpp>
+#include <boost/range/adaptor/filtered.hpp>
 
 AssTime::AssTime(int time) : time(mid(0, time, 10 * 60 * 60 * 1000 - 1)) { }
 
-AssTime::AssTime(wxString const& text)
+AssTime::AssTime(std::string const& text)
 : time(0)
 {
-	size_t pos = 0, end = 0;
-
-	int colons = text.Freq(':');
-
-	// Set start so that there are only two colons at most
-	for (; colons > 2; --colons) pos = text.find(':', pos) + 1;
-
-	// Hours
-	if (colons == 2) {
-		while (text[end++] != ':') { }
-		time += AegiStringToInt(text, pos, end) * 60 * 60 * 1000;
-		pos = end;
+	int after_decimal = -1;
+	int current = 0;
+	for (char c : text | boost::adaptors::filtered(boost::is_any_of(":0123456789."))) {
+		if (c == ':') {
+			time = time * 60 + current;
+			current = 0;
+		}
+		else if (c == '.') {
+			time = (time * 60 + current) * 1000;
+			current = 0;
+			after_decimal = 100;
+		}
+		else if (after_decimal < 0) {
+			current *= 10;
+			current += c - '0';
+		}
+		else {
+			time += (c - '0') * after_decimal;
+			after_decimal /= 10;
+		}
 	}
 
-	// Minutes
-	if (colons >= 1) {
-		while (text[end++] != ':') { }
-		time += AegiStringToInt(text, pos, end) * 60 * 1000;
-	}
-
-	// Milliseconds (includes seconds)
-	time += AegiStringToFix(text, 3, end, text.size());
+	// Never saw a decimal, so convert now to ms
+	if (after_decimal < 0)
+		time = (time * 60 + current) * 1000;
 
 	// Limit to the valid range
 	time = mid(0, time, 10 * 60 * 60 * 1000 - 1);
 }
 
-wxString AssTime::GetAssFormated(bool msPrecision) const {
-	wxChar ret[] = {
-		'0' + GetTimeHours(),
-		':',
-		'0' + (time % (60 * 60 * 1000)) / (60 * 1000 * 10),
-		'0' + (time % (10 * 60 * 1000)) / (60 * 1000),
-		':',
-		'0' + (time % (60 * 1000)) / (1000 * 10),
-		'0' + (time % (10 * 1000)) / 1000,
-		'.',
-		'0' + (time % 1000) / 100,
-		'0' + (time % 100) / 10,
-		'0' + time % 10
-	};
-
-	return wxString(ret, msPrecision ? 11 : 10);
+std::string AssTime::GetAssFormated(bool msPrecision) const {
+	std::string ret(10 + msPrecision, ':');
+	ret[0] = '0' + GetTimeHours();
+	ret[2] = '0' + (time % (60 * 60 * 1000)) / (60 * 1000 * 10);
+	ret[3] = '0' + (time % (10 * 60 * 1000)) / (60 * 1000);
+	ret[5] = '0' + (time % (60 * 1000)) / (1000 * 10);
+	ret[6] = '0' + (time % (10 * 1000)) / 1000;
+	ret[7] = '.';
+	ret[8] = '0' + (time % 1000) / 100;
+	ret[9] = '0' + (time % 100) / 10;
+	if (msPrecision)
+		ret[10] = '0' + time % 10;
+	return ret;
 }
 
 int AssTime::GetTimeHours() const { return time / 3600000; }
@@ -99,25 +89,27 @@ int AssTime::GetTimeSeconds() const { return (time % 60000) / 1000; }
 int AssTime::GetTimeMiliseconds() const { return (time % 1000); }
 int AssTime::GetTimeCentiseconds() const { return (time % 1000) / 10; }
 
-SmpteFormatter::SmpteFormatter(agi::vfr::Framerate fps, char sep)
+SmpteFormatter::SmpteFormatter(agi::vfr::Framerate fps, std::string const& sep)
 : fps(fps)
 , sep(sep)
 {
 }
 
-wxString SmpteFormatter::ToSMPTE(AssTime time) const {
+std::string SmpteFormatter::ToSMPTE(AssTime time) const {
 	int h=0, m=0, s=0, f=0;
 	fps.SmpteAtTime(time, &h, &m, &s, &f);
-	return wxString::Format("%02d%c%02d%c%02d%c%02d", h, sep, m, sep, s, sep, f);
+	return str(boost::format("%02d%s%02d%s%02d%c%02d") % h % sep % m % sep % s % sep % f);
 }
 
-AssTime SmpteFormatter::FromSMPTE(wxString const& str) const {
-	long h, m, s, f;
-	wxArrayString toks = wxStringTokenize(str, sep);
+AssTime SmpteFormatter::FromSMPTE(std::string const& str) const {
+	std::vector<std::string> toks;
+	boost::split(toks, str, boost::is_any_of(sep));
 	if (toks.size() != 4) return 0;
-	toks[0].ToLong(&h);
-	toks[1].ToLong(&m);
-	toks[2].ToLong(&s);
-	toks[3].ToLong(&f);
+
+	int h, m, s, f;
+	agi::util::try_parse(toks[0], &h);
+	agi::util::try_parse(toks[1], &m);
+	agi::util::try_parse(toks[2], &s);
+	agi::util::try_parse(toks[3], &f);
 	return fps.TimeAtSmpte(h, m, s, f);
 }
diff --git a/aegisub/src/ass_time.h b/aegisub/src/ass_time.h
index 44e0372c768a127e01a785fb291e9b42036616b4..54d29ba7671689ce7b61c2cdc67f1704f9f987db 100644
--- a/aegisub/src/ass_time.h
+++ b/aegisub/src/ass_time.h
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -34,7 +21,7 @@
 
 #pragma once
 
-#include <wx/string.h>
+#include <string>
 
 #include <libaegisub/vfr.h>
 
@@ -44,7 +31,7 @@ class AssTime {
 
 public:
 	AssTime(int ms = 0);
-	AssTime(wxString const& text);
+	AssTime(std::string const& text);
 
 	/// Get millisecond, rounded to centisecond precision
 	operator int() const { return time / 10 * 10; }
@@ -57,7 +44,7 @@ public:
 
 	/// Return the time as a string
 	/// @param ms Use milliseconds precision, for non-ASS formats
-	wxString GetAssFormated(bool ms=false) const;
+	std::string GetAssFormated(bool ms=false) const;
 };
 
 /// @class SmpteFormatter
@@ -66,13 +53,13 @@ class SmpteFormatter {
 	/// Frame rate to use
 	agi::vfr::Framerate fps;
 	/// Separator character
-	char sep;
+	std::string sep;
 
 public:
-	SmpteFormatter(agi::vfr::Framerate fps, char sep=':');
+	SmpteFormatter(agi::vfr::Framerate fps, std::string const& sep=":");
 
 	/// Convert an AssTime to a SMPTE timecode
-	wxString ToSMPTE(AssTime time) const;
+	std::string ToSMPTE(AssTime time) const;
 	/// Convert a SMPTE timecode to an AssTime
-	AssTime FromSMPTE(wxString const& str) const;
+	AssTime FromSMPTE(std::string const& str) const;
 };
diff --git a/aegisub/src/audio_controller.cpp b/aegisub/src/audio_controller.cpp
index 446fc626b0248c1867d34537d758b72dd88aabcc..dc3427362d7d38a0a16f057fd9354244e95cfda7 100644
--- a/aegisub/src/audio_controller.cpp
+++ b/aegisub/src/audio_controller.cpp
@@ -34,14 +34,9 @@
 
 #include "config.h"
 
-#include <algorithm>
-
-#include <wx/filename.h>
-
-#include <libaegisub/io.h>
+#include "audio_controller.h"
 
 #include "ass_file.h"
-#include "audio_controller.h"
 #include "audio_provider_dummy.h"
 #include "audio_timing.h"
 #include "compat.h"
@@ -51,10 +46,14 @@
 #include "pen.h"
 #include "options.h"
 #include "selection_controller.h"
-#include "standard_paths.h"
 #include "utils.h"
 #include "video_context.h"
 
+#include <libaegisub/io.h>
+#include <libaegisub/path.h>
+
+#include <algorithm>
+
 AudioController::AudioController(agi::Context *context)
 : context(context)
 , subtitle_save_slot(context->ass->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
@@ -81,13 +80,11 @@ AudioController::AudioController(agi::Context *context)
 #endif
 }
 
-
 AudioController::~AudioController()
 {
 	CloseAudio();
 }
 
-
 void AudioController::OnPlaybackTimer(wxTimerEvent &)
 {
 	int64_t pos = player->GetCurrentPosition();
@@ -105,7 +102,6 @@ void AudioController::OnPlaybackTimer(wxTimerEvent &)
 	}
 }
 
-
 #ifdef wxHAS_POWER_EVENTS
 void AudioController::OnComputerSuspending(wxPowerEvent &)
 {
@@ -114,7 +110,6 @@ void AudioController::OnComputerSuspending(wxPowerEvent &)
 	player = 0;
 }
 
-
 void AudioController::OnComputerResuming(wxPowerEvent &)
 {
 	if (provider)
@@ -154,25 +149,25 @@ void AudioController::OnAudioProviderChanged()
 {
 	if (IsAudioOpen())
 		// url is cloned because CloseAudio clears it and OpenAudio takes a const reference
-		OpenAudio(audio_url.Clone());
+		OpenAudio(agi::fs::path(audio_url));
 }
 
 
-void AudioController::OpenAudio(const wxString &url)
+void AudioController::OpenAudio(agi::fs::path const& url)
 {
-	if (!url)
+	if (url.empty())
 		throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", 0);
 
 	AudioProvider *new_provider = 0;
 	try {
 		new_provider = AudioProviderFactory::GetProvider(url);
-		StandardPaths::SetPathValue("?audio", wxFileName(url).GetPath());
+		config::path->SetToken("?audio", url);
 	}
 	catch (agi::UserCancelException const&) {
 		throw;
 	}
 	catch (...) {
-		config::mru->Remove("Audio", from_wx(url));
+		config::mru->Remove("Audio", url);
 		throw;
 	}
 
@@ -192,11 +187,10 @@ void AudioController::OpenAudio(const wxString &url)
 
 	audio_url = url;
 
-	config::mru->Add("Audio", from_wx(url));
+	config::mru->Add("Audio", url);
 
 	try
 	{
-		// Tell listeners about this.
 		AnnounceAudioOpen(provider);
 	}
 	catch (...)
@@ -206,7 +200,6 @@ void AudioController::OpenAudio(const wxString &url)
 	}
 }
 
-
 void AudioController::CloseAudio()
 {
 	Stop();
@@ -218,7 +211,7 @@ void AudioController::CloseAudio()
 
 	audio_url.clear();
 
-	StandardPaths::SetPathValue("?audio", "");
+	config::path->SetToken("?audio", "");
 
 	AnnounceAudioClose();
 }
@@ -229,12 +222,6 @@ bool AudioController::IsAudioOpen() const
 	return player && provider;
 }
 
-
-wxString AudioController::GetAudioURL() const
-{
-	return audio_url;
-}
-
 void AudioController::SetTimingController(AudioTimingController *new_controller)
 {
 	if (timing_controller.get() != new_controller) {
@@ -259,13 +246,9 @@ void AudioController::OnTimingControllerUpdatedPrimaryRange()
 void AudioController::OnSubtitlesSave()
 {
 	if (IsAudioOpen())
-	{
-		context->ass->SetScriptInfo("Audio URI", MakeRelativePath(audio_url, context->ass->filename));
-	}
+		context->ass->SetScriptInfo("Audio URI", config::path->MakeRelative(audio_url, "?script").generic_string());
 	else
-	{
 		context->ass->SetScriptInfo("Audio URI", "");
-	}
 }
 
 void AudioController::PlayRange(const TimeRange &range)
@@ -402,13 +385,13 @@ int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
 	return millisamples / sr;
 }
 
-void AudioController::SaveClip(wxString const& filename, TimeRange const& range) const
+void AudioController::SaveClip(agi::fs::path const& filename, TimeRange const& range) const
 {
 	int64_t start_sample = SamplesFromMilliseconds(range.begin());
 	int64_t end_sample = SamplesFromMilliseconds(range.end());
 	if (filename.empty() || start_sample > provider->GetNumSamples() || range.length() == 0) return;
 
-	agi::io::Save outfile(from_wx(filename), true);
+	agi::io::Save outfile(filename, true);
 	std::ofstream& out(outfile.Get());
 
 	size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
diff --git a/aegisub/src/audio_controller.h b/aegisub/src/audio_controller.h
index e63ed1814fd44de18afee1ad0ee39e6cb1438a27..b804e073c45786b764d46ea7d7f09da35fd79044 100644
--- a/aegisub/src/audio_controller.h
+++ b/aegisub/src/audio_controller.h
@@ -33,20 +33,16 @@
 
 #pragma once
 
-#include <cassert>
+#include <boost/filesystem/path.hpp>
 #include <cstdint>
 #include <memory>
-#include <set>
-#include <vector>
 
 #include <wx/event.h>
-#include <wx/string.h>
 #include <wx/timer.h>
-#include <wx/pen.h>
 #include <wx/power.h>
 
 #include <libaegisub/exception.h>
-#include <libaegisub/scoped_ptr.h>
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/signal.h>
 
 class AudioPlayer;
@@ -96,10 +92,10 @@ class AudioController : public wxEvtHandler {
 	AudioProvider *provider;
 
 	/// The current timing mode, if any; owned by the audio controller
-	agi::scoped_ptr<AudioTimingController> timing_controller;
+	std::unique_ptr<AudioTimingController> timing_controller;
 
 	/// The URL of the currently open audio, if any
-	wxString audio_url;
+	agi::fs::path audio_url;
 
 
 	enum PlaybackMode {
@@ -157,13 +153,9 @@ public:
 	/// @brief Destructor
 	~AudioController();
 
-
 	/// @brief Open an audio stream
 	/// @param url URL of the stream to open
-	///
-	/// The URL can either be a plain filename (with no qualifiers) or one
-	/// recognised by various providers.
-	void OpenAudio(const wxString &url);
+	void OpenAudio(agi::fs::path const& url);
 
 	/// @brief Closes the current audio stream
 	void CloseAudio();
@@ -177,7 +169,7 @@ public:
 	///
 	/// The returned URL can be passed into OpenAudio() later to open the same
 	/// stream again.
-	wxString GetAudioURL() const;
+	agi::fs::path GetAudioURL() const { return audio_url; }
 
 
 	/// @brief Start or restart audio playback, playing a range
@@ -263,7 +255,7 @@ public:
 	/// @brief Save a portion of the decoded loaded audio to a wav file
 	/// @param filename File to save to
 	/// @param range Time range to save
-	void SaveClip(wxString const& filename, TimeRange const& range) const;
+	void SaveClip(agi::fs::path const& filename, TimeRange const& range) const;
 
 	DEFINE_SIGNAL_ADDERS(AnnounceAudioOpen,               AddAudioOpenListener)
 	DEFINE_SIGNAL_ADDERS(AnnounceAudioClose,              AddAudioCloseListener)
diff --git a/aegisub/src/audio_display.cpp b/aegisub/src/audio_display.cpp
index 5de97051b418764b938e9ecf4c3dcba80922aaa3..fc512a1efb03bc32cb53df0ea8f1d08ff34f5c61 100644
--- a/aegisub/src/audio_display.cpp
+++ b/aegisub/src/audio_display.cpp
@@ -963,7 +963,7 @@ void AudioDisplay::SetTrackCursor(int new_pos, bool show_time)
 		if (show_time)
 		{
 			AssTime new_label_time = TimeFromAbsoluteX(track_cursor_pos);
-			track_cursor_label = new_label_time.GetAssFormated();
+			track_cursor_label = to_wx(new_label_time.GetAssFormated());
 			track_cursor_label_rect.x += new_pos - old_pos;
 			RefreshRect(track_cursor_label_rect, false);
 		}
diff --git a/aegisub/src/audio_player.cpp b/aegisub/src/audio_player.cpp
index 3d27ff9c767871111b5b4fa0855f56f08be95714..951c15c0c9036c09a544977269f44311b980f399 100644
--- a/aegisub/src/audio_player.cpp
+++ b/aegisub/src/audio_player.cpp
@@ -44,7 +44,6 @@
 #include "audio_player_pulse.h"
 
 #include "audio_controller.h"
-#include "compat.h"
 #include "options.h"
 
 AudioPlayer::AudioPlayer(AudioProvider *provider)
diff --git a/aegisub/src/audio_player_alsa.cpp b/aegisub/src/audio_player_alsa.cpp
index c8f0ed5fd5c7d237e36899661e31e2d85f943da9..cba6432c4a190f1256c1fc870dd8e42e4c75d930 100644
--- a/aegisub/src/audio_player_alsa.cpp
+++ b/aegisub/src/audio_player_alsa.cpp
@@ -369,8 +369,7 @@ AlsaPlayer::AlsaPlayer(AudioProvider *provider)
 {
 	ps->provider = provider;
 
-	wxString device_name = to_wx(OPT_GET("Player/Audio/ALSA/Device")->GetString());
-	ps->device_name = std::string(device_name.utf8_str());
+	ps->device_name = OPT_GET("Player/Audio/ALSA/Device")->GetString();
 
 	if (pthread_create(&thread, 0, &playback_thread, ps.get()) != 0)
 		throw agi::AudioPlayerOpenError("AlsaPlayer: Creating the playback thread failed", 0);
diff --git a/aegisub/src/audio_player_oss.cpp b/aegisub/src/audio_player_oss.cpp
index 1c2d89ca46f3419bdd131f5b2bf461f20140fac2..92df02acf46b01c744c81bb93180bb4e3ffea281 100644
--- a/aegisub/src/audio_player_oss.cpp
+++ b/aegisub/src/audio_player_oss.cpp
@@ -75,7 +75,7 @@ void OSSPlayer::OpenStream()
 
     // Open device
     wxString device = to_wx(OPT_GET("Player/Audio/OSS/Device")->GetString());
-    dspdev = ::open(device.mb_str(wxConvUTF8), O_WRONLY, 0);
+    dspdev = ::open(device.utf8_str(), O_WRONLY, 0);
     if (dspdev < 0) {
         throw OSSError("OSS player: opening device failed", 0);
     }
diff --git a/aegisub/src/audio_provider.cpp b/aegisub/src/audio_provider.cpp
index d9e7d2b5eb79eb26f012a2bca2ba1fc2e90cba37..f6a2b5317c4c25819fd187ecfabd5805d32b076c 100644
--- a/aegisub/src/audio_provider.cpp
+++ b/aegisub/src/audio_provider.cpp
@@ -32,11 +32,8 @@
 /// @ingroup audio_input
 ///
 
-
 #include "config.h"
 
-#include <cstdint>
-
 #include "audio_provider_avs.h"
 #include "audio_provider_convert.h"
 #include "audio_provider_dummy.h"
@@ -47,13 +44,13 @@
 #include "audio_provider_ram.h"
 
 #include "audio_controller.h"
-#include "compat.h"
 #include "dialog_progress.h"
 #include "frame_main.h"
 #include "main.h"
 #include "options.h"
 #include "utils.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
 
 void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
@@ -126,7 +123,7 @@ struct provider_creator {
 				LOG_I("audio_provider") << "Using audio provider: " << name;
 			return provider;
 		}
-		catch (agi::FileNotFoundError const& err) {
+		catch (agi::fs::FileNotFound const& err) {
 			LOG_D("audio_provider") << err.GetChainedMessage();
 			msg += name + ": " + err.GetMessage() + " not found.\n";
 		}
@@ -147,7 +144,7 @@ struct provider_creator {
 };
 }
 
-AudioProvider *AudioProviderFactory::GetProvider(wxString const& filename) {
+AudioProvider *AudioProviderFactory::GetProvider(agi::fs::path const& filename) {
 	provider_creator creator;
 	AudioProvider *provider = nullptr;
 
@@ -172,7 +169,7 @@ AudioProvider *AudioProviderFactory::GetProvider(wxString const& filename) {
 			throw agi::AudioProviderOpenError(creator.msg, 0);
 		if (creator.found_file)
 			throw agi::AudioDataNotFoundError(creator.msg, 0);
-		throw agi::FileNotFoundError(from_wx(filename));
+		throw agi::fs::FileNotFound(filename);
 	}
 
 	bool needsCache = provider->NeedsCache();
@@ -206,4 +203,4 @@ void AudioProviderFactory::RegisterProviders() {
 #endif
 }
 
-template<> AudioProviderFactory::map *FactoryBase<AudioProvider *(*)(wxString)>::classes = nullptr;
+template<> AudioProviderFactory::map *FactoryBase<AudioProvider *(*)(agi::fs::path)>::classes = nullptr;
diff --git a/aegisub/src/audio_provider_avs.cpp b/aegisub/src/audio_provider_avs.cpp
index dd3178f65496c6742a5789397b3a00c42acf73ce..07ee347f2ae4bb2c8078083986dc89c816f94fae 100644
--- a/aegisub/src/audio_provider_avs.cpp
+++ b/aegisub/src/audio_provider_avs.cpp
@@ -38,38 +38,38 @@
 #include "audio_provider_avs.h"
 
 #include "audio_controller.h"
-#include "charset_conv.h"
-#include "compat.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "utils.h"
 
-#include <wx/filename.h>
+#include <libaegisub/access.h>
+#include <libaegisub/charset_conv.h>
+#include <libaegisub/fs.h>
 
-AvisynthAudioProvider::AvisynthAudioProvider(wxString filename) {
+#include <mutex>
+
+AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
 	this->filename = filename;
 
-	wxMutexLocker lock(avs_wrapper.GetMutex());
+	agi::acs::CheckFileRead(filename);
 
-	wxFileName fn(filename);
-	if (!fn.FileExists())
-		throw agi::FileNotFoundError(from_wx(filename));
+	std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());
 
 	try {
 		IScriptEnvironment *env = avs_wrapper.GetEnv();
 
 		// Include
-		if (filename.EndsWith(".avs"))
-			LoadFromClip(env->Invoke("Import", env->SaveString(fn.GetShortPath().mb_str(csConvLocal))));
+		if (agi::fs::HasExtension(filename, "avs"))
+			LoadFromClip(env->Invoke("Import", env->SaveString(agi::fs::ShortName(filename).c_str())));
 		// Use DirectShowSource
 		else {
 			const char * argnames[3] = { 0, "video", "audio" };
-			AVSValue args[3] = { env->SaveString(fn.GetShortPath().mb_str(csConvLocal)), false, true };
+			AVSValue args[3] = { env->SaveString(agi::fs::ShortName(filename).c_str()), false, true };
 
 			// Load DirectShowSource.dll from app dir if it exists
-			wxFileName dsspath(StandardPaths::DecodePath("?data/DirectShowSource.dll"));
-			if (dsspath.FileExists())
-				env->Invoke("LoadPlugin", env->SaveString(dsspath.GetShortPath().mb_str(csConvLocal)));
+			agi::fs::path dsspath(StandardPaths::DecodePath("?data/DirectShowSource.dll"));
+			if (agi::fs::FileExists(dsspath))
+				env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dsspath).c_str()));
 
 			// Load audio with DSS if it exists
 			if (env->FunctionExists("DirectShowSource"))
diff --git a/aegisub/src/audio_provider_avs.h b/aegisub/src/audio_provider_avs.h
index 8784aa6d479c4fddd18ea592fa2bea476d1d68d6..3e0cb58b0e9874be55ffb2cb72b932f876b6dc55 100644
--- a/aegisub/src/audio_provider_avs.h
+++ b/aegisub/src/audio_provider_avs.h
@@ -46,7 +46,7 @@ class AvisynthAudioProvider : public AudioProvider {
 	void FillBuffer(void *buf, int64_t start, int64_t count) const;
 
 public:
-	AvisynthAudioProvider(wxString filename);
+	AvisynthAudioProvider(agi::fs::path const& filename);
 
 	bool AreSamplesNativeEndian() const { return true; }
 	bool NeedsCache() const { return true; }
diff --git a/aegisub/src/audio_provider_convert.cpp b/aegisub/src/audio_provider_convert.cpp
index 0049721484796ac6bb35d8c74169fb9826eb7cb1..fbab34c2d789619b045794cd0dc30ee7fb4ca27f 100644
--- a/aegisub/src/audio_provider_convert.cpp
+++ b/aegisub/src/audio_provider_convert.cpp
@@ -46,7 +46,7 @@ public:
 	}
 
 	bool AreSamplesNativeEndian() const { return true; }
-	wxString GetFilename() const { return source->GetFilename(); }
+	agi::fs::path GetFilename() const { return source->GetFilename(); }
 };
 
 /// Anything integral -> 16 bit signed machine-endian audio converter
diff --git a/aegisub/src/audio_provider_dummy.cpp b/aegisub/src/audio_provider_dummy.cpp
index 28a672e9132262f00abbd454aa4648854aab9031..d9f60d6bb96fc0f47cf6fb42e36aac594de0ad5d 100644
--- a/aegisub/src/audio_provider_dummy.cpp
+++ b/aegisub/src/audio_provider_dummy.cpp
@@ -37,7 +37,9 @@
 #include "audio_provider_dummy.h"
 #include "utils.h"
 
-#include <wx/uri.h>
+#include <libaegisub/fs.h>
+
+#include <boost/algorithm/string/predicate.hpp>
 
 /*
  * scheme            ::= "dummy-audio" ":" signal-specifier "?" signal-parameters
@@ -59,13 +61,11 @@
  *       in every channel even if one would be LFE.
  * "ln", length of signal in samples. ln/sr gives signal length in seconds.
  */
-DummyAudioProvider::DummyAudioProvider(wxString uri)
-{
-	wxURI parsed(uri);
-	if (parsed.GetScheme() != "dummy-audio")
-		throw agi::FileNotFoundError("Not a dummy audio URI");
+DummyAudioProvider::DummyAudioProvider(agi::fs::path const& uri) {
+	if (!boost::starts_with(uri.string(), "dummy-audio:"))
+		throw agi::fs::FileNotFound(std::string("Not a dummy audio URI"));
 
-	noise = parsed.GetPath() == "noise";
+	noise = boost::contains(uri.string(), ":noise?");
 	channels = 1;
 	sample_rate = 44100;
 	bytes_per_sample = 2;
diff --git a/aegisub/src/audio_provider_dummy.h b/aegisub/src/audio_provider_dummy.h
index 1dbcaf9753b5f8c5ad3e9975025bfb1b26abace4..05d5f02f8a08167fb3bbed6f74db005f076046b3 100644
--- a/aegisub/src/audio_provider_dummy.h
+++ b/aegisub/src/audio_provider_dummy.h
@@ -39,7 +39,7 @@ class DummyAudioProvider : public AudioProvider {
 	void FillBuffer(void *buf, int64_t start, int64_t count) const;
 
 public:
-	DummyAudioProvider(wxString uri);
+	DummyAudioProvider(agi::fs::path const& uri);
 
 	bool AreSamplesNativeEndian() const { return true; }
 };
diff --git a/aegisub/src/audio_provider_ffmpegsource.cpp b/aegisub/src/audio_provider_ffmpegsource.cpp
index 79e07c0728df05e8e964d050f620953a0d34b2bf..203110a03b643f6f001d2a9e728b961810f12a84 100644
--- a/aegisub/src/audio_provider_ffmpegsource.cpp
+++ b/aegisub/src/audio_provider_ffmpegsource.cpp
@@ -35,22 +35,18 @@
 #include "config.h"
 
 #ifdef WITH_FFMS2
-
-#ifdef WIN32
-#include <objbase.h>
-#endif
-
-#include <map>
-
 #include "audio_provider_ffmpegsource.h"
 
 #include "audio_controller.h"
-#include "compat.h"
 #include "options.h"
 
+#include <libaegisub/fs.h>
+
+#include <map>
+
 /// @brief Constructor
 /// @param filename The filename to open
-FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(wxString filename) try
+FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(agi::fs::path const& filename) try
 : AudioSource(nullptr, FFMS_DestroyAudioSource)
 {
 	ErrInfo.Buffer		= FFMSErrMsg;
@@ -61,26 +57,24 @@ FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(wxString filename) try
 
 	LoadAudio(filename);
 }
-catch (wxString const& err) {
-	throw agi::AudioProviderOpenError(from_wx(err), 0);
+catch (std::string const& err) {
+	throw agi::AudioProviderOpenError(err, 0);
 }
 catch (const char *err) {
 	throw agi::AudioProviderOpenError(err, 0);
 }
 
-void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
-	wxString FileNameShort = wxFileName(filename).GetShortPath();
-
-	FFMS_Indexer *Indexer = FFMS_CreateIndexer(FileNameShort.utf8_str(), &ErrInfo);
+void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
+	FFMS_Indexer *Indexer = FFMS_CreateIndexer(filename.string().c_str(), &ErrInfo);
 	if (!Indexer) {
 		if (ErrInfo.SubType == FFMS_ERROR_FILE_READ)
-			throw agi::FileNotFoundError(ErrInfo.Buffer);
+			throw agi::fs::FileNotFound(std::string(ErrInfo.Buffer));
 		else
 			throw agi::AudioDataNotFoundError(ErrInfo.Buffer, 0);
 	}
 
-	std::map<int,wxString> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_AUDIO);
-	if (TrackList.size() <= 0)
+	std::map<int, std::string> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_AUDIO);
+	if (TrackList.empty())
 		throw agi::AudioDataNotFoundError("no audio tracks found", 0);
 
 	// initialize the track number to an invalid value so we can detect later on
@@ -94,13 +88,13 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
 	}
 
 	// generate a name for the cache file
-	wxString CacheName = GetCacheFilename(filename);
+	agi::fs::path CacheName = GetCacheFilename(filename);
 
 	// try to read index
 	agi::scoped_holder<FFMS_Index*, void (FFMS_CC*)(FFMS_Index*)>
-		Index(FFMS_ReadIndex(CacheName.utf8_str(), &ErrInfo), FFMS_DestroyIndex);
+		Index(FFMS_ReadIndex(CacheName.string().c_str(), &ErrInfo), FFMS_DestroyIndex);
 
-	if (Index && FFMS_IndexBelongsToFile(Index, FileNameShort.utf8_str(), &ErrInfo))
+	if (Index && FFMS_IndexBelongsToFile(Index, filename.string().c_str(), &ErrInfo))
 		Index = nullptr;
 
 	// index valid but track number still not set?
@@ -143,16 +137,13 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
 		if (TrackNumber == FFMS_TRACKMASK_ALL)
 			TrackNumber = FFMS_GetFirstTrackOfType(Index, FFMS_TYPE_AUDIO, &ErrInfo);
 	}
-	else {
+	else
 		FFMS_CancelIndexing(Indexer);
-	}
 
 	// update access time of index file so it won't get cleaned away
-	if (!wxFileName(CacheName).Touch()) {
-		// warn user?
-	}
+	agi::fs::Touch(CacheName);
 
-	AudioSource = FFMS_CreateAudioSource(FileNameShort.utf8_str(), TrackNumber, Index, -1, &ErrInfo);
+	AudioSource = FFMS_CreateAudioSource(filename.string().c_str(), TrackNumber, Index, -1, &ErrInfo);
 	if (!AudioSource)
 		throw agi::AudioProviderOpenError(std::string("Failed to open audio track: ") + ErrInfo.Buffer, 0);
 
@@ -192,8 +183,7 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
 }
 
 void FFmpegSourceAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const {
-	if (FFMS_GetAudio(AudioSource, Buf, Start, Count, &ErrInfo)) {
+	if (FFMS_GetAudio(AudioSource, Buf, Start, Count, &ErrInfo))
 		throw AudioDecodeError(std::string("Failed to get audio samples: ") + ErrInfo.Buffer);
-	}
 }
 #endif /* WITH_FFMS2 */
diff --git a/aegisub/src/audio_provider_ffmpegsource.h b/aegisub/src/audio_provider_ffmpegsource.h
index 73fef6b345a749e2095990865f6089f84c1fd71e..95fa8da18b8a43201633aead9bac3fc5292146c0 100644
--- a/aegisub/src/audio_provider_ffmpegsource.h
+++ b/aegisub/src/audio_provider_ffmpegsource.h
@@ -34,8 +34,8 @@
 
 #ifdef WITH_FFMS2
 #include "include/aegisub/audio_provider.h"
-#include "ffmpegsource_common.h"
 
+#include "ffmpegsource_common.h"
 
 /// @class FFmpegSourceAudioProvider
 /// @brief Implements audio loading with the FFMS library.
@@ -46,11 +46,11 @@ class FFmpegSourceAudioProvider : public AudioProvider, FFmpegSourceProvider {
 	mutable char FFMSErrMsg[1024];			///< FFMS error message
 	mutable FFMS_ErrorInfo ErrInfo;			///< FFMS error codes/messages
 
-	void LoadAudio(wxString filename);
+	void LoadAudio(agi::fs::path const& filename);
 	void FillBuffer(void *buf, int64_t start, int64_t count) const;
 
 public:
-	FFmpegSourceAudioProvider(wxString filename);
+	FFmpegSourceAudioProvider(agi::fs::path const& filename);
 
 	/// @brief Checks sample endianness
 	/// @return Returns true.
diff --git a/aegisub/src/audio_provider_hd.cpp b/aegisub/src/audio_provider_hd.cpp
index 82553d6b929e3caecbe198d7885c4cdd50a9b2db..54989727c447dad215cde855726287e65d9d6f58 100644
--- a/aegisub/src/audio_provider_hd.cpp
+++ b/aegisub/src/audio_provider_hd.cpp
@@ -36,50 +36,43 @@
 
 #include "audio_provider_hd.h"
 
-#include <wx/filefn.h>
-#include <wx/filename.h>
-
-#include <libaegisub/background_runner.h>
-#include <libaegisub/io.h>
-
 #include "audio_controller.h"
 #include "audio_provider_pcm.h"
 #include "compat.h"
 #include "options.h"
-#include "standard_paths.h"
 #include "utils.h"
 
+#include <libaegisub/access.h>
+#include <libaegisub/background_runner.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/io.h>
+#include <libaegisub/path.h>
+#include <libaegisub/util.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/filesystem.hpp>
+
 namespace {
-wxString cache_dir() {
-	wxString path = to_wx(OPT_GET("Audio/Cache/HD/Location")->GetString());
+agi::fs::path cache_dir() {
+	std::string path = OPT_GET("Audio/Cache/HD/Location")->GetString();
 	if (path == "default")
-		path = "?temp/";
+		path = "?temp";
 
-	return DecodeRelativePath(StandardPaths::DecodePath(path), StandardPaths::DecodePath("?user/"));
+	return config::path->MakeAbsolute(config::path->Decode(path), "?temp");
 }
 
-wxString cache_path() {
-	wxString pattern = to_wx(OPT_GET("Audio/Cache/HD/Name")->GetString());
-	if (pattern.Find("%02i") == wxNOT_FOUND) pattern = "audio%02i.tmp";
-
-	// Try from 00 to 99
-	for (int i=0;i<100;i++) {
-		// File exists?
-		wxFileName curNameTry(cache_dir(), wxString::Format(pattern, i));
-#if wxCHECK_VERSION(2, 9, 4)
-		if (!curNameTry.Exists())
-#else
-		if (!curNameTry.FileExists() && !curNameTry.DirExists())
-#endif
-			return curNameTry.GetFullPath();
-	}
-	return "";
+agi::fs::path cache_path() {
+	std::string pattern = OPT_GET("Audio/Cache/HD/Name")->GetString();
+	if (!boost::contains(pattern, "%02i")) pattern = "audio%02i.tmp";
+	boost::replace_all(pattern, "%02i", "%%%%-%%%%-%%%%-%%%%");
+	return unique_path(cache_dir()/pattern);
 }
 
 /// A PCM audio provider for raw dumps with no header
 class RawAudioProvider : public PCMAudioProvider {
 public:
-	RawAudioProvider(wxString const& cache_filename, AudioProvider *src)
+	RawAudioProvider(agi::fs::path const& cache_filename, AudioProvider *src)
 	: PCMAudioProvider(cache_filename)
 	{
 		bytes_per_sample = src->GetBytesPerSample();
@@ -110,30 +103,27 @@ HDAudioProvider::HDAudioProvider(AudioProvider *src, agi::BackgroundRunner *br)
 	float_samples    = source->AreSamplesFloat();
 
 	// Check free space
-	wxDiskspaceSize_t freespace;
-	if (wxGetDiskSpace(cache_dir(), 0, &freespace)) {
-		if (num_samples * channels * bytes_per_sample > freespace)
-			throw agi::AudioCacheOpenError("Not enough free disk space in " + from_wx(cache_dir()) + " to cache the audio", 0);
-	}
+	if ((uint64_t)num_samples * channels * bytes_per_sample > agi::fs::FreeSpace(cache_dir()))
+		throw agi::AudioCacheOpenError("Not enough free disk space in " + cache_dir().string() + " to cache the audio", 0);
 
 	diskCacheFilename = cache_path();
 
 	try {
 		{
-			agi::io::Save out(from_wx(diskCacheFilename), true);
+			agi::io::Save out(diskCacheFilename, true);
 			br->Run(bind(&HDAudioProvider::FillCache, this, src, &out.Get(), std::placeholders::_1));
 		}
 		cache_provider.reset(new RawAudioProvider(diskCacheFilename, src));
 	}
 	catch (...) {
-		wxRemoveFile(diskCacheFilename);
+		agi::fs::Remove(diskCacheFilename);
 		throw;
 	}
 }
 
 HDAudioProvider::~HDAudioProvider() {
 	cache_provider.reset(); // explicitly close the file so we can delete it
-	wxRemoveFile(diskCacheFilename);
+	agi::fs::Remove(diskCacheFilename);
 }
 
 void HDAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
diff --git a/aegisub/src/audio_provider_hd.h b/aegisub/src/audio_provider_hd.h
index 1f878702adc02d9a0fcf7eceacc95d59819d9369..8162ff1eb9dfb3cb4c906af0589fb6957905bc7a 100644
--- a/aegisub/src/audio_provider_hd.h
+++ b/aegisub/src/audio_provider_hd.h
@@ -32,8 +32,6 @@
 /// @ingroup audio_input
 ///
 
-#include <iosfwd>
-
 #include "include/aegisub/audio_provider.h"
 
 #include <libaegisub/scoped_ptr.h>
@@ -45,7 +43,7 @@ namespace agi {
 
 class HDAudioProvider : public AudioProvider {
 	/// Name of the file which the decoded audio is written to
-	wxString diskCacheFilename;
+	agi::fs::path diskCacheFilename;
 	/// Audio provider which reads from the decoded cache
 	agi::scoped_ptr<AudioProvider> cache_provider;
 
diff --git a/aegisub/src/audio_provider_lock.cpp b/aegisub/src/audio_provider_lock.cpp
index 12e948d13da9397be64a6d0e10ad58719c04990e..44100c67d4cb6f7b9760b38d93d15a858a52df23 100644
--- a/aegisub/src/audio_provider_lock.cpp
+++ b/aegisub/src/audio_provider_lock.cpp
@@ -29,6 +29,6 @@ LockAudioProvider::LockAudioProvider(AudioProvider *source) : source(source) {
 }
 
 void LockAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
-	wxMutexLocker lock(mutex);
+	std::unique_lock<std::mutex> lock(mutex);
 	source->GetAudio(buf, start, count);
 }
diff --git a/aegisub/src/audio_provider_lock.h b/aegisub/src/audio_provider_lock.h
index 3315d0d64d0f847269908eb7ff099fd199cb0db8..053ef51c1a81542b7b1c7b32f36b4abedbf72f1c 100644
--- a/aegisub/src/audio_provider_lock.h
+++ b/aegisub/src/audio_provider_lock.h
@@ -18,13 +18,12 @@
 
 #include "include/aegisub/audio_provider.h"
 
-#include <libaegisub/scoped_ptr.h>
-
-#include <wx/thread.h>
+#include <memory>
+#include <mutex>
 
 class LockAudioProvider : public AudioProvider {
-	agi::scoped_ptr<const AudioProvider> source;
-	mutable wxMutex mutex;
+	std::unique_ptr<const AudioProvider> source;
+	mutable std::mutex mutex;
 
 	void FillBuffer(void *buf, int64_t start, int64_t count) const;
 public:
diff --git a/aegisub/src/audio_provider_pcm.cpp b/aegisub/src/audio_provider_pcm.cpp
index 59a35c81b0335e4acb112a8ab60ad63e7a105020..13da1844f887d7d59504b95e255206c528da92f4 100644
--- a/aegisub/src/audio_provider_pcm.cpp
+++ b/aegisub/src/audio_provider_pcm.cpp
@@ -32,30 +32,26 @@
 /// @ingroup audio_input
 ///
 
-
 #include "config.h"
 
+#include "audio_provider_pcm.h"
+
+#include "aegisub_endian.h"
+#include "audio_controller.h"
+#include "utils.h"
+
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
+
 #include <cassert>
 #include <cstdint>
-#ifndef __WINDOWS__
+#ifndef _WIN32
 #include <sys/fcntl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #endif
 
-#include <wx/file.h>
-#include <wx/filename.h>
-#include <wx/log.h>
-
-#include <libaegisub/log.h>
-
-#include "aegisub_endian.h"
-#include "audio_controller.h"
-#include "audio_provider_pcm.h"
-#include "compat.h"
-#include "utils.h"
-
-PCMAudioProvider::PCMAudioProvider(const wxString &filename)
+PCMAudioProvider::PCMAudioProvider(agi::fs::path const& filename)
 : current_mapping(0)
 , mapping_start(0)
 , mapping_length(0)
@@ -73,13 +69,7 @@ PCMAudioProvider::PCMAudioProvider(const wxString &filename)
 		0);
 
 	if (file_handle == INVALID_HANDLE_VALUE)
-		throw agi::FileNotFoundError(from_wx(filename));
-
-	LARGE_INTEGER li_file_size = {0};
-	if (!GetFileSizeEx(file_handle, &li_file_size))
-		throw agi::AudioProviderOpenError("Failed getting file size", 0);
-
-	file_size = li_file_size.QuadPart;
+		throw agi::fs::FileNotFound(filename);
 
 	file_mapping = CreateFileMapping(
 		file_handle,
@@ -91,24 +81,22 @@ PCMAudioProvider::PCMAudioProvider(const wxString &filename)
 	if (file_mapping == 0)
 		throw agi::AudioProviderOpenError("Failed creating file mapping", 0);
 #else
-, file_handle(open(filename.mb_str(*wxConvFileName), O_RDONLY), close)
+, file_handle(open(filename.c_str(), O_RDONLY), close)
 {
 	if (file_handle == -1)
-		throw agi::FileNotFoundError(from_wx(filename));
-
-	struct stat filestats;
-	memset(&filestats, 0, sizeof(filestats));
-	if (fstat(file_handle, &filestats)) {
-		close(file_handle);
-		throw agi::AudioProviderOpenError("Could not stat file to get size", 0);
-	}
-	file_size = filestats.st_size;
+		throw agi::fs::FileNotFound(filename.string());
 #endif
 	float_samples = false;
+
+	try {
+		file_size = agi::fs::Size(filename);
+	}
+	catch (agi::Exception const& e) {
+		throw agi::AudioPlayerOpenError("Could not get file size", e.Copy());
+	}
 }
 
-PCMAudioProvider::~PCMAudioProvider()
-{
+PCMAudioProvider::~PCMAudioProvider() {
 #ifdef _WIN32
 	if (current_mapping)
 		UnmapViewOfFile(current_mapping);
@@ -118,11 +106,9 @@ PCMAudioProvider::~PCMAudioProvider()
 #endif
 }
 
-char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length) const
-{
-	if (range_start + range_length > file_size) {
+char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length) const {
+	if (range_start + range_length > file_size)
 		throw AudioDecodeError("Attempted to map beyond end of file");
-	}
 
 	// Check whether the requested range is already visible
 	if (!current_mapping || range_start < mapping_start || range_start+range_length > mapping_start+(int64_t)mapping_length) {
@@ -171,9 +157,8 @@ char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t rang
 		current_mapping = mmap(0, mapping_length, PROT_READ, MAP_PRIVATE, file_handle, mapping_start);
 #endif
 
-		if (!current_mapping) {
+		if (!current_mapping)
 			throw AudioDecodeError("Failed mapping a view of the file");
-		}
 	}
 
 	assert(current_mapping);
@@ -186,8 +171,7 @@ char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t rang
 	return ((char*)current_mapping) + rel_ofs;
 }
 
-void PCMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const
-{
+void PCMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
 	// Read blocks from the file
 	size_t index = 0;
 	while (count > 0 && index < index_points.size()) {
@@ -268,10 +252,10 @@ class  RiffWavPCMAudioProvider : public PCMAudioProvider {
 
 public:
 
-	RiffWavPCMAudioProvider(const wxString &_filename)
-	: PCMAudioProvider(_filename)
+	RiffWavPCMAudioProvider(agi::fs::path const& filename)
+	: PCMAudioProvider(filename)
 	{
-		filename = _filename;
+		this->filename = filename;
 
 		// Read header
 		void *filestart = EnsureRangeAccessible(0, sizeof(RIFFChunk));
@@ -345,8 +329,7 @@ public:
 		}
 	}
 
-	bool AreSamplesNativeEndian() const
-	{
+	bool AreSamplesNativeEndian() const {
 		// 8 bit samples don't consider endianness
 		if (bytes_per_sample < 2) return true;
 		// Otherwise test whether we're little endian
@@ -411,17 +394,16 @@ class Wave64AudioProvider : public PCMAudioProvider {
 		uint64_t chunk_size;
 	};
 
-	inline bool CheckGuid(const uint8_t *guid1, const uint8_t *guid2)
-	{
+	bool CheckGuid(const uint8_t *guid1, const uint8_t *guid2) {
 		return memcmp(guid1, guid2, 16) == 0;
 	}
 
 public:
 
-	Wave64AudioProvider(const wxString &_filename)
-	: PCMAudioProvider(_filename)
+	Wave64AudioProvider(agi::fs::path const& filename)
+	: PCMAudioProvider(filename)
 	{
-		filename = _filename;
+		this->filename = filename;
 
 		int64_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
 
@@ -496,8 +478,7 @@ public:
 		}
 	}
 
-	bool AreSamplesNativeEndian() const
-	{
+	bool AreSamplesNativeEndian() const {
 		// 8 bit samples don't consider endianness
 		if (bytes_per_sample < 2) return true;
 		// Otherwise test whether we're little endian
@@ -506,8 +487,7 @@ public:
 	}
 };
 
-AudioProvider *CreatePCMAudioProvider(const wxString &filename)
-{
+AudioProvider *CreatePCMAudioProvider(agi::fs::path const& filename) {
 	bool wrong_file_type = true;
 	std::string msg;
 
diff --git a/aegisub/src/audio_provider_pcm.h b/aegisub/src/audio_provider_pcm.h
index 758129cffbc4d93150cb87cebed6011438868c15..ab61363f8fbc0d69133381e874d0dfd0aabc1812 100644
--- a/aegisub/src/audio_provider_pcm.h
+++ b/aegisub/src/audio_provider_pcm.h
@@ -34,9 +34,6 @@
 
 #include <vector>
 
-#include <wx/file.h>
-#include <wx/thread.h>
-
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
@@ -63,7 +60,7 @@ class PCMAudioProvider : public AudioProvider {
 #endif
 
 protected:
-	PCMAudioProvider(const wxString &filename); // Create base object and open the file mapping
+	PCMAudioProvider(agi::fs::path const& filename); // Create base object and open the file mapping
 	virtual ~PCMAudioProvider(); // Closes the file mapping
 	char * EnsureRangeAccessible(int64_t range_start, int64_t range_length) const; // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range
 
@@ -84,4 +81,4 @@ protected:
 };
 
 // Construct the right PCM audio provider (if any) for the file
-AudioProvider *CreatePCMAudioProvider(const wxString &filename);
+AudioProvider *CreatePCMAudioProvider(agi::fs::path const& filename);
diff --git a/aegisub/src/auto4_base.cpp b/aegisub/src/auto4_base.cpp
index 9345fb1e4344efa9ce0583a24139560852aa8a40..455f2576a4d35ff479402b3eb81ff753dc1d0c80 100644
--- a/aegisub/src/auto4_base.cpp
+++ b/aegisub/src/auto4_base.cpp
@@ -36,35 +36,9 @@
 
 #include "auto4_base.h"
 
-#ifdef __WINDOWS__
-#include <tchar.h>
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-#include <libaegisub/charset_conv_win.h>
-#endif
-
-#include <wx/button.h>
-#include <wx/dcmemory.h>
-#include <wx/dir.h>
-#include <wx/filefn.h>
-#include <wx/filename.h>
-#include <wx/log.h>
-#include <wx/msgdlg.h>
-#include <wx/thread.h>
-#include <wx/tokenzr.h>
-
-#include <tuple>
-
-#ifndef __WINDOWS__
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#endif
-
 #include "ass_file.h"
 #include "ass_style.h"
 #include "command/command.h"
-#include "compat.h"
 #include "dialog_progress.h"
 #include "include/aegisub/context.h"
 #include "options.h"
@@ -73,8 +47,28 @@
 #include "subtitle_format.h"
 #include "utils.h"
 
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/path.h>
+
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/format.hpp>
+#include <boost/tokenizer.hpp>
+
+#include <wx/dcmemory.h>
+#include <wx/log.h>
+#include <wx/sizer.h>
+#include <wx/msgdlg.h>
+
+#ifdef __WINDOWS__
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <libaegisub/charset_conv_win.h>
+#endif
+
 namespace Automation4 {
-	bool CalculateTextExtents(AssStyle *style, wxString const& text, double &width, double &height, double &descent, double &extlead)
+	bool CalculateTextExtents(AssStyle *style, std::string const& text, double &width, double &height, double &descent, double &extlead)
 	{
 		width = height = descent = extlead = 0;
 
@@ -105,27 +99,22 @@ namespace Automation4 {
 		if (!thefont) return false;
 		SelectObject(thedc, thefont);
 
+		std::wstring wtext(agi::charset::ConvertW(text));
 		SIZE sz;
-		size_t thetextlen = text.length();
-		const TCHAR *thetext = text.wc_str();
 		if (spacing != 0 ) {
 			width = 0;
-			for (unsigned int i = 0; i < thetextlen; i++) {
-				GetTextExtentPoint32(thedc, &thetext[i], 1, &sz);
+			for (auto c : wtext) {
+				GetTextExtentPoint32(thedc, &c, 1, &sz);
 				width += sz.cx + spacing;
 				height = sz.cy;
 			}
-		} else {
-			GetTextExtentPoint32(thedc, thetext, (int)thetextlen, &sz);
+		}
+		else {
+			GetTextExtentPoint32(thedc, &wtext[0], (int)wtext.size(), &sz);
 			width = sz.cx;
 			height = sz.cy;
 		}
 
-		// HACKISH FIX! This seems to work, but why? It shouldn't be needed?!?
-		//fontsize = style->fontsize;
-		//width = (int)(width * fontsize/height + 0.5);
-		//height = (int)(fontsize + 0.5);
-
 		TEXTMETRIC tm;
 		GetTextMetrics(thedc, &tm);
 		descent = tm.tmDescent;
@@ -150,17 +139,18 @@ namespace Automation4 {
 			style->italic ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL,
 			style->bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL,
 			style->underline,
-			style->font,
+			to_wx(style->font),
 			wxFONTENCODING_SYSTEM); // FIXME! make sure to get the right encoding here, make some translation table between windows and wx encodings
 		thedc.SetFont(thefont);
 
+		wxString wtext(to_wx(text));
 		if (spacing) {
 			// If there's inter-character spacing, kerning info must not be used, so calculate width per character
 			// NOTE: Is kerning actually done either way?!
-			for (unsigned int i = 0; i < text.length(); i++) {
+			for (auto const& wc : wtext) {
 				int a, b, c, d;
-				thedc.GetTextExtent(text[i], &a, &b, &c, &d);
-				double scaling = fontsize / (double)(b>0?b:1); // semi-workaround for missing OS/2 table data for scaling
+				thedc.GetTextExtent(wc, &a, &b, &c, &d);
+				double scaling = fontsize / (double)(b > 0 ? b : 1); // semi-workaround for missing OS/2 table data for scaling
 				width += (a + spacing)*scaling;
 				height = b > height ? b*scaling : height;
 				descent = c > descent ? c*scaling : descent;
@@ -169,8 +159,8 @@ namespace Automation4 {
 		} else {
 			// If the inter-character spacing should be zero, kerning info can (and must) be used, so calculate everything in one go
 			wxCoord lwidth, lheight, ldescent, lextlead;
-			thedc.GetTextExtent(text, &lwidth, &lheight, &ldescent, &lextlead);
-			double scaling = fontsize / (double)(lheight>0?lheight:1); // semi-workaround for missing OS/2 table data for scaling
+			thedc.GetTextExtent(wtext, &lwidth, &lheight, &ldescent, &lextlead);
+			double scaling = fontsize / (double)(lheight > 0 ? lheight : 1); // semi-workaround for missing OS/2 table data for scaling
 			width = lwidth*scaling; height = lheight*scaling; descent = ldescent*scaling; extlead = lextlead*scaling;
 		}
 #endif
@@ -184,7 +174,7 @@ namespace Automation4 {
 		return true;
 	}
 
-	ExportFilter::ExportFilter(wxString const& name, wxString const& description, int priority)
+	ExportFilter::ExportFilter(std::string const& name, std::string const& description, int priority)
 	: AssExportFilter(name, description, priority)
 	{
 		AssExportFilterChain::Register(this);
@@ -195,16 +185,16 @@ namespace Automation4 {
 		AssExportFilterChain::Unregister(this);
 	}
 
-	wxString ExportFilter::GetScriptSettingsIdentifier()
+	std::string ExportFilter::GetScriptSettingsIdentifier()
 	{
-		return inline_string_encode(wxString::Format("Automation Settings %s", GetName()));
+		return inline_string_encode("Automation Settings " + GetName());
 	}
 
 	wxWindow* ExportFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) {
 		config_dialog.reset(GenerateConfigDialog(parent, c));
 
 		if (config_dialog) {
-			wxString val = c->ass->GetScriptInfo(GetScriptSettingsIdentifier());
+			std::string val = c->ass->GetScriptInfo(GetScriptSettingsIdentifier());
 			if (!val.empty())
 				config_dialog->Unserialise(val);
 			return config_dialog->CreateWindow(parent);
@@ -215,7 +205,7 @@ namespace Automation4 {
 
 	void ExportFilter::LoadSettings(bool is_default, agi::Context *c) {
 		if (config_dialog) {
-			wxString val = config_dialog->Serialise();
+			std::string val = config_dialog->Serialise();
 			if (!val.empty())
 				c->ass->SetScriptInfo(GetScriptSettingsIdentifier(), val);
 		}
@@ -231,10 +221,10 @@ namespace Automation4 {
 
 	void ProgressSink::ShowDialog(ScriptDialog *config_dialog)
 	{
-		InvokeOnMainThread([=] {
+		agi::dispatch::Main().Sync([=] {
 			wxDialog w; // container dialog box
 			w.SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
-			w.Create(bsr->GetParentWindow(), -1, bsr->GetTitle());
+			w.Create(bsr->GetParentWindow(), -1, to_wx(bsr->GetTitle()));
 			wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL); // sizer for putting contents in
 			wxWindow *ww = config_dialog->CreateWindow(&w); // generate actual dialog contents
 			s->Add(ww, 0, wxALL, 5); // add contents to dialog
@@ -247,12 +237,12 @@ namespace Automation4 {
 	int ProgressSink::ShowDialog(wxDialog *dialog)
 	{
 		int ret = 0;
-		InvokeOnMainThread([&] { ret = dialog->ShowModal(); });
+		agi::dispatch::Main().Sync([&] { ret = dialog->ShowModal(); });
 		return ret;
 	}
 
-	BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, wxString const& title)
-	: impl(new DialogProgress(parent, title))
+	BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, std::string const& title)
+	: impl(new DialogProgress(parent, to_wx(title)))
 	{
 	}
 
@@ -279,26 +269,23 @@ namespace Automation4 {
 		return impl.get();
 	}
 
-	wxString BackgroundScriptRunner::GetTitle() const
+	std::string BackgroundScriptRunner::GetTitle() const
 	{
-		return impl->GetTitle();
+		return from_wx(impl->GetTitle());
 	}
 
 	// Script
-	Script::Script(wxString const& filename)
+	Script::Script(agi::fs::path const& filename)
 	: filename(filename)
 	{
-		// copied from auto3
-		include_path.clear();
-		include_path.EnsureFileAccessible(filename);
-		wxStringTokenizer toker(to_wx(OPT_GET("Path/Automation/Include")->GetString()), "|", wxTOKEN_STRTOK);
-		while (toker.HasMoreTokens()) {
-			// todo? make some error reporting here
-			wxFileName path(StandardPaths::DecodePath(toker.GetNextToken()));
-			if (!path.IsOk()) continue;
-			if (path.IsRelative()) continue;
-			if (!path.DirExists()) continue;
-			include_path.Add(path.GetLongPath());
+		include_path.emplace_back(filename.parent_path());
+
+		std::string include_paths = OPT_GET("Path/Automation/Include")->GetString();
+		boost::char_separator<char> sep("|");
+		for (auto const& tok : boost::tokenizer<boost::char_separator<char>>(include_paths, sep)) {
+			auto path = StandardPaths::DecodePath(tok);
+			if (path.is_absolute() && agi::fs::DirectoryExists(path))
+				include_path.emplace_back(std::move(path));
 		}
 	}
 
@@ -352,9 +339,8 @@ namespace Automation4 {
 		return macros;
 	}
 
-
 	// AutoloadScriptManager
-	AutoloadScriptManager::AutoloadScriptManager(wxString const& path)
+	AutoloadScriptManager::AutoloadScriptManager(std::string const& path)
 	: path(path)
 	{
 		Reload();
@@ -366,31 +352,17 @@ namespace Automation4 {
 
 		int error_count = 0;
 
-		wxStringTokenizer tok(path, "|", wxTOKEN_STRTOK);
-		while (tok.HasMoreTokens()) {
-			wxDir dir;
-			wxString dirname = StandardPaths::DecodePath(tok.GetNextToken());
-			if (!dir.Exists(dirname)) {
-				//wxLogWarning("A directory was specified in the Automation autoload path, but it doesn't exist: %s", dirname.c_str());
-				continue;
-			}
-			if (!dir.Open(dirname)) {
-				//wxLogWarning("Failed to open a directory in the Automation autoload path: %s", dirname.c_str());
-				continue;
-			}
+		boost::char_separator<char> sep("|");
+		for (auto const& tok : boost::tokenizer<boost::char_separator<char>>(path, sep)) {
+			auto dirname = StandardPaths::DecodePath(tok);
+			if (!agi::fs::DirectoryExists(dirname)) continue;
 
-			wxString fn;
-			wxFileName script_path(dirname + "/", "");
-			bool more = dir.GetFirst(&fn, wxEmptyString, wxDIR_FILES);
-			while (more) {
-				script_path.SetName(fn);
-				wxString fullpath = script_path.GetFullPath();
-				if (ScriptFactory::CanHandleScriptFormat(fullpath)) {
-					Script *s = ScriptFactory::CreateFromFile(fullpath, true);
+			for (auto filename : agi::fs::DirectoryIterator(dirname, "*.*")) {
+				Script *s = ScriptFactory::CreateFromFile(dirname/filename, true);
+				if (s) {
 					scripts.push_back(s);
-					if (!s->GetLoadedState()) error_count++;
+					if (!s->GetLoadedState()) ++error_count;
 				}
-				more = dir.GetNext(&fn);
 			}
 		}
 
@@ -415,39 +387,37 @@ namespace Automation4 {
 	{
 		delete_clear(scripts);
 
-		wxString local_scripts = context->ass->GetScriptInfo("Automation Scripts");
+		auto local_scripts = context->ass->GetScriptInfo("Automation Scripts");
 		if (local_scripts.empty()) {
 			ScriptsChanged();
 			return;
 		}
 
-		wxStringTokenizer tok(local_scripts, "|", wxTOKEN_STRTOK);
-		wxFileName assfn(context->ass->filename);
-		wxString autobasefn(to_wx(OPT_GET("Path/Automation/Base")->GetString()));
-		while (tok.HasMoreTokens()) {
-			wxString trimmed = tok.GetNextToken().Trim(true).Trim(false);
+		auto autobasefn(OPT_GET("Path/Automation/Base")->GetString());
+
+		boost::char_separator<char> sep("|");
+		for (auto const& cur : boost::tokenizer<boost::char_separator<char>>(local_scripts, sep)) {
+			auto trimmed = boost::trim_copy(cur);
 			char first_char = trimmed[0];
-			trimmed.Remove(0, 1);
+			trimmed.erase(0);
 
-			wxString basepath;
+			agi::fs::path basepath;
 			if (first_char == '~') {
-				basepath = assfn.GetPath();
+				basepath = context->ass->filename.parent_path();
 			} else if (first_char == '$') {
 				basepath = autobasefn;
 			} else if (first_char == '/') {
 			} else {
 				wxLogWarning("Automation Script referenced with unknown location specifier character.\nLocation specifier found: %c\nFilename specified: %s",
-					first_char, trimmed);
+					first_char, to_wx(trimmed));
 				continue;
 			}
-			wxFileName sfname(trimmed);
-			sfname.MakeAbsolute(basepath);
-			if (sfname.FileExists()) {
-				scripts.push_back(Automation4::ScriptFactory::CreateFromFile(sfname.GetFullPath(), true));
-			}
+			auto sfname = basepath/trimmed;
+			if (agi::fs::FileExists(sfname))
+				scripts.push_back(Automation4::ScriptFactory::CreateFromFile(sfname, true));
 			else {
 				wxLogWarning("Automation Script referenced could not be found.\nFilename specified: %c%s\nSearched relative to: %s\nResolved filename: %s",
-					first_char, trimmed, basepath, sfname.GetFullPath());
+					first_char, to_wx(trimmed), basepath.wstring(), sfname.wstring());
 			}
 		}
 
@@ -462,24 +432,23 @@ namespace Automation4 {
 		// 2. Otherwise try making it relative to the ass filename
 		// 3. If step 2 failed, or absolute path is shorter than path relative to ass, use absolute path ("/")
 		// 4. Otherwise, use path relative to ass ("~")
-		wxString scripts_string;
-		wxString autobasefn(to_wx(OPT_GET("Path/Automation/Base")->GetString()));
+		std::string scripts_string;
+		agi::fs::path autobasefn(OPT_GET("Path/Automation/Base")->GetString());
 
 		for (auto script : GetScripts()) {
 			if (!scripts_string.empty())
 				scripts_string += "|";
 
-			wxString autobase_rel, assfile_rel;
-			wxString scriptfn(script->GetFilename());
-			autobase_rel = MakeRelativePath(scriptfn, autobasefn);
-			assfile_rel = MakeRelativePath(scriptfn, context->ass->filename);
+			auto scriptfn(script->GetFilename().string());
+			auto autobase_rel = config::path->MakeRelative(scriptfn, autobasefn);
+			auto assfile_rel = config::path->MakeRelative(scriptfn, "?script");
 
-			if (autobase_rel.size() <= scriptfn.size() && autobase_rel.size() <= assfile_rel.size()) {
-				scriptfn = "$" + autobase_rel;
-			} else if (assfile_rel.size() <= scriptfn.size() && assfile_rel.size() <= autobase_rel.size()) {
-				scriptfn = "~" + assfile_rel;
+			if (autobase_rel.string().size() <= scriptfn.size() && autobase_rel.string().size() <= assfile_rel.string().size()) {
+				scriptfn = "$" + autobase_rel.generic_string();
+			} else if (assfile_rel.string().size() <= scriptfn.size() && assfile_rel.string().size() <= autobase_rel.string().size()) {
+				scriptfn = "~" + assfile_rel.generic_string();
 			} else {
-				scriptfn = "/" + wxFileName(scriptfn).GetFullPath(wxPATH_UNIX);
+				scriptfn = "/" + script->GetFilename().generic_string();
 			}
 
 			scripts_string += scriptfn;
@@ -488,7 +457,7 @@ namespace Automation4 {
 	}
 
 	// ScriptFactory
-	ScriptFactory::ScriptFactory(wxString engine_name, wxString filename_pattern)
+	ScriptFactory::ScriptFactory(std::string const& engine_name, std::string const& filename_pattern)
 	: engine_name(engine_name)
 	, filename_pattern(filename_pattern)
 	{
@@ -511,32 +480,23 @@ namespace Automation4 {
 		}
 	}
 
-	Script* ScriptFactory::CreateFromFile(wxString const& filename, bool log_errors)
+	Script* ScriptFactory::CreateFromFile(agi::fs::path const& filename, bool log_errors, bool create_unknown)
 	{
 		for (auto factory : Factories()) {
 			Script *s = factory->Produce(filename);
 			if (s) {
 				if (!s->GetLoadedState() && log_errors) {
-					wxLogError(_("An Automation script failed to load. File name: '%s', error reported: %s"), filename, s->GetDescription());
+					wxLogError(_("An Automation script failed to load. File name: '%s', error reported: %s"), filename.wstring(), s->GetDescription());
 				}
 				return s;
 			}
 		}
 
 		if (log_errors) {
-			wxLogError(_("The file was not recognised as an Automation script: %s"), filename);
+			wxLogError(_("The file was not recognised as an Automation script: %s"), filename.wstring());
 		}
 
-
-		return new UnknownScript(filename);
-	}
-
-	bool ScriptFactory::CanHandleScriptFormat(wxString const& filename)
-	{
-		using std::placeholders::_1;
-		// Just make this always return true to bitch about unknown script formats in autoload
-		return any_of(Factories().begin(), Factories().end(),
-			[&](ScriptFactory *sf) { return filename.Matches(sf->GetFilenamePattern()); });
+		return create_unknown ? new UnknownScript(filename) : nullptr;
 	}
 
 	std::vector<ScriptFactory*>& ScriptFactory::Factories()
@@ -550,30 +510,24 @@ namespace Automation4 {
 		return Factories();
 	}
 
-	wxString ScriptFactory::GetWildcardStr()
+	std::string ScriptFactory::GetWildcardStr()
 	{
-		wxString fnfilter, catchall;
+		std::string fnfilter, catchall;
 		for (auto fact : Factories()) {
 			if (fact->GetEngineName().empty() || fact->GetFilenamePattern().empty())
 				continue;
 
-			fnfilter = wxString::Format("%s%s scripts (%s)|%s|", fnfilter, fact->GetEngineName(), fact->GetFilenamePattern(), fact->GetFilenamePattern());
+			fnfilter += str(boost::format("%s scripts (%s)|%s|") % fact->GetEngineName() % fact->GetFilenamePattern() % fact->GetFilenamePattern());
 			catchall += fact->GetFilenamePattern() + ";";
 		}
-		fnfilter += _("All Files") + " (*.*)|*.*";
+		fnfilter += from_wx(_("All Files")) + " (*.*)|*.*";
 
 		if (!catchall.empty())
-			catchall.RemoveLast();
+			catchall.pop_back();
 
 		if (Factories().size() > 1)
-			fnfilter = _("All Supported Formats") + "|" + catchall + "|" + fnfilter;
+			fnfilter = from_wx(_("All Supported Formats")) + "|" + catchall + "|" + fnfilter;
 
 		return fnfilter;
 	}
-
-	// UnknownScript
-	UnknownScript::UnknownScript(wxString const& filename)
-	: Script(filename)
-	{
-	}
 }
diff --git a/aegisub/src/auto4_base.h b/aegisub/src/auto4_base.h
index c63c1960abc86c57e080f1f8888b32da8162dbaf..42cf2dad130ebfe811a680df76549c51dbcb8ac9 100644
--- a/aegisub/src/auto4_base.h
+++ b/aegisub/src/auto4_base.h
@@ -34,24 +34,20 @@
 
 #pragma once
 
-#include <deque>
-#include <vector>
-
-#include <wx/dialog.h>
-#include <wx/filename.h>
-#include <wx/gauge.h>
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-#include <wx/string.h>
-#include <wx/textctrl.h>
-#include <wx/timer.h>
-
 #include <libaegisub/background_runner.h>
 #include <libaegisub/exception.h>
-#include <libaegisub/scoped_ptr.h>
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/signal.h>
 
 #include "ass_export_filter.h"
+#include "compat.h"
+
+#include <boost/filesystem/path.hpp>
+#include <deque>
+#include <memory>
+#include <vector>
+
+#include <wx/dialog.h>
 
 class AssFile;
 class AssStyle;
@@ -59,8 +55,6 @@ class DialogProgress;
 class SubtitleFormat;
 class wxWindow;
 class wxDialog;
-class wxStopWatch;
-class wxPathList;
 
 namespace agi { struct Context; }
 namespace cmd { class Command; }
@@ -71,21 +65,21 @@ namespace Automation4 {
 	DEFINE_SIMPLE_EXCEPTION_NOINNER(MacroRunError, AutomationError, "automation/macro/generic")
 
 	// Calculate the extents of a text string given a style
-	bool CalculateTextExtents(AssStyle *style, wxString const& text, double &width, double &height, double &descent, double &extlead);
+	bool CalculateTextExtents(AssStyle *style, std::string const& text, double &width, double &height, double &descent, double &extlead);
 
 	class ScriptDialog;
 
 	class ExportFilter : public AssExportFilter {
-		agi::scoped_ptr<ScriptDialog> config_dialog;
+		std::unique_ptr<ScriptDialog> config_dialog;
 
 		/// subclasses should implement this, producing a new ScriptDialog
 		virtual ScriptDialog* GenerateConfigDialog(wxWindow *parent, agi::Context *c) = 0;
 
 	protected:
-		wxString GetScriptSettingsIdentifier();
+		std::string GetScriptSettingsIdentifier();
 
 	public:
-		ExportFilter(wxString const& name, wxString const& description, int priority);
+		ExportFilter(std::string const& name, std::string const& description, int priority);
 		virtual ~ExportFilter();
 
 		wxWindow* GetConfigDialogWindow(wxWindow *parent, agi::Context *c);
@@ -106,25 +100,25 @@ namespace Automation4 {
 
 		/// Serialize the values of the controls in this dialog to a string
 		/// suitable for storage in the subtitle script
-		virtual wxString Serialise() { return ""; }
+		virtual std::string Serialise() { return ""; }
 
 		/// Restore the values of the controls in this dialog from a string
 		/// stored in the subtitle script
-		virtual void Unserialise(wxString const& serialised) { }
+		virtual void Unserialise(std::string const& serialised) { }
 	};
 
 	class ProgressSink;
 
 	class BackgroundScriptRunner {
-		agi::scoped_ptr<DialogProgress> impl;
+		std::unique_ptr<DialogProgress> impl;
 
 	public:
 		wxWindow *GetParentWindow() const;
-		wxString GetTitle() const;
+		std::string GetTitle() const;
 
 		void Run(std::function<void(ProgressSink*)> task);
 
-		BackgroundScriptRunner(wxWindow *parent, wxString const& title);
+		BackgroundScriptRunner(wxWindow *parent, std::string const& title);
 		~BackgroundScriptRunner();
 	};
 
@@ -155,13 +149,13 @@ namespace Automation4 {
 	};
 
 	class Script {
-		wxString filename;
+		agi::fs::path filename;
 
 	protected:
 		/// The automation include path, consisting of the user-specified paths
 		/// along with the script's path
-		wxPathList include_path;
-		Script(wxString const& filename);
+		std::vector<agi::fs::path> include_path;
+		Script(agi::fs::path const& filename);
 
 	public:
 		virtual ~Script() { }
@@ -170,17 +164,17 @@ namespace Automation4 {
 		virtual void Reload() = 0;
 
 		/// The script's file name with path
-		wxString GetFilename() const { return filename; }
+		agi::fs::path GetFilename() const { return filename; }
 		/// The script's file name without path
-		wxString GetPrettyFilename() const { return wxFileName(filename).GetFullName(); }
+		agi::fs::path GetPrettyFilename() const { return filename.filename(); }
 		/// The script's name. Not required to be unique.
-		virtual wxString GetName() const=0;
+		virtual std::string GetName() const=0;
 		/// A short description of the script
-		virtual wxString GetDescription() const=0;
+		virtual std::string GetDescription() const=0;
 		/// The author of the script
-		virtual wxString GetAuthor() const=0;
+		virtual std::string GetAuthor() const=0;
 		/// A version string that should not be used for anything but display
-		virtual wxString GetVersion() const=0;
+		virtual std::string GetVersion() const=0;
 		/// Did the script load correctly?
 		virtual bool GetLoadedState() const=0;
 
@@ -237,17 +231,17 @@ namespace Automation4 {
 
 	/// Manager for scripts in the autoload directory
 	class AutoloadScriptManager : public ScriptManager {
-		wxString path;
+		std::string path;
 	public:
-		AutoloadScriptManager(wxString const& path);
+		AutoloadScriptManager(std::string const& path);
 		void Reload();
 	};
 
 	/// Both a base class for script factories and a manager of registered
 	/// script factories
 	class ScriptFactory {
-		wxString engine_name;
-		wxString filename_pattern;
+		std::string engine_name;
+		std::string filename_pattern;
 
 		/// Load a file, or return nullptr if the file is not in a supported
 		/// format. If the file is in a supported format but is invalid, a
@@ -256,35 +250,33 @@ namespace Automation4 {
 		///
 		/// This is private as it should only ever be called through
 		/// CreateFromFile
-		virtual Script* Produce(wxString const& filename) const = 0;
+		virtual Script* Produce(agi::fs::path const& filename) const = 0;
 
 		static inline std::vector<ScriptFactory*>& Factories();
 
 	protected:
-		ScriptFactory(wxString engine_name, wxString filename_pattern);
+		ScriptFactory(std::string const& engine_name, std::string const& filename_pattern);
 		virtual ~ScriptFactory() { }
 
 	public:
 		/// Name of this automation engine
-		const wxString& GetEngineName() const { return engine_name; }
+		const std::string& GetEngineName() const { return engine_name; }
 		/// Extension which this engine supports
-		const wxString& GetFilenamePattern() const { return filename_pattern; }
+		const std::string& GetFilenamePattern() const { return filename_pattern; }
 
 		/// Register an automation engine. Calling code retains ownership of pointer
 		static void Register(ScriptFactory *factory);
 		/// Unregister and delete an automation engine
 		static void Unregister(ScriptFactory *factory);
-		/// Is there an automation engine registered which can open the file?
-		static bool CanHandleScriptFormat(wxString const& filename);
 
 		/// Get the full wildcard string for all loaded engines
-		static wxString GetWildcardStr();
+		static std::string GetWildcardStr();
 
 		/// Load a script from a file
 		/// @param filename Script to load
 		/// @param log_errors Should load errors be displayed?
-		/// @return Always returns a valid Script, even if no engine could load the file
-		static Script* CreateFromFile(wxString const& filename, bool log_errors);
+		/// @param create_unknown Create a placeholder rather than returning nullptr if no script engine supports the file
+		static Script* CreateFromFile(agi::fs::path const& filename, bool log_errors, bool create_unknown=true);
 
 		static const std::vector<ScriptFactory*>& GetFactories();
 	};
@@ -293,14 +285,14 @@ namespace Automation4 {
 	/// automation engines
 	class UnknownScript : public Script {
 	public:
-		UnknownScript(wxString const& filename);
+		UnknownScript(agi::fs::path const& filename) : Script(filename) { }
 
 		void Reload() { }
 
-		wxString GetName() const { return wxFileName(GetFilename()).GetName(); }
-		wxString GetDescription() const { return _("File was not recognized as a script"); }
-		wxString GetAuthor() const { return ""; }
-		wxString GetVersion() const { return ""; }
+		std::string GetName() const { return GetFilename().stem().string(); }
+		std::string GetDescription() const { return from_wx(_("File was not recognized as a script")); }
+		std::string GetAuthor() const { return ""; }
+		std::string GetVersion() const { return ""; }
 		bool GetLoadedState() const { return false; }
 
 		std::vector<cmd::Command*> GetMacros() const { return std::vector<cmd::Command*>(); }
diff --git a/aegisub/src/auto4_lua.cpp b/aegisub/src/auto4_lua.cpp
index a12b975387f5aead8fb6e17bdc6f7bc41092ff8a..56695752493449d891f2306e2887d63823b8742d 100644
--- a/aegisub/src/auto4_lua.cpp
+++ b/aegisub/src/auto4_lua.cpp
@@ -38,24 +38,6 @@
 
 #include "auto4_lua.h"
 
-#include <cassert>
-#include <cstdint>
-
-#include <algorithm>
-
-#include <wx/clipbrd.h>
-#include <wx/filefn.h>
-#include <wx/filename.h>
-#include <wx/log.h>
-#include <wx/msgdlg.h>
-#include <wx/regex.h>
-#include <wx/tokenzr.h>
-#include <wx/window.h>
-
-#include <libaegisub/access.h>
-#include <libaegisub/log.h>
-#include <libaegisub/scoped_ptr.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
@@ -69,6 +51,28 @@
 #include "video_context.h"
 #include "utils.h"
 
+#include <libaegisub/access.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
+#include <libaegisub/scoped_ptr.h>
+
+#include <algorithm>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/format.hpp>
+#include <boost/tokenizer.hpp>
+#include <cassert>
+#include <cstdint>
+
+#include <wx/clipbrd.h>
+#include <wx/filefn.h>
+#include <wx/filename.h>
+#include <wx/log.h>
+#include <wx/msgdlg.h>
+#include <wx/regex.h>
+#include <wx/window.h>
+
 // This must be below the headers above.
 #ifdef __WINDOWS__
 #include "../../contrib/lua51/src/lualib.h"
@@ -78,21 +82,34 @@
 #endif
 
 namespace {
-	inline void push_value(lua_State *L, lua_CFunction fn)
-	{
+	inline void push_value(lua_State *L, lua_CFunction fn) {
 		lua_pushcfunction(L, fn);
 	}
 
-	inline void push_value(lua_State *L, int n)
-	{
+	inline void push_value(lua_State *L, int n) {
 		lua_pushinteger(L, n);
 	}
 
-	inline void push_value(lua_State *L, void *p)
-	{
+	inline void push_value(lua_State *L, void *p) {
 		lua_pushlightuserdata(L, p);
 	}
 
+	inline void push_value(lua_State *L, agi::fs::path const& p) {
+		lua_pushstring(L, p.string().c_str());
+	}
+
+	inline void push_value(lua_State *L, wxString const& s) {
+		lua_pushstring(L, s.utf8_str());
+	}
+
+	inline void push_value(lua_State *L, std::string const& s) {
+		lua_pushstring(L, s.c_str());
+	}
+
+	inline void push_value(lua_State *L, const char *s) {
+		lua_pushstring(L, s);
+	}
+
 	template<class T>
 	inline void set_field(lua_State *L, const char *name, T value)
 	{
@@ -102,20 +119,20 @@ namespace {
 
 	inline wxString get_wxstring(lua_State *L, int idx)
 	{
-		return wxString(lua_tostring(L, idx), wxConvUTF8);
+		return wxString::FromUTF8(lua_tostring(L, idx));
 	}
 
 	inline wxString check_wxstring(lua_State *L, int idx)
 	{
-		return wxString(luaL_checkstring(L, idx), wxConvUTF8);
+		return wxString::FromUTF8(luaL_checkstring(L, idx));
 	}
 
-	wxString get_global_string(lua_State *L, const char *name)
+	std::string get_global_string(lua_State *L, const char *name)
 	{
 		lua_getglobal(L, name);
-		wxString ret;
+		std::string ret;
 		if (lua_isstring(L, -1))
-			ret = get_wxstring(L, -1);
+			ret = lua_tostring(L, -1);
 		lua_pop(L, 1);
 		return ret;
 	}
@@ -123,7 +140,7 @@ namespace {
 	void set_context(lua_State *L, const agi::Context *c)
 	{
 		// Explicit cast is needed to discard the const
-		lua_pushlightuserdata(L, (void *)c);
+		push_value(L, (void *)c);
 		lua_setfield(L, LUA_REGISTRYINDEX, "project_context");
 	}
 
@@ -142,8 +159,8 @@ namespace {
 	int get_file_name(lua_State *L)
 	{
 		const agi::Context *c = get_context(L);
-		if (c && c->ass->filename.size())
-			lua_pushstring(L, wxFileName(c->ass->filename).GetFullName().utf8_str());
+		if (c && !c->ass->filename.empty())
+			push_value(L, c->ass->filename.filename());
 		else
 			lua_pushnil(L);
 		return 1;
@@ -152,7 +169,7 @@ namespace {
 	int get_translation(lua_State *L)
 	{
 		wxString str(check_wxstring(L, 1));
-		lua_pushstring(L, _(str).utf8_str());
+		push_value(L, _(str));
 		return 1;
 	}
 
@@ -171,9 +188,9 @@ namespace {
 	{
 		wxRegEx *re = get_regex(L);
 		if (re->Matches(check_wxstring(L, 2)))
-			lua_pushinteger(L, re->GetMatchCount());
+			push_value(L, re->GetMatchCount());
 		else
-			lua_pushinteger(L, 0);
+			push_value(L, 0);
 		return 1;
 	}
 
@@ -191,8 +208,8 @@ namespace {
 		wxString str(check_wxstring(L, 2));
 		size_t start, len;
 		get_regex(L)->GetMatch(&start, &len, luaL_checkinteger(L, 3));
-		lua_pushinteger(L, utf8_len(str.Left(start)) + 1);
-		lua_pushinteger(L, utf8_len(str.Left(start + len)));
+		push_value(L, utf8_len(str.Left(start)) + 1);
+		push_value(L, utf8_len(str.Left(start + len)));
 		return 2;
 	}
 
@@ -200,8 +217,8 @@ namespace {
 	{
 		wxString str(check_wxstring(L, 3));
 		int reps = get_regex(L)->Replace(&str, check_wxstring(L, 2), luaL_checkinteger(L, 4));
-		lua_pushstring(L, str.utf8_str());
-		lua_pushinteger(L, reps);
+		push_value(L, str);
+		push_value(L, reps);
 		return 2;
 	}
 
@@ -236,13 +253,13 @@ namespace {
 		int nargs = lua_gettop(L);
 		for (int i = 1; i <= nargs; ++i) {
 			if (!lua_islightuserdata(L, i)) {
-				lua_pushstring(L, "Flags must follow all non-flag arguments");
+				push_value(L, "Flags must follow all non-flag arguments");
 				return 1;
 			}
 			ret |= (int)(intptr_t)lua_touserdata(L, i);
 		}
 
-		lua_pushinteger(L, ret);
+		push_value(L, ret);
 		return 1;
 	}
 
@@ -277,17 +294,17 @@ namespace {
 
 	int clipboard_get(lua_State *L)
 	{
-		wxString data = GetClipboard();
-		if (!data)
+		std::string data = GetClipboard();
+		if (data.empty())
 			lua_pushnil(L);
 		else
-			lua_pushstring(L, data.utf8_str());
+			push_value(L, data);
 		return 1;
 	}
 
 	int clipboard_set(lua_State *L)
 	{
-		wxString str(check_wxstring(L, 1));
+		std::string str(luaL_checkstring(L, 1));
 
 		bool succeeded = false;
 
@@ -300,7 +317,7 @@ namespace {
 		wxClipboard *theCB = wxTheClipboard;
 #endif
 		if (theCB->Open()) {
-			succeeded = theCB->SetData(new wxTextDataObject(str));
+			succeeded = theCB->SetData(new wxTextDataObject(to_wx(str)));
 			theCB->Close();
 			theCB->Flush();
 		}
@@ -338,7 +355,7 @@ namespace {
 			LOG_D("automation/lua/stackdump") << "--- dumping lua stack...";
 			for (int i = top; i > 0; i--) {
 				lua_pushvalue(L, i);
-				wxString type(lua_typename(L, lua_type(L, -1)), wxConvUTF8);
+				std::string type(lua_typename(L, lua_type(L, -1)));
 				if (lua_isstring(L, i)) {
 					LOG_D("automation/lua/stackdump") << type << ": " << lua_tostring(L, -1);
 				} else {
@@ -361,7 +378,7 @@ namespace {
 
 namespace Automation4 {
 	// LuaScript
-	LuaScript::LuaScript(wxString const& filename)
+	LuaScript::LuaScript(agi::fs::path const& filename)
 	: Script(filename)
 	, L(0)
 	{
@@ -383,13 +400,13 @@ namespace Automation4 {
 			LuaStackcheck _stackcheck(L);
 
 			// register standard libs
-			lua_pushcfunction(L, luaopen_base); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_package); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_string); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_table); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_math); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_io); lua_call(L, 0, 0);
-			lua_pushcfunction(L, luaopen_os); lua_call(L, 0, 0);
+			push_value(L, luaopen_base); lua_call(L, 0, 0);
+			push_value(L, luaopen_package); lua_call(L, 0, 0);
+			push_value(L, luaopen_string); lua_call(L, 0, 0);
+			push_value(L, luaopen_table); lua_call(L, 0, 0);
+			push_value(L, luaopen_math); lua_call(L, 0, 0);
+			push_value(L, luaopen_io); lua_call(L, 0, 0);
+			push_value(L, luaopen_os); lua_call(L, 0, 0);
 			_stackcheck.check_stack(0);
 
 			// dofile and loadfile are replaced with include
@@ -397,18 +414,17 @@ namespace Automation4 {
 			lua_setglobal(L, "dofile");
 			lua_pushnil(L);
 			lua_setglobal(L, "loadfile");
-			lua_pushcfunction(L, LuaInclude);
+			push_value(L, LuaInclude);
 			lua_setglobal(L, "include");
 
 			// add include_path to the module load path
 			lua_getglobal(L, "package");
-			lua_pushstring(L, "path");
-			lua_pushstring(L, "path");
+			push_value(L, "path");
+			push_value(L, "path");
 			lua_gettable(L, -3);
 
-			for (wxString const& path : include_path) {
-				wxCharBuffer p = path.utf8_str();
-				lua_pushfstring(L, ";%s/?.lua;%s/?/init.lua", p.data(), p.data());
+			for (auto const& path : include_path) {
+				lua_pushfstring(L, ";%s/?.lua;%s/?/init.lua", path.string().c_str(), path.string().c_str());
 				lua_concat(L, 2);
 			}
 
@@ -416,7 +432,7 @@ namespace Automation4 {
 
 			// Replace the default lua module loader with our unicode compatible one
 			lua_getfield(L, -1, "loaders");
-			lua_pushcfunction(L, LuaModuleLoader);
+			push_value(L, LuaModuleLoader);
 			lua_rawseti(L, -2, 2);
 			lua_pop(L, 2);
 			_stackcheck.check_stack(0);
@@ -424,12 +440,12 @@ namespace Automation4 {
 			// prepare stuff in the registry
 
 			// store the script's filename
-			lua_pushstring(L, wxFileName(GetFilename()).GetName().utf8_str().data());
+			push_value(L, GetFilename().stem());
 			lua_setfield(L, LUA_REGISTRYINDEX, "filename");
 			_stackcheck.check_stack(0);
 
 			// reference to the script object
-			lua_pushlightuserdata(L, this);
+			push_value(L, this);
 			lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
 			_stackcheck.check_stack(0);
 
@@ -458,10 +474,10 @@ namespace Automation4 {
 
 			// load user script
 			LuaScriptReader script_reader(GetFilename());
-			if (lua_load(L, script_reader.reader_func, &script_reader, GetPrettyFilename().utf8_str())) {
-				wxString err = wxString::Format("Error loading Lua script \"%s\":\n\n%s", GetPrettyFilename(), get_wxstring(L, -1));
+			if (lua_load(L, script_reader.reader_func, &script_reader, GetPrettyFilename().string().c_str())) {
+				std::string err = str(boost::format("Error loading Lua script \"%s\":\n\n%s") % GetPrettyFilename().string() % lua_tostring(L, -1));
 				lua_pop(L, 1);
-				throw ScriptLoadError(from_wx(err));
+				throw ScriptLoadError(err);
 			}
 			_stackcheck.check_stack(1);
 
@@ -470,9 +486,9 @@ namespace Automation4 {
 			// don't thread this, as there's no point in it and it seems to break on wx 2.8.3, for some reason
 			if (lua_pcall(L, 0, 0, 0)) {
 				// error occurred, assumed to be on top of Lua stack
-				wxString err = wxString::Format("Error initialising Lua script \"%s\":\n\n%s", GetPrettyFilename(), get_wxstring(L, -1));
+				std::string err = str(boost::format("Error initialising Lua script \"%s\":\n\n%s") % GetPrettyFilename().string() % lua_tostring(L, -1));
 				lua_pop(L, 1);
-				throw ScriptLoadError(from_wx(err));
+				throw ScriptLoadError(err);
 			}
 			_stackcheck.check_stack(0);
 
@@ -488,7 +504,7 @@ namespace Automation4 {
 			version = get_global_string(L, "script_version");
 
 			if (name.empty())
-				name = GetPrettyFilename();
+				name = GetPrettyFilename().string();
 
 			lua_pop(L, 1);
 			// if we got this far, the script should be ready
@@ -497,8 +513,8 @@ namespace Automation4 {
 		}
 		catch (agi::Exception const& e) {
 			Destroy();
-			name = GetPrettyFilename();
-			description = to_wx(e.GetChainedMessage());
+			name = GetPrettyFilename().string();
+			description = e.GetChainedMessage();
 		}
 	}
 
@@ -529,7 +545,7 @@ namespace Automation4 {
 			if (macro->name() == command->name()) {
 				luaL_error(L,
 					"A macro named '%s' is already defined in script '%s'",
-					command->StrDisplay(0).utf8_str().data(), name.utf8_str().data());
+					command->StrDisplay(0).utf8_str().data(), name.c_str());
 			}
 		}
 		macros.push_back(command);
@@ -566,13 +582,13 @@ namespace Automation4 {
 			return luaL_error(L, "Not a style entry");
 
 		double width, height, descent, extlead;
-		if (!CalculateTextExtents(st, check_wxstring(L, 2), width, height, descent, extlead))
+		if (!CalculateTextExtents(st, luaL_checkstring(L, 2), width, height, descent, extlead))
 			return luaL_error(L, "Some internal error occurred calculating text_extents");
 
-		lua_pushnumber(L, width);
-		lua_pushnumber(L, height);
-		lua_pushnumber(L, descent);
-		lua_pushnumber(L, extlead);
+		push_value(L, width);
+		push_value(L, height);
+		push_value(L, descent);
+		push_value(L, extlead);
 		return 4;
 	}
 
@@ -582,35 +598,33 @@ namespace Automation4 {
 	int LuaScript::LuaModuleLoader(lua_State *L)
 	{
 		int pretop = lua_gettop(L);
-		wxString module(get_wxstring(L, -1));
-		module.Replace(".", LUA_DIRSEP);
+		std::string module(lua_tostring(L, -1));
+		boost::replace_all(module, ".", LUA_DIRSEP);
 
 		// Get the lua package include path (which the user may have modified)
 		lua_getglobal(L, "package");
-		lua_pushstring(L, "path");
+		push_value(L, "path");
 		lua_gettable(L, -2);
-		wxString package_paths(get_wxstring(L, -1));
+		std::string package_paths(lua_tostring(L, -1));
 		lua_pop(L, 2);
 
-		wxStringTokenizer toker(package_paths, ";", wxTOKEN_STRTOK);
-		while (toker.HasMoreTokens()) {
-			wxString filename = toker.GetNextToken();
-			filename.Replace("?", module);
+		boost::char_separator<char> sep(";");
+		for (auto filename : boost::tokenizer<boost::char_separator<char>>(package_paths, sep)) {
+			boost::replace_all(filename, "?", module);
 			try {
 				LuaScriptReader script_reader(filename);
-				if (lua_load(L, script_reader.reader_func, &script_reader, filename.utf8_str())) {
-					return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.utf8_str().data(), lua_tostring(L, -1));
-				}
+				if (lua_load(L, script_reader.reader_func, &script_reader, filename.c_str()))
+					return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.c_str(), lua_tostring(L, -1));
 				break;
 			}
-			catch (agi::FileNotFoundError const&) {
+			catch (agi::fs::FileNotFound const&) {
 				// Not an error so swallow and continue on
 			}
-			catch (agi::acs::NotAFile const&) {
+			catch (agi::fs::NotAFile const&) {
 				// Not an error so swallow and continue on
 			}
 			catch (agi::Exception const& e) {
-				return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.utf8_str().data(), e.GetChainedMessage().c_str());
+				return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.c_str(), e.GetChainedMessage().c_str());
 			}
 		}
 
@@ -619,27 +633,28 @@ namespace Automation4 {
 
 	int LuaScript::LuaInclude(lua_State *L)
 	{
-		LuaScript *s = GetScriptObject(L);
-
-		wxString fnames(check_wxstring(L, 1));
-
-		wxFileName fname(fnames);
-		if (fname.GetDirCount() == 0) {
-			// filename only
-			fname = s->include_path.FindAbsoluteValidPath(fnames);
-		} else if (fname.IsRelative()) {
-			// relative path
-			wxFileName sfname(s->GetFilename());
-			fname.MakeAbsolute(sfname.GetPath(true));
-		} else {
-			// absolute path, do nothing
+		const LuaScript *s = GetScriptObject(L);
+
+		const std::string filename(luaL_checkstring(L, 1));
+		agi::fs::path filepath;
+
+		// Relative or absolute path
+		if (!boost::all(filename, !boost::is_any_of("/\\")))
+			filepath = s->GetFilename().parent_path()/filename;
+		else { // Plain filename
+			for (auto const& dir : s->include_path) {
+				filepath = dir/filename;
+				if (agi::fs::FileExists(filepath))
+					break;
+			}
 		}
-		if (!fname.IsOk() || !fname.FileExists())
-			return luaL_error(L, "Lua include not found: %s", fnames.utf8_str().data());
 
-		LuaScriptReader script_reader(fname.GetFullPath());
-		if (lua_load(L, script_reader.reader_func, &script_reader, fname.GetFullName().utf8_str()))
-			return luaL_error(L, "Error loading Lua include \"%s\":\n\n%s", fname.GetFullPath().utf8_str().data(), lua_tostring(L, -1));
+		if (!agi::fs::FileExists(filepath))
+			return luaL_error(L, "Lua include not found: %s", filename.c_str());
+
+		LuaScriptReader script_reader(filepath);
+		if (lua_load(L, script_reader.reader_func, &script_reader, filename.c_str()))
+			return luaL_error(L, "Error loading Lua include \"%s\":\n\n%s", filename.c_str(), lua_tostring(L, -1));
 
 		int pretop = lua_gettop(L) - 1; // don't count the function value itself
 		lua_call(L, 0, LUA_MULTRET);
@@ -652,7 +667,7 @@ namespace Automation4 {
 		int ms = lua_tointeger(L, -1);
 		lua_pop(L, 1);
 		if (c && c->videoController->TimecodesLoaded())
-			lua_pushnumber(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
+			push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
 		else
 			lua_pushnil(L);
 
@@ -665,7 +680,7 @@ namespace Automation4 {
 		int frame = lua_tointeger(L, -1);
 		lua_pop(L, 1);
 		if (c && c->videoController->TimecodesLoaded())
-			lua_pushnumber(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
+			push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
 		else
 			lua_pushnil(L);
 		return 1;
@@ -675,10 +690,10 @@ namespace Automation4 {
 	{
 		const agi::Context *c = get_context(L);
 		if (c && c->videoController->IsLoaded()) {
-			lua_pushnumber(L, c->videoController->GetWidth());
-			lua_pushnumber(L, c->videoController->GetHeight());
-			lua_pushnumber(L, c->videoController->GetAspectRatioValue());
-			lua_pushnumber(L, c->videoController->GetAspectRatioType());
+			push_value(L, c->videoController->GetWidth());
+			push_value(L, c->videoController->GetHeight());
+			push_value(L, c->videoController->GetAspectRatioValue());
+			push_value(L, (int)c->videoController->GetAspectRatioType());
 			return 4;
 		}
 		else {
@@ -699,7 +714,7 @@ namespace Automation4 {
 
 		lua_newtable(L);
 		for (size_t i = 0; i < kf.size(); ++i) {
-			lua_pushinteger(L, kf[i]);
+			push_value(L, kf[i]);
 			lua_rawseti(L, -2, i);
 		}
 
@@ -708,9 +723,9 @@ namespace Automation4 {
 
 	int LuaScript::LuaDecodePath(lua_State *L)
 	{
-		wxString path = check_wxstring(L, 1);
+		std::string path = luaL_checkstring(L, 1);
 		lua_pop(L, 1);
-		lua_pushstring(L, StandardPaths::DecodePath(path).utf8_str());
+		push_value(L, StandardPaths::DecodePath(path));
 		return 1;
 	}
 
@@ -720,7 +735,7 @@ namespace Automation4 {
 		return lua_error(L);
 	}
 
-	void LuaThreadedCall(lua_State *L, int nargs, int nresults, wxString const& title, wxWindow *parent, bool can_open_config)
+	void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config)
 	{
 		bool failed = false;
 		BackgroundScriptRunner bsr(parent, title);
@@ -765,7 +780,7 @@ namespace Automation4 {
 		// get this feature's function pointers
 		lua_rawgeti(L, LUA_REGISTRYINDEX, myid);
 		// get pointer for validation function
-		lua_pushstring(L, function);
+		push_value(L, function);
 		lua_rawget(L, -2);
 		// remove the function table
 		lua_remove(L, -2);
@@ -786,7 +801,7 @@ namespace Automation4 {
 	, cmd_type(cmd::COMMAND_NORMAL)
 	{
 		lua_getfield(L, LUA_REGISTRYINDEX, "filename");
-		cmd_name = from_wx(wxString::Format("automation/lua/%s/%s", get_wxstring(L, -1), check_wxstring(L, 1)));
+		cmd_name = str(boost::format("automation/lua/%s/%s") % lua_tostring(L, -1) % luaL_checkstring(L, 1));
 
 		if (!lua_isfunction(L, 3))
 			luaL_error(L, "The macro processing function must be a function");
@@ -801,17 +816,17 @@ namespace Automation4 {
 		lua_newtable(L);
 
 		// store processing function
-		lua_pushstring(L, "run");
+		push_value(L, "run");
 		lua_pushvalue(L, 3);
 		lua_rawset(L, -3);
 
 		// store validation function
-		lua_pushstring(L, "validate");
+		push_value(L, "validate");
 		lua_pushvalue(L, 4);
 		lua_rawset(L, -3);
 
 		// store active function
-		lua_pushstring(L, "isactive");
+		push_value(L, "isactive");
 		lua_pushvalue(L, 5);
 		lua_rawset(L, -3);
 
@@ -844,7 +859,7 @@ namespace Automation4 {
 
 			if (diag == active_line) active_idx = row;
 			if (sel.count(diag)) {
-				lua_pushinteger(L, row);
+				push_value(L, row);
 				lua_rawseti(L, -2, idx++);
 			}
 		}
@@ -860,7 +875,7 @@ namespace Automation4 {
 
 		GetFeatureFunction("validate");
 		LuaAssFile *subsobj = new LuaAssFile(L, c->ass);
-		lua_pushinteger(L, transform_selection(L, c));
+		push_value(L, transform_selection(L, c));
 
 		int err = lua_pcall(L, 3, 2, 0);
 
@@ -893,10 +908,10 @@ namespace Automation4 {
 
 		GetFeatureFunction("run");
 		LuaAssFile *subsobj = new LuaAssFile(L, c->ass, true, true);
-		lua_pushinteger(L, transform_selection(L, c));
+		push_value(L, transform_selection(L, c));
 
 		try {
-			LuaThreadedCall(L, 3, 2, StrDisplay(c), c->parent, true);
+			LuaThreadedCall(L, 3, 2, from_wx(StrDisplay(c)), c->parent, true);
 
 			subsobj->ProcessingComplete(StrDisplay(c));
 
@@ -971,7 +986,7 @@ namespace Automation4 {
 
 		GetFeatureFunction("isactive");
 		LuaAssFile *subsobj = new LuaAssFile(L, c->ass);
-		lua_pushinteger(L, transform_selection(L, c));
+		push_value(L, transform_selection(L, c));
 
 		int err = lua_pcall(L, 3, 1, 0);
 		subsobj->ProcessingComplete();
@@ -991,7 +1006,7 @@ namespace Automation4 {
 
 	// LuaFeatureFilter
 	LuaExportFilter::LuaExportFilter(lua_State *L)
-	: ExportFilter(check_wxstring(L, 1), get_wxstring(L, 2), lua_tointeger(L, 3))
+	: ExportFilter(luaL_checkstring(L, 1), lua_tostring(L, 2), lua_tointeger(L, 3))
 	, LuaFeature(L)
 	{
 		if (!lua_isfunction(L, 4))
@@ -1001,12 +1016,12 @@ namespace Automation4 {
 		lua_newtable(L);
 
 		// store processing function
-		lua_pushstring(L, "run");
+		push_value(L, "run");
 		lua_pushvalue(L, 4);
 		lua_rawset(L, -3);
 
 		// store config function
-		lua_pushstring(L, "config");
+		push_value(L, "config");
 		lua_pushvalue(L, 5);
 		has_config = lua_isfunction(L, -1);
 		lua_rawset(L, -3);
@@ -1096,15 +1111,14 @@ namespace Automation4 {
 		Register(this);
 	}
 
-	Script* LuaScriptFactory::Produce(const wxString &filename) const
+	Script* LuaScriptFactory::Produce(agi::fs::path const& filename) const
 	{
 		// Just check if file extension is .lua
 		// Reject anything else
-		if (filename.Right(4).Lower() == ".lua") {
+		if (agi::fs::HasExtension(filename, "lua"))
 			return new LuaScript(filename);
-		} else {
+		else
 			return 0;
-		}
 	}
 }
 
diff --git a/aegisub/src/auto4_lua.h b/aegisub/src/auto4_lua.h
index a53434a6ad4fb8b73f63538de84feee91fa36ccd..7f79157d2ee8ae027d5274e54ec65b202f492dd4 100644
--- a/aegisub/src/auto4_lua.h
+++ b/aegisub/src/auto4_lua.h
@@ -148,10 +148,10 @@ namespace Automation4 {
 	class LuaDialogControl {
 	public:
 		/// Name of this control in the output table
-		wxString name;
+		std::string name;
 
 		/// Tooltip of this control
-		wxString hint;
+		std::string hint;
 
 		int x, y, width, height;
 
@@ -170,10 +170,10 @@ namespace Automation4 {
 
 		/// Serialize the control's current value so that it can be stored
 		/// in the script
-		virtual wxString SerialiseValue() const { return ""; }
+		virtual std::string SerialiseValue() const { return ""; }
 
 		/// Restore the control's value from a saved value in the script
-		virtual void UnserialiseValue(const wxString &serialised) { }
+		virtual void UnserialiseValue(const std::string &serialised) { }
 
 		LuaDialogControl(lua_State *L);
 
@@ -186,7 +186,7 @@ namespace Automation4 {
 		/// Controls in this dialog
 		std::vector<LuaDialogControl*> controls;
 		/// The names of buttons in this dialog if non-default ones were used
-		std::vector<wxString> buttons;
+		std::vector<std::string> buttons;
 
 		/// Does the dialog contain any buttons
 		bool use_buttons;
@@ -208,8 +208,8 @@ namespace Automation4 {
 
 		// ScriptDialog implementation
 		wxWindow* CreateWindow(wxWindow *parent);
-		wxString Serialise();
-		void Unserialise(const wxString &serialised);
+		std::string Serialise();
+		void Unserialise(const std::string &serialised);
 	};
 
 	class LuaFeature {
@@ -233,7 +233,7 @@ namespace Automation4 {
 	/// @param parent Parent window for the progress dialog
 	/// @param can_open_config Can the function open its own dialogs?
 	/// @throws agi::UserCancelException if the function fails to run to completion (either due to cancelling or errors)
-	void LuaThreadedCall(lua_State *L, int nargs, int nresults, wxString const& title, wxWindow *parent, bool can_open_config);
+	void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config);
 
 	class LuaCommand : public cmd::Command, private LuaFeature {
 		std::string cmd_name;
@@ -278,10 +278,10 @@ namespace Automation4 {
 	class LuaScript : public Script {
 		lua_State *L;
 
-		wxString name;
-		wxString description;
-		wxString author;
-		wxString version;
+		std::string name;
+		std::string description;
+		std::string author;
+		std::string version;
 
 		std::vector<cmd::Command*> macros;
 		std::vector<ExportFilter*> filters;
@@ -302,7 +302,7 @@ namespace Automation4 {
 		static int LuaCancel(lua_State *L);
 
 	public:
-		LuaScript(const wxString &filename);
+		LuaScript(agi::fs::path const& filename);
 		~LuaScript();
 
 		void RegisterCommand(LuaCommand *command);
@@ -314,10 +314,10 @@ namespace Automation4 {
 		// Script implementation
 		void Reload();
 
-		wxString GetName() const { return name; }
-		wxString GetDescription() const { return description; }
-		wxString GetAuthor() const { return author; }
-		wxString GetVersion() const { return version; }
+		std::string GetName() const { return name; }
+		std::string GetDescription() const { return description; }
+		std::string GetAuthor() const { return author; }
+		std::string GetVersion() const { return version; }
 		bool GetLoadedState() const { return L != 0; }
 
 		std::vector<cmd::Command*> GetMacros() const { return macros; }
diff --git a/aegisub/src/auto4_lua_assfile.cpp b/aegisub/src/auto4_lua_assfile.cpp
index d73823bdca162e828ad111e7334ae07b9b1e0642..07e7b87afcf3375e99432300fedc1246850dcd85 100644
--- a/aegisub/src/auto4_lua_assfile.cpp
+++ b/aegisub/src/auto4_lua_assfile.cpp
@@ -35,27 +35,25 @@
 #include "config.h"
 
 #ifdef WITH_AUTO4_LUA
-
-#include <algorithm>
-#include <cassert>
-
-#include <boost/range/adaptor/indirected.hpp>
-#include <boost/range/algorithm_ext.hpp>
-
-#include <wx/log.h>
-
-#include <libaegisub/exception.h>
-#include <libaegisub/log.h>
-#include <libaegisub/scoped_ptr.h>
+#include "auto4_lua.h"
 
 #include "ass_dialogue.h"
 #include "ass_info.h"
 #include "ass_file.h"
 #include "ass_karaoke.h"
 #include "ass_style.h"
-#include "auto4_lua.h"
 #include "utils.h"
 
+#include <libaegisub/exception.h>
+#include <libaegisub/log.h>
+#include <libaegisub/scoped_ptr.h>
+
+#include <algorithm>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/range/adaptor/indirected.hpp>
+#include <boost/range/algorithm_ext.hpp>
+#include <cassert>
+
 // This must be below the headers above.
 #ifdef __WINDOWS__
 #include "../../contrib/lua51/src/lualib.h"
@@ -65,10 +63,6 @@
 #endif
 
 namespace {
-	void push_value(lua_State *L, wxString const& value) {
-		lua_pushstring(L, value.utf8_str());
-	}
-
 	void push_value(lua_State *L, std::string const& value) {
 		lua_pushstring(L, value.c_str());
 	}
@@ -108,16 +102,6 @@ namespace {
 		return BadField(std::string("Invalid or missing field '") + name + "' in '" + line_clasee + "' class subtitle line (expected " + expected_type + ")");
 	}
 
-	wxString get_wxstring_field(lua_State *L, const char *name, const char *line_class)
-	{
-		lua_getfield(L, -1, name);
-		if (!lua_isstring(L, -1))
-			throw bad_field("string", name, line_class);
-		wxString ret(lua_tostring(L, -1), wxConvUTF8);
-		lua_pop(L, 1);
-		return ret;
-	}
-
 	std::string get_string_field(lua_State *L, const char *name, const char *line_class)
 	{
 		lua_getfield(L, -1, name);
@@ -202,10 +186,8 @@ namespace Automation4 {
 	{
 		lua_newtable(L);
 
-		wxString raw(e->GetEntryData());
-
 		set_field(L, "section", e->GroupHeader());
-		set_field(L, "raw", raw);
+		set_field(L, "raw", e->GetEntryData());
 
 		if (AssInfo *info = dynamic_cast<AssInfo*>(e)) {
 			set_field(L, "key", info->Key());
@@ -291,18 +273,15 @@ namespace Automation4 {
 		if (!lua_isstring(L, -1))
 			luaL_error(L, "Table lacks 'class' field, can't convert to AssEntry");
 
-		wxString lclass(lua_tostring(L, -1), wxConvUTF8);
-		lclass.MakeLower();
+		std::string lclass(lua_tostring(L, -1));
+		boost::to_lower(lclass);
 		lua_pop(L, 1);
 
 		AssEntry *result = 0;
 
 		try {
-			wxString section = get_wxstring_field(L, "section", "common");
-
-			if (lclass == "info") {
-				result = new AssInfo(get_wxstring_field(L, "key", "info"), get_wxstring_field(L, "value", "info"));
-			}
+			if (lclass == "info")
+				result = new AssInfo(get_string_field(L, "key", "info"), get_string_field(L, "value", "info"));
 			else if (lclass == "style") {
 				AssStyle *sty = new AssStyle;
 				result = sty;
@@ -339,16 +318,16 @@ namespace Automation4 {
 				dia->Layer = get_int_field(L, "layer", "dialogue");
 				dia->Start = get_int_field(L, "start_time", "dialogue");
 				dia->End = get_int_field(L, "end_time", "dialogue");
-				dia->Style = get_wxstring_field(L, "style", "dialogue");
-				dia->Actor = get_wxstring_field(L, "actor", "dialogue");
+				dia->Style = get_string_field(L, "style", "dialogue");
+				dia->Actor = get_string_field(L, "actor", "dialogue");
 				dia->Margin[0] = get_int_field(L, "margin_l", "dialogue");
 				dia->Margin[1] = get_int_field(L, "margin_r", "dialogue");
 				dia->Margin[2] = get_int_field(L, "margin_t", "dialogue");
-				dia->Effect = get_wxstring_field(L, "effect", "dialogue");
-				dia->Text = get_wxstring_field(L, "text", "dialogue");
+				dia->Effect = get_string_field(L, "effect", "dialogue");
+				dia->Text = get_string_field(L, "text", "dialogue");
 			}
 			else {
-				luaL_error(L, "Found line with unknown class: %s", lclass.utf8_str().data());
+				luaL_error(L, "Found line with unknown class: %s", lclass.c_str());
 				return 0;
 			}
 
@@ -620,7 +599,7 @@ namespace Automation4 {
 			PendingCommit& back = pending_commits.back();
 
 			back.modification_type = modification_type;
-			back.mesage = wxString(luaL_checkstring(L, 1), wxConvUTF8);
+			back.mesage = wxString::FromUTF8(luaL_checkstring(L, 1));
 			back.lines = lines;
 			modification_type = 0;
 		}
diff --git a/aegisub/src/auto4_lua_dialog.cpp b/aegisub/src/auto4_lua_dialog.cpp
index 4a7de31f1a6e6f025f27932c25193debcf49500d..da383f4fae1e7f2738195336389fd1ecd59aada6 100644
--- a/aegisub/src/auto4_lua_dialog.cpp
+++ b/aegisub/src/auto4_lua_dialog.cpp
@@ -32,38 +32,38 @@
 /// @ingroup scripting
 ///
 
-
 #include "config.h"
 
 #ifdef WITH_AUTO4_LUA
 
 #include "auto4_lua.h"
 
+#include "ass_style.h"
+#include "colour_button.h"
+#include "compat.h"
+#include "string_codec.h"
+#include "utils.h"
+#include "validators.h"
+
+#include <libaegisub/log.h>
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/tokenizer.hpp>
 #include <cassert>
+#include <cfloat>
 
 #include <wx/button.h>
 #include <wx/checkbox.h>
 #include <wx/combobox.h>
 #include <wx/gbsizer.h>
-#include <wx/log.h>
 #include <wx/panel.h>
 #include <wx/spinctrl.h>
-#include <wx/tokenzr.h>
+#include <wx/stattext.h>
 #include <wx/validate.h>
 #include <wx/valgen.h>
 #include <wx/valnum.h>
 #include <wx/window.h>
 
-#include <cfloat>
-
-#include <libaegisub/log.h>
-
-#include "ass_style.h"
-#include "colour_button.h"
-#include "compat.h"
-#include "string_codec.h"
-#include "utils.h"
-
 // These must be after the headers above.
 #ifdef __WINDOWS__
 #include "../../contrib/lua51/src/lualib.h"
@@ -73,51 +73,44 @@
 #endif
 
 namespace {
-	inline void get_if_right_type(lua_State *L, wxString &def)
-	{
+	inline void get_if_right_type(lua_State *L, std::string &def) {
 		if (lua_isstring(L, -1))
-			def = wxString(lua_tostring(L, -1), wxConvUTF8);
+			def = lua_tostring(L, -1);
 	}
 
-	inline void get_if_right_type(lua_State *L, double &def)
-	{
+	inline void get_if_right_type(lua_State *L, double &def) {
 		if (lua_isnumber(L, -1))
 			def = lua_tonumber(L, -1);
 	}
 
-	inline void get_if_right_type(lua_State *L, int &def)
-	{
+	inline void get_if_right_type(lua_State *L, int &def) {
 		if (lua_isnumber(L, -1))
 			def = lua_tointeger(L, -1);
 	}
 
-	inline void get_if_right_type(lua_State *L, bool &def)
-	{
+	inline void get_if_right_type(lua_State *L, bool &def) {
 		if (lua_isboolean(L, -1))
 			def = !!lua_toboolean(L, -1);
 	}
 
 	template<class T>
-	T get_field(lua_State *L, const char *name, T def)
-	{
+	T get_field(lua_State *L, const char *name, T def) {
 		lua_getfield(L, -1, name);
 		get_if_right_type(L, def);
 		lua_pop(L, 1);
 		return def;
 	}
 
-	inline wxString get_field(lua_State *L, const char *name)
-	{
-		return get_field(L, name, wxString());
+	inline std::string get_field(lua_State *L, const char *name) {
+		return get_field(L, name, std::string());
 	}
 
 	template<class T>
-	void read_string_array(lua_State *L, T &cont)
-	{
+	void read_string_array(lua_State *L, T &cont) {
 		lua_pushnil(L);
 		while (lua_next(L, -2)) {
 			if (lua_isstring(L, -1))
-				cont.push_back(wxString(lua_tostring(L, -1), wxConvUTF8));
+				cont.push_back(lua_tostring(L, -1));
 			lua_pop(L, 1);
 		}
 		lua_pop(L, 1);
@@ -135,29 +128,23 @@ namespace Automation4 {
 	, width(get_field(L, "width", 1))
 	, height(get_field(L, "height", 1))
 	{
-		LOG_D("automation/lua/dialog") << "created control: '" << from_wx(name) << "', (" << x << "," << y << ")(" << width << "," << height << "), "<< from_wx(hint);
+		LOG_D("automation/lua/dialog") << "created control: '" << name << "', (" << x << "," << y << ")(" << width << "," << height << "), " << hint;
 	}
 
 	namespace LuaControl {
 		/// A static text label
 		class Label : public LuaDialogControl {
-			wxString label;
+			std::string label;
 		public:
-			Label(lua_State *L)
-			: LuaDialogControl(L)
-			, label(get_field(L, "label"))
-			{
-			}
+			Label(lua_State *L) : LuaDialogControl(L), label(get_field(L, "label")) { }
 
-			wxControl *Create(wxWindow *parent)
-			{
-				return new wxStaticText(parent, -1, label);
+			wxControl *Create(wxWindow *parent) {
+				return new wxStaticText(parent, -1, to_wx(label));
 			}
 
 			int GetSizerFlags() const { return wxALIGN_CENTRE_VERTICAL | wxALIGN_LEFT; }
 
-			void LuaReadBack(lua_State *L)
-			{
+			void LuaReadBack(lua_State *L) {
 				// Label doesn't produce output, so let it be nil
 				lua_pushnil(L);
 			}
@@ -166,10 +153,10 @@ namespace Automation4 {
 		/// A single-line text edit control
 		class Edit : public LuaDialogControl {
 		protected:
-			wxString text;
+			std::string text;
 			wxTextCtrl *cw;
-		public:
 
+		public:
 			Edit(lua_State *L)
 			: LuaDialogControl(L)
 			, text(get_field(L, "value"))
@@ -182,49 +169,39 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const { return true; }
+			std::string SerialiseValue() const { return inline_string_encode(text); }
+			void UnserialiseValue(const std::string &serialised) { text = inline_string_decode(serialised); }
 
-			wxString SerialiseValue() const
-			{
-				return inline_string_encode(text);
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				text = inline_string_decode(serialised);
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
-				cw = new wxTextCtrl(parent, -1, text);
-				cw->SetValidator(wxGenericValidator(&text));
-				cw->SetToolTip(hint);
+			wxControl *Create(wxWindow *parent) {
+				cw = new wxTextCtrl(parent, -1, to_wx(text));
+				cw->SetValidator(StringBinder(&text));
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
-				lua_pushstring(L, text.utf8_str());
+			void LuaReadBack(lua_State *L) {
+				lua_pushstring(L, text.c_str());
 			}
 		};
 
 		/// A color-picker button
 		class Color : public LuaDialogControl {
-			wxString text;
+			std::string text;
 			bool alpha;
 
 			struct ColorValidator : public wxValidator {
-				wxString *text;
-				ColorValidator(wxString *text) : text(text) { }
+				std::string *text;
+				ColorValidator(std::string *text) : text(text) { }
 				wxValidator *Clone() const { return new ColorValidator(text); }
 				bool Validate(wxWindow*) { return true; }
 				bool TransferToWindow() { return true; }
 
-				bool TransferFromWindow()
-				{
-					*text = to_wx(static_cast<ColourButton*>(GetWindow())->GetColor().GetHexFormatted());
+				bool TransferFromWindow() {
+					*text = static_cast<ColourButton*>(GetWindow())->GetColor().GetHexFormatted();
 					return true;
 				}
 			};
+
 		public:
 			Color(lua_State *L, bool alpha)
 			: LuaDialogControl(L)
@@ -234,45 +211,31 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const { return true; }
+			std::string SerialiseValue() const { return inline_string_encode(text); }
+			void UnserialiseValue(const std::string &serialised) { text = inline_string_decode(serialised); }
 
-			wxString SerialiseValue() const
-			{
-				return inline_string_encode(text);
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				text = inline_string_decode(serialised);
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
-				agi::Color colour(from_wx(text));
+			wxControl *Create(wxWindow *parent) {
+				agi::Color colour(text);
 				wxControl *cw = new ColourButton(parent, wxSize(50*width,10*height), alpha, colour, ColorValidator(&text));
-				cw->SetToolTip(hint);
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
-				lua_pushstring(L, text.utf8_str());
+			void LuaReadBack(lua_State *L) {
+				lua_pushstring(L, text.c_str());
 			}
 		};
 
 		/// A multiline text edit control
 		class Textbox : public Edit {
 		public:
-			Textbox(lua_State *L)
-			: Edit(L)
-			{
-			}
+			Textbox(lua_State *L) : Edit(L) { }
 
 			// Same serialisation interface as single-line edit
-			wxControl *Create(wxWindow *parent)
-			{
-				cw = new wxTextCtrl(parent, -1, text, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE, wxGenericValidator(&text));
+			wxControl *Create(wxWindow *parent) {
+				cw = new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE, StringBinder(&text));
 				cw->SetMinSize(wxSize(0, 30));
-				cw->SetToolTip(hint);
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 		};
@@ -283,8 +246,8 @@ namespace Automation4 {
 			wxSpinCtrl *cw;
 			int value;
 			int min, max;
-		public:
 
+		public:
 			IntEdit(lua_State *L)
 			: Edit(L)
 			, cw(0)
@@ -299,34 +262,21 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const  { return true; }
+			std::string SerialiseValue() const { return std::to_string(value); }
+			void UnserialiseValue(const std::string &serialised) { value = atoi(serialised.c_str()); }
 
-			wxString SerialiseValue() const
-			{
-				return wxString::Format("%d", value);
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				long tmp;
-				if (serialised.ToLong(&tmp))
-					value = tmp;
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
-				cw = new wxSpinCtrl(parent, -1, wxString::Format("%d", value), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, value);
+			wxControl *Create(wxWindow *parent) {
+				cw = new wxSpinCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, value);
 				cw->SetValidator(wxGenericValidator(&value));
-				cw->SetToolTip(hint);
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
+			void LuaReadBack(lua_State *L) {
 				lua_pushinteger(L, value);
 			}
 		};
 
-
 		// Float only edit
 		class FloatEdit : public Edit {
 			double value;
@@ -342,8 +292,7 @@ namespace Automation4 {
 				bool Validate(wxWindow*) { return true; }
 				bool TransferToWindow() { return true; }
 
-				bool TransferFromWindow()
-				{
+				bool TransferFromWindow() {
 					*value = static_cast<wxSpinCtrlDouble*>(GetWindow())->GetValue();
 					return true;
 				}
@@ -365,48 +314,34 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const { return true; }
+			std::string SerialiseValue() const { return std::to_string(value); }
+			void UnserialiseValue(const std::string &serialised) { value = atof(serialised.c_str()); }
 
-			wxString SerialiseValue() const
-			{
-				return wxString::Format("%g", value);
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				double newval;
-				if (serialised.ToDouble(&newval))
-					value = newval;
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
+			wxControl *Create(wxWindow *parent) {
 				if (step > 0) {
-					scd = new wxSpinCtrlDouble(parent, -1,
-						wxString::Format("%g", value), wxDefaultPosition,
-						wxDefaultSize, wxSP_ARROW_KEYS, min, max, value, step);
+					scd = new wxSpinCtrlDouble(parent, -1, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, value, step);
 					scd->SetValidator(DoubleValidator(&value));
-					scd->SetToolTip(hint);
+					scd->SetToolTip(to_wx(hint));
 					return scd;
 				}
 
 				wxFloatingPointValidator<double> val(4, &value, wxNUM_VAL_NO_TRAILING_ZEROES);
 				val.SetRange(min, max);
 
-				cw = new wxTextCtrl(parent, -1, SerialiseValue(), wxDefaultPosition, wxDefaultSize, 0, val);
-				cw->SetToolTip(hint);
+				cw = new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, val);
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
+			void LuaReadBack(lua_State *L) {
 				lua_pushnumber(L, value);
 			}
 		};
 
 		/// A dropdown list
 		class Dropdown : public LuaDialogControl {
-			wxArrayString items;
-			wxString value;
+			std::vector<std::string> items;
+			std::string value;
 			wxComboBox *cw;
 
 		public:
@@ -420,36 +355,26 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const { return true; }
+			std::string SerialiseValue() const { return inline_string_encode(value); }
+			void UnserialiseValue(const std::string &serialised) { value = inline_string_decode(serialised); }
 
-			wxString SerialiseValue() const
-			{
-				return inline_string_encode(value);
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				value = inline_string_decode(serialised);
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
-				cw = new wxComboBox(parent, -1, value, wxDefaultPosition, wxDefaultSize, items, wxCB_READONLY, wxGenericValidator(&value));
-				cw->SetToolTip(hint);
+			wxControl *Create(wxWindow *parent) {
+				cw = new wxComboBox(parent, -1, to_wx(value), wxDefaultPosition, wxDefaultSize, to_wx(items), wxCB_READONLY, StringBinder(&value));
+				cw->SetToolTip(to_wx(hint));
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
-				lua_pushstring(L, value.utf8_str());
+			void LuaReadBack(lua_State *L) {
+				lua_pushstring(L, value.c_str());
 			}
 		};
 
 		class Checkbox : public LuaDialogControl {
-			wxString label;
+			std::string label;
 			bool value;
 			wxCheckBox *cw;
-		public:
 
+		public:
 			Checkbox(lua_State *L)
 			: LuaDialogControl(L)
 			, label(get_field(L, "label"))
@@ -459,29 +384,18 @@ namespace Automation4 {
 			}
 
 			bool CanSerialiseValue() const { return true; }
+			std::string SerialiseValue() const { return value ? "1" : "0"; }
+			void UnserialiseValue(const std::string &serialised) { value = serialised != "0"; }
 
-			wxString SerialiseValue() const
-			{
-				return value ? "1" : "0";
-			}
-
-			void UnserialiseValue(const wxString &serialised)
-			{
-				// fixme? should this allow more different "false" values?
-				value = serialised != "0";
-			}
-
-			wxControl *Create(wxWindow *parent)
-			{
-				cw = new wxCheckBox(parent, -1, label);
+			wxControl *Create(wxWindow *parent) {
+				cw = new wxCheckBox(parent, -1, to_wx(label));
 				cw->SetValidator(wxGenericValidator(&value));
-				cw->SetToolTip(hint);
+				cw->SetToolTip(to_wx(hint));
 				cw->SetValue(value);
 				return cw;
 			}
 
-			void LuaReadBack(lua_State *L)
-			{
+			void LuaReadBack(lua_State *L) {
 				lua_pushboolean(L, value);
 			}
 		};
@@ -503,40 +417,38 @@ namespace Automation4 {
 		lua_pushvalue(L, 1);
 		lua_pushnil(L); // initial key
 		while (lua_next(L, -2)) {
-			wxString controlclass;
-
-			if (lua_istable(L, -1))
-				controlclass = get_field(L, "class");
+			if (!lua_istable(L, -1))
+				luaL_error(L, "bad control table entry");
 
-			controlclass.LowerCase();
+			std::string controlclass = get_field(L, "class");
+			boost::to_lower(controlclass);
 
 			LuaDialogControl *ctl;
 
 			// Check control class and create relevant control
-			if (controlclass == "label") {
+			if (controlclass == "label")
 				ctl = new LuaControl::Label(L);
-			} else if (controlclass == "edit") {
+			else if (controlclass == "edit")
 				ctl = new LuaControl::Edit(L);
-			} else if (controlclass == "intedit") {
+			else if (controlclass == "intedit")
 				ctl = new LuaControl::IntEdit(L);
-			} else if (controlclass == "floatedit") {
+			else if (controlclass == "floatedit")
 				ctl = new LuaControl::FloatEdit(L);
-			} else if (controlclass == "textbox") {
+			else if (controlclass == "textbox")
 				ctl = new LuaControl::Textbox(L);
-			} else if (controlclass == "dropdown") {
+			else if (controlclass == "dropdown")
 				ctl = new LuaControl::Dropdown(L);
-			} else if (controlclass == "checkbox") {
+			else if (controlclass == "checkbox")
 				ctl = new LuaControl::Checkbox(L);
-			} else if (controlclass == "color") {
+			else if (controlclass == "color")
 				ctl = new LuaControl::Color(L, false);
-			} else if (controlclass == "coloralpha") {
+			else if (controlclass == "coloralpha")
 				ctl = new LuaControl::Color(L, true);
-			} else if (controlclass == "alpha") {
+			else if (controlclass == "alpha")
 				// FIXME
 				ctl = new LuaControl::Edit(L);
-			} else {
+			else
 				luaL_error(L, "bad control table entry");
-			}
 
 			controls.push_back(ctl);
 			lua_pop(L, 1);
@@ -548,29 +460,29 @@ namespace Automation4 {
 		}
 	}
 
-	LuaDialog::~LuaDialog()
-	{
+	LuaDialog::~LuaDialog() {
 		delete_clear(controls);
 	}
 
-	wxWindow* LuaDialog::CreateWindow(wxWindow *parent)
-	{
+	wxWindow* LuaDialog::CreateWindow(wxWindow *parent) {
 		window = new wxPanel(parent);
-		wxGridBagSizer *s = new wxGridBagSizer(4, 4);
 
+		wxGridBagSizer *s = new wxGridBagSizer(4, 4);
 		for (auto c : controls)
 			s->Add(c->Create(window), wxGBPosition(c->y, c->x), wxGBSpan(c->height, c->width), c->GetSizerFlags());
 
-		if (use_buttons) {
-			wxStdDialogButtonSizer *bs = new wxStdDialogButtonSizer();
+		if (!use_buttons)
+			window->SetSizerAndFit(s);
+		else {
+			wxStdDialogButtonSizer *bs = new wxStdDialogButtonSizer;
 			if (buttons.size() > 0) {
 				LOG_D("automation/lua/dialog") << "creating user buttons";
 				for (size_t i = 0; i < buttons.size(); ++i) {
-					LOG_D("automation/lua/dialog") << "button '" << from_wx(buttons[i]) << "' gets id " << 1001+(wxWindowID)i;
-
-					bs->Add(new wxButton(window, 1001+(wxWindowID)i, buttons[i]));
+					LOG_D("automation/lua/dialog") << "button '" << buttons[i] << "' gets id " << 1001+(wxWindowID)i;
+					bs->Add(new wxButton(window, 1001+(wxWindowID)i, to_wx(buttons[i])));
 				}
-			} else {
+			}
+			else {
 				LOG_D("automation/lua/dialog") << "creating default buttons";
 				bs->Add(new wxButton(window, wxID_OK));
 				bs->Add(new wxButton(window, wxID_CANCEL));
@@ -583,15 +495,12 @@ namespace Automation4 {
 			ms->Add(s, 0, wxBOTTOM, 5);
 			ms->Add(bs);
 			window->SetSizerAndFit(ms);
-		} else {
-			window->SetSizerAndFit(s);
 		}
 
 		return window;
 	}
 
-	int LuaDialog::LuaReadBack(lua_State *L)
-	{
+	int LuaDialog::LuaReadBack(lua_State *L) {
 		// First read back which button was pressed, if any
 		if (use_buttons) {
 			LOG_D("automation/lua/dialog") << "reading back button_pushed";
@@ -600,13 +509,15 @@ namespace Automation4 {
 				LOG_D("automation/lua/dialog") << "was zero, canceled";
 				// Always cancel/closed
 				lua_pushboolean(L, 0);
-			} else if (buttons.size() == 0 && btn == 1) {
+			}
+			else if (buttons.size() == 0 && btn == 1) {
 				LOG_D("automation/lua/dialog") << "default buttons, button 1 bushed, Ok button";
 				lua_pushboolean(L, 1);
-			} else {
-				LOG_D("automation/lua/dialog") << "user button: " << from_wx(buttons.at(btn-1));
+			}
+			else {
+				LOG_D("automation/lua/dialog") << "user button: " << buttons.at(btn-1);
 				// button_pushed is index+1 to reserve 0 for Cancel
-				lua_pushstring(L, buttons.at(btn-1).utf8_str());
+				lua_pushstring(L, buttons.at(btn-1).c_str());
 			}
 		}
 
@@ -614,45 +525,35 @@ namespace Automation4 {
 		lua_newtable(L);
 		for (auto control : controls) {
 			control->LuaReadBack(L);
-			lua_setfield(L, -2, control->name.utf8_str());
+			lua_setfield(L, -2, control->name.c_str());
 		}
 
-		if (use_buttons) {
-			return 2;
-		} else {
-			return 1;
-		}
+		return use_buttons ? 2 : 1;
 	}
 
-	wxString LuaDialog::Serialise()
-	{
-		wxString res;
+	std::string LuaDialog::Serialise() {
+		std::string res;
 
-		// Format into "name1:value1|name2:value2|name3:value3|"
+		// Format into "name1:value1|name2:value2|name3:value3"
 		for (auto control : controls) {
 			if (control->CanSerialiseValue()) {
-				wxString sn = inline_string_encode(control->name);
-				wxString sv = control->SerialiseValue();
-				res += wxString::Format("%s:%s|", sn, sv);
+				if (!res.empty())
+					res += "|";
+				res += inline_string_encode(control->name) + ":" + control->SerialiseValue();
 			}
 		}
 
-		// Remove trailing pipe
-		if (!res.empty())
-			res.RemoveLast();
-
 		return res;
 	}
 
-	void LuaDialog::Unserialise(const wxString &serialised)
-	{
-		// Split by pipe
-		wxStringTokenizer tk(serialised, "|");
-		while (tk.HasMoreTokens()) {
-			// Split by colon
-			wxString pair = tk.GetNextToken();
-			wxString name = inline_string_decode(pair.BeforeFirst(':'));
-			wxString value = pair.AfterFirst(':');
+	void LuaDialog::Unserialise(const std::string &serialised) {
+		boost::char_separator<char> psep("|"), csep(":");
+		for (auto const& cur : boost::tokenizer<boost::char_separator<char>>(serialised, psep)) {
+			size_t pos = cur.find(':');
+			if (pos == std::string::npos) continue;
+
+			std::string name = inline_string_decode(cur.substr(0, pos));
+			std::string value = cur.substr(pos + 1);
 
 			// Hand value to all controls matching name
 			for (auto control : controls) {
@@ -662,17 +563,18 @@ namespace Automation4 {
 		}
 	}
 
-	void LuaDialog::OnButtonPush(wxCommandEvent &evt)
-	{
+	void LuaDialog::OnButtonPush(wxCommandEvent &evt) {
 		// Let button_pushed == 0 mean "cancelled", such that pushing Cancel or closing the dialog
 		// will both result in button_pushed == 0
 		if (evt.GetId() == wxID_OK) {
 			LOG_D("automation/lua/dialog") << "was wxID_OK";
 			button_pushed = 1;
-		} else if (evt.GetId() == wxID_CANCEL) {
+		}
+		else if (evt.GetId() == wxID_CANCEL) {
 			LOG_D("automation/lua/dialog") << "was wxID_CANCEL";
 			button_pushed = 0;
-		} else {
+		}
+		else {
 			LOG_D("automation/lua/dialog") << "was user button";
 			// Therefore, when buttons are numbered from 1001 to 1000+n, make sure to set it to i+1
 			button_pushed = evt.GetId() - 1000;
diff --git a/aegisub/src/auto4_lua_factory.h b/aegisub/src/auto4_lua_factory.h
index a2411f844ec2470b1b3ff20ab694f1857de71125..3029e30b698717a0ba7803301a0a44a6f01a580a 100644
--- a/aegisub/src/auto4_lua_factory.h
+++ b/aegisub/src/auto4_lua_factory.h
@@ -36,7 +36,7 @@
 
 namespace Automation4 {
 	class LuaScriptFactory : public ScriptFactory {
-		Script* Produce(const wxString &filename) const;
+		Script* Produce(agi::fs::path const& filename) const;
 	public:
 		LuaScriptFactory();
 	};
diff --git a/aegisub/src/auto4_lua_progresssink.cpp b/aegisub/src/auto4_lua_progresssink.cpp
index c9157adbf367dbc444991bcc7a736b4906ee464c..7a84806bbd028c89a06e0c680d537858e1399b27 100644
--- a/aegisub/src/auto4_lua_progresssink.cpp
+++ b/aegisub/src/auto4_lua_progresssink.cpp
@@ -63,7 +63,7 @@ namespace {
 
 	inline wxString check_wxstring(lua_State *L, int idx)
 	{
-		return wxString(luaL_checkstring(L, idx), wxConvUTF8);
+		return wxString::FromUTF8(luaL_checkstring(L, idx));
 	}
 }
 
diff --git a/aegisub/src/auto4_lua_scriptreader.cpp b/aegisub/src/auto4_lua_scriptreader.cpp
index 4580f7cb0c250751b4acd5de8d62c8c08918a5c3..3e7f09028095ee4e0cd510b072f428b453273548 100644
--- a/aegisub/src/auto4_lua_scriptreader.cpp
+++ b/aegisub/src/auto4_lua_scriptreader.cpp
@@ -23,16 +23,17 @@
 
 #include "auto4_lua_scriptreader.h"
 
+#include "charset_detect.h"
+
 #include <libaegisub/io.h>
 #include <libaegisub/charset_conv.h>
 
-#include "charset_detect.h"
-#include "compat.h"
+#include <fstream>
 
 namespace Automation4 {
-	LuaScriptReader::LuaScriptReader(wxString const& filename)
+	LuaScriptReader::LuaScriptReader(agi::fs::path const& filename)
 	: conv(new agi::charset::IconvWrapper(CharSetDetect::GetEncoding(filename).c_str(), "utf-8", false))
-	, file(agi::io::Open(from_wx(filename)))
+	, file(agi::io::Open(filename))
 	{
 	}
 
diff --git a/aegisub/src/auto4_lua_scriptreader.h b/aegisub/src/auto4_lua_scriptreader.h
index deaec1442d7da04ce3abec16c6900558476995fa..5e8d1291e7c25b59712920c47e0ef821b34e13e7 100644
--- a/aegisub/src/auto4_lua_scriptreader.h
+++ b/aegisub/src/auto4_lua_scriptreader.h
@@ -17,23 +17,24 @@
 /// @ingroup scripting
 ///
 
-#include <istream>
+#include <libaegisub/fs_fwd.h>
 
-#include <libaegisub/scoped_ptr.h>
+#include <iosfwd>
+#include <memory>
+#include <string>
 
 struct lua_State;
 namespace agi { namespace charset { class IconvWrapper; } }
-class wxString;
 
 namespace Automation4 {
 	class LuaScriptReader {
-		agi::scoped_ptr<agi::charset::IconvWrapper> conv;
-		agi::scoped_ptr<std::istream> file;
+		std::unique_ptr<agi::charset::IconvWrapper> conv;
+		std::unique_ptr<std::istream> file;
 		char buf[512];
 
 		const char *Read(size_t *bytes_read);
 	public:
-		LuaScriptReader(wxString const& filename);
+		LuaScriptReader(agi::fs::path const& filename);
 		~LuaScriptReader();
 
 		static const char* reader_func(lua_State *, void *data, size_t *size);
diff --git a/aegisub/src/avisynth_wrap.cpp b/aegisub/src/avisynth_wrap.cpp
index bd8331db26f86ee0c57473259a4cb9c11124d983..52839b40a5e0d10a4547d14c2c1ae178c54dd401 100644
--- a/aegisub/src/avisynth_wrap.cpp
+++ b/aegisub/src/avisynth_wrap.cpp
@@ -40,12 +40,14 @@
 #include "avisynth.h"
 #include "options.h"
 
+#include <mutex>
+
 // Allocate storage for and initialise static members
 namespace {
 	int avs_refcount = 0;
 	HINSTANCE hLib = nullptr;
 	IScriptEnvironment *env = nullptr;
-	wxMutex AviSynthMutex;
+	std::mutex AviSynthMutex;
 }
 
 typedef IScriptEnvironment* __stdcall FUNC(int);
@@ -55,11 +57,11 @@ AviSynthWrapper::AviSynthWrapper() {
 		hLib = LoadLibrary(L"avisynth.dll");
 
 		if (!hLib)
-			throw wxString("Could not load avisynth.dll");
+			throw AvisynthError("Could not load avisynth.dll");
 
 		FUNC *CreateScriptEnv = (FUNC*)GetProcAddress(hLib, "CreateScriptEnvironment");
 		if (!CreateScriptEnv)
-			throw wxString("Failed to get address of CreateScriptEnv from avisynth.dll");
+			throw AvisynthError("Failed to get address of CreateScriptEnv from avisynth.dll");
 
 		// Require Avisynth 2.5.6+?
 		if (OPT_GET("Provider/Avisynth/Allow Ancient")->GetBool())
@@ -68,11 +70,11 @@ AviSynthWrapper::AviSynthWrapper() {
 			env = CreateScriptEnv(AVISYNTH_INTERFACE_VERSION);
 
 		if (!env)
-			throw wxString("Failed to create a new avisynth script environment. Avisynth is too old?");
+			throw AvisynthError("Failed to create a new avisynth script environment. Avisynth is too old?");
 
 		// Set memory limit
 		const int memoryMax = OPT_GET("Provider/Avisynth/Memory Max")->GetInt();
-		if (memoryMax != 0)
+		if (memoryMax)
 			env->SetMemoryMax(memoryMax);
 	}
 }
@@ -84,7 +86,7 @@ AviSynthWrapper::~AviSynthWrapper() {
 	}
 }
 
-wxMutex& AviSynthWrapper::GetMutex() const {
+std::mutex& AviSynthWrapper::GetMutex() const {
 	return AviSynthMutex;
 }
 
diff --git a/aegisub/src/avisynth_wrap.h b/aegisub/src/avisynth_wrap.h
index d4e5b2e2a92014cc7a2a94a162fa8f8bb0deef56..ece6fac634bdd16b8e242fb5cf659777242069af 100644
--- a/aegisub/src/avisynth_wrap.h
+++ b/aegisub/src/avisynth_wrap.h
@@ -34,12 +34,15 @@
 
 #ifdef WITH_AVISYNTH
 
+#include <libaegisub/exception.h>
+
 class IScriptEnvironment;
+namespace std { class mutex; }
 
 class AviSynthWrapper {
 	AviSynthWrapper(AviSynthWrapper const&);
 public:
-	wxMutex& GetMutex() const;
+	std::mutex& GetMutex() const;
 	IScriptEnvironment *GetEnv() const;
 
 	AviSynthWrapper();
diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp
index d60d827e5a178f1845bdfeb57ee5e8ee66b5d547..ad159ea9942fd476ca3e73c9d188d202b756f899 100644
--- a/aegisub/src/base_grid.cpp
+++ b/aegisub/src/base_grid.cpp
@@ -191,8 +191,8 @@ void BaseGrid::OnSubtitlesOpen() {
 }
 
 void BaseGrid::OnSubtitlesSave() {
-	context->ass->SetScriptInfo("Scroll Position", wxString::Format("%d", yPos));
-	context->ass->SetScriptInfo("Active Line", wxString::Format("%d", GetDialogueIndex(active_line)));
+	context->ass->SetScriptInfo("Scroll Position", std::to_string(yPos));
+	context->ass->SetScriptInfo("Active Line", std::to_string(GetDialogueIndex(active_line)));
 }
 
 void BaseGrid::OnShowColMenu(wxCommandEvent &event) {
@@ -592,15 +592,15 @@ void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wx
 		if (paint_columns[3]) strings[3] = wxString::Format("%d", context->videoController->FrameAtTime(line->End, agi::vfr::END));
 	}
 	else {
-		if (paint_columns[2]) strings[2] = line->Start.GetAssFormated();
-		if (paint_columns[3]) strings[3] = line->End.GetAssFormated();
+		if (paint_columns[2]) strings[2] = to_wx(line->Start.GetAssFormated());
+		if (paint_columns[3]) strings[3] = to_wx(line->End.GetAssFormated());
 	}
-	if (paint_columns[4]) strings[4] = line->Style;
-	if (paint_columns[5]) strings[5] = line->Actor;
-	if (paint_columns[6]) strings[6] = line->Effect;
-	if (paint_columns[7]) strings[7] = line->GetMarginString(0);
-	if (paint_columns[8]) strings[8] = line->GetMarginString(1);
-	if (paint_columns[9]) strings[9] = line->GetMarginString(2);
+	if (paint_columns[4]) strings[4] = to_wx(line->Style);
+	if (paint_columns[5]) strings[5] = to_wx(line->Actor);
+	if (paint_columns[6]) strings[6] = to_wx(line->Effect);
+	if (paint_columns[7]) strings[7] = to_wx(line->GetMarginString(0));
+	if (paint_columns[8]) strings[8] = to_wx(line->GetMarginString(1));
+	if (paint_columns[9]) strings[9] = to_wx(line->GetMarginString(2));
 
 	if (paint_columns[10]) {
 		strings[10].clear();
@@ -609,18 +609,19 @@ void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wx
 		if (replace) {
 			strings[10].reserve(line->Text.get().size());
 			size_t start = 0, pos;
-			while ((pos = line->Text.get().find('{', start)) != wxString::npos) {
-				strings[10] += line->Text.get().Mid(start, pos - start);
+			while ((pos = line->Text.get().find('{', start)) != std::string::npos) {
+				strings[10] += to_wx(line->Text.get().substr(start, pos - start));
 				strings[10] += rep_char;
 				start = line->Text.get().find('}', pos);
-				if (start != wxString::npos) ++start;
+				if (start != std::string::npos) ++start;
 			}
-			strings[10] += line->Text.get().Mid(start);
+			if (start != std::string::npos)
+				strings[10] += to_wx(line->Text.get().substr(start));
 		}
 
 		// Show overrides
 		else
-			strings[10] = line->Text;
+			strings[10] = to_wx(line->Text);
 
 		// Cap length and set text
 		if (strings[10].size() > 512)
@@ -833,7 +834,7 @@ void BaseGrid::SetColumnWidths() {
 	int startLen = 0;
 	int endLen = 0;
 	if (!byFrame)
-		startLen = endLen = dc.GetTextExtent(AssTime().GetAssFormated()).GetWidth();
+		startLen = endLen = dc.GetTextExtent(to_wx(AssTime().GetAssFormated())).GetWidth();
 
 	// O(n) widths
 	bool showMargin[3] = { false, false, false };
@@ -847,9 +848,9 @@ void BaseGrid::SetColumnWidths() {
 		AssDialogue *curDiag = GetDialogue(i);
 
 		maxLayer = std::max(maxLayer, curDiag->Layer);
-		actorLen = std::max(actorLen, dc.GetTextExtent(curDiag->Actor).GetWidth());
-		styleLen = std::max(styleLen, dc.GetTextExtent(curDiag->Style).GetWidth());
-		effectLen = std::max(effectLen, dc.GetTextExtent(curDiag->Effect).GetWidth());
+		actorLen = std::max(actorLen, dc.GetTextExtent(to_wx(curDiag->Actor)).GetWidth());
+		styleLen = std::max(styleLen, dc.GetTextExtent(to_wx(curDiag->Style)).GetWidth());
+		effectLen = std::max(effectLen, dc.GetTextExtent(to_wx(curDiag->Effect)).GetWidth());
 
 		// Margins
 		for (int j = 0; j < 3; j++) {
diff --git a/aegisub/src/charset_conv.cpp b/aegisub/src/charset_conv.cpp
deleted file mode 100644
index 01a796898e657eeed0895d3b4d6199909f009801..0000000000000000000000000000000000000000
--- a/aegisub/src/charset_conv.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2009, Thomas Goyne
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-//
-// Aegisub Project http://www.aegisub.org/
-
-/// @file charset_conv.cpp
-/// @brief Iconv-based implementation of character set conversions
-/// @ingroup utility
-///
-
-#include "config.h"
-
-#include "charset_conv.h"
-
-#include <cerrno>
-#include <cstdint>
-
-#include <wx/intl.h>
-
-class AegisubCSConvImpl : public AegisubCSConv {
-public:
-	AegisubCSConvImpl() { }
-};
-
-AegisubCSConv::AegisubCSConv()
-: conv("wchar_t", "")
-{
-}
-
-size_t AegisubCSConv::ToWChar(wchar_t *dst, size_t dstSize, const char *src, size_t srcLen) const {
-	throw agi::charset::UnsupportedConversion("Cannot convert to local with csConvLocal");
-}
-
-/// @brief Convert a string from wide characters to multibyte
-/// @param dst     Destination buffer
-/// @param dstSize Length of destination buffer in bytes
-/// @param src     Source wide character string
-/// @param srcLen  Length in wchar_ts of source, or -1 to autodetect
-/// @return The number of bytes needed to store the string in the target charset
-size_t AegisubCSConv::FromWChar(char *dst, size_t dstSize, const wchar_t *src, size_t srcLen) const {
-	try {
-		if (srcLen != (size_t)-1) {
-			if (src[srcLen - 1] == 0) srcLen -= 1;
-			srcLen *= sizeof(wchar_t);
-		}
-		if (dstSize == 0) {
-			return conv.RequiredBufferSize(reinterpret_cast<const char*>(src), srcLen);
-		}
-		return conv.Convert(reinterpret_cast<const char*>(src), srcLen, dst, dstSize);
-	}
-	catch (agi::charset::ConvError const&) {
-		return (size_t)-1;
-	}
-}
-static AegisubCSConvImpl localConv;
-AegisubCSConv& csConvLocal = localConv;
diff --git a/aegisub/src/charset_conv.h b/aegisub/src/charset_conv.h
deleted file mode 100644
index da8224a9f7b742f107eaabbd57946ce1b6788771..0000000000000000000000000000000000000000
--- a/aegisub/src/charset_conv.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// Aegisub Project http://www.aegisub.org/
-
-/// @file charset_conv.h
-/// @see charset_conv.cpp
-/// @ingroup utility
-///
-
-#include <wx/string.h>
-#include <wx/strconv.h>
-#include <wx/thread.h>
-
-#include <libaegisub/charset_conv.h>
-
-/// @class AegisubCSConv
-/// @brief wxMBConv implementation for converting to and from unicode
-class AegisubCSConv : public wxMBConv {
-public:
-
-	// wxMBConv implementation; see strconv.h for usage details
-	size_t ToWChar(wchar_t *dst, size_t dstLen, const char *src, size_t srcLen = wxNO_LEN) const;
-	size_t FromWChar(char *dst, size_t dstLen, const wchar_t *src, size_t srcLen = wxNO_LEN) const;
-	wxMBConv *Clone() const { return nullptr; };
-
-protected:
-	AegisubCSConv();
-private:
-	AegisubCSConv(const AegisubCSConv&);
-	AegisubCSConv& operator=(const AegisubCSConv&);
-	wxString localCharset;
-
-	mutable wxMutex iconvMutex;
-
-	// ToWChar and FromWChar are const in wxMBConv, but iconv can't be used
-	// immutably
-	mutable agi::charset::IconvWrapper conv;
-};
-
-extern AegisubCSConv& csConvLocal;
diff --git a/aegisub/src/charset_detect.cpp b/aegisub/src/charset_detect.cpp
index 6bdf4371b2185cfa00fc4d6b4fad3f064093ccca..a61c653456d81bdcf98b7642b71f13a2229542cd 100644
--- a/aegisub/src/charset_detect.cpp
+++ b/aegisub/src/charset_detect.cpp
@@ -34,26 +34,26 @@
 
 #include "config.h"
 
-#include <fstream>
-#include <list>
+#include "charset_detect.h"
 
-#include <wx/arrstr.h>
-#include <wx/choicdlg.h>
-#include <wx/intl.h>
+#include "compat.h"
 
 #include <libaegisub/charset.h>
 #include <libaegisub/log.h>
 
-#include "charset_detect.h"
-#include "compat.h"
+#include <boost/filesystem/path.hpp>
+
+#include <wx/arrstr.h>
+#include <wx/choicdlg.h>
+#include <wx/intl.h>
 
 namespace CharSetDetect {
 
-wxString GetEncoding(wxString const& filename) {
+std::string GetEncoding(agi::fs::path const& filename) {
 	agi::charset::CharsetListDetected list;
 
 	try {
-		list = agi::charset::DetectAll(from_wx(filename));
+		list = agi::charset::DetectAll(filename);
 	} catch (const agi::charset::UnknownCharset&) {
 		/// @todo If the charset is unknown we need to display a complete list of character sets.
 	}
@@ -61,7 +61,7 @@ wxString GetEncoding(wxString const& filename) {
 	if (list.size() == 1) {
 		auto charset = list.begin();
 		LOG_I("charset/file") << filename << " (" << charset->second << ")";
-		return to_wx(charset->second);
+		return charset->second;
 	}
 
 	wxArrayString choices;
@@ -74,9 +74,12 @@ wxString GetEncoding(wxString const& filename) {
 
 	LOG_I("charset/file") << filename << " (" << log_choice << ")";
 
-	int choice = wxGetSingleChoiceIndex(_("Aegisub could not narrow down the character set to a single one.\nPlease pick one below:"),_("Choose character set"),choices);
-	if (choice == -1) throw "Canceled";
-	return choices[choice];
+	int choice = wxGetSingleChoiceIndex(
+		_("Aegisub could not narrow down the character set to a single one.\nPlease pick one below:"),
+		_("Choose character set"),
+		choices);
+	if (choice == -1) throw agi::UserCancelException("Cancelled encoding selection");
+	return list[choice].second;
 }
 
 }
diff --git a/aegisub/src/charset_detect.h b/aegisub/src/charset_detect.h
index 0307245ae5c07679144da5b7a899ee90ce184137..ec528a598d2e8585687cec7fe383075bff74f3ea 100644
--- a/aegisub/src/charset_detect.h
+++ b/aegisub/src/charset_detect.h
@@ -32,9 +32,12 @@
 /// @ingroup utility
 ///
 
+#include <libaegisub/fs_fwd.h>
+#include <string>
+
 namespace CharSetDetect {
 	/// @brief Get character set name.
 	/// @param filename File to check
 	/// @return Character set name
-	wxString GetEncoding(wxString const& filename);
+	std::string GetEncoding(agi::fs::path const& filename);
 }
diff --git a/aegisub/src/colorspace.cpp b/aegisub/src/colorspace.cpp
index bbf98ea2cbb6375cafc8893af40bf1bd3ad4fc21..b63506d386979f42a5aaf50cae1a58ca7f2d3d27 100644
--- a/aegisub/src/colorspace.cpp
+++ b/aegisub/src/colorspace.cpp
@@ -37,19 +37,11 @@
 #include "colorspace.h"
 #include "utils.h"
 
-
-// Matrix from http://forum.doom9.org/showthread.php?p=684080#post684080
-void yuv_to_rgb(int Y, int U, int V, unsigned char *R, unsigned char *G, unsigned char *B)
+static inline unsigned int clip_colorval(int val)
 {
-	U = U - 128;
-	V = V - 128;
-	*R = clip_colorval((Y*65536                        + int(1.140*65536) * V) / 65536);
-	*G = clip_colorval((Y*65536 - int(0.395*65536) * U - int(0.581*65536) * V) / 65536);
-	*B = clip_colorval((Y*65536 + int(2.032*65536) * U                       ) / 65536);
+	return mid(0, val, 255);
 }
 
-
-
 // Algorithm from http://130.113.54.154/~monger/hsl-rgb.html
 void hsl_to_rgb(int H, int S, int L, unsigned char *R, unsigned char *G, unsigned char *B)
 {
@@ -253,14 +245,6 @@ void hsv_to_rgb(int H, int S, int V, unsigned char *R, unsigned char *G, unsigne
 	*B = clip_colorval(b/256);
 }
 
-// Matrix from http://forum.doom9.org/showthread.php?p=684080#post684080
-void rgb_to_yuv(int R, int G, int B, unsigned char *Y, unsigned char *U, unsigned char *V)
-{
-	*Y = clip_colorval(( int(0.299*65536) * R + int(0.587*65536) * G + int(0.114*65536) * B) / 65536);
-	*U = clip_colorval((-int(0.147*65536) * R - int(0.289*65536) * G + int(0.436*65536) * B) / 65536 + 128);
-	*V = clip_colorval(( int(0.615*65536) * R - int(0.515*65536) * G - int(0.100*65536) * B) / 65536 + 128);
-}
-
 /// Algorithm from http://130.113.54.154/~monger/hsl-rgb.html
 void rgb_to_hsl(int R, int G, int B, unsigned char *H, unsigned char *S, unsigned char *L)
 {
diff --git a/aegisub/src/colorspace.h b/aegisub/src/colorspace.h
index 18296490e11b7aa3584eb718e0c3daaecee66aaa..ce1640bc23902e5dbf9761df4cff4b202ac6ddfc 100644
--- a/aegisub/src/colorspace.h
+++ b/aegisub/src/colorspace.h
@@ -32,27 +32,12 @@
 /// @ingroup utility
 ///
 
-#include <wx/colour.h>
-#include <wx/string.h>
-
-inline unsigned int clip_colorval(int val)
-{
-	return std::max(0, std::min(val, 255));
-}
-
-
-/// Convert an YUV color to RGB; all values are expected to be in range 0..255
-void yuv_to_rgb(int Y, int U, int V, unsigned char *R, unsigned char *G, unsigned char *B);
-
 /// Convert a HSL color to RGB; all values are expected to be in range 0..255
 void hsl_to_rgb(int H, int S, int L, unsigned char *R, unsigned char *G, unsigned char *B);
 
 /// Convert a HSV color to RGB; all values are expected to be in range 0..255
 void hsv_to_rgb(int H, int S, int V, unsigned char *R, unsigned char *G, unsigned char *B);
 
-/// Convert an RGB color to YUV; all values are expected to be in range 0..255
-void rgb_to_yuv(int R, int G, int B, unsigned char *Y, unsigned char *U, unsigned char *V);
-
 /// Convert an RGB color to HSL; all values are expected to be in range 0..255
 void rgb_to_hsl(int R, int G, int B, unsigned char *H, unsigned char *S, unsigned char *L);
 
diff --git a/aegisub/src/command/audio.cpp b/aegisub/src/command/audio.cpp
index 00c9402f3772dc074b9da319b508cbb01dc34f65..3fb14f58f06b09fe590060ff891984490ee4bbda 100644
--- a/aegisub/src/command/audio.cpp
+++ b/aegisub/src/command/audio.cpp
@@ -36,10 +36,6 @@
 
 #include "../config.h"
 
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-#include <wx/msgdlg.h>
-
 #include "command.h"
 
 #include "../ass_dialogue.h"
@@ -53,6 +49,10 @@
 #include "../selection_controller.h"
 #include "../video_context.h"
 
+#include <boost/filesystem/path.hpp>
+#include <wx/filedlg.h>
+#include <wx/msgdlg.h>
+
 namespace {
 	using cmd::Command;
 
@@ -92,10 +92,10 @@ struct audio_open : public Command {
 			wxString str = _("Audio Formats") + " (*.aac,*.ac3,*.ape,*.dts,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.dts;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.w64;*.wav;*.wma|"
 						+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
 						+ _("All Files") + " (*.*)|*.*";
-			wxString filename = wxFileSelector(_("Open Audio File"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+			agi::fs::path filename = wxFileSelector(_("Open Audio File"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 			if (!filename.empty()) {
 				c->audioController->OpenAudio(filename);
-				OPT_SET("Path/Last/Audio")->SetString(from_wx(wxFileName(filename).GetPath()));
+				OPT_SET("Path/Last/Audio")->SetString(filename.parent_path().string());
 			}
 		}
 		catch (agi::UserCancelException const&) { }
@@ -224,7 +224,7 @@ struct audio_save_clip : public Command {
 		}
 
 		c->audioController->SaveClip(
-			wxFileSelector(_("Save audio clip"), "", "", "wav", "", wxFD_SAVE|wxFD_OVERWRITE_PROMPT, c->parent),
+			from_wx(wxFileSelector(_("Save audio clip"), "", "", "wav", "", wxFD_SAVE|wxFD_OVERWRITE_PROMPT, c->parent)),
 			TimeRange(start, end));
 	}
 };
diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp
index ca6423459c048deabd9d1e38a0578c956f9eb869..c5872dec1c56055f324224c78485b7e29b3118e4 100644
--- a/aegisub/src/command/edit.cpp
+++ b/aegisub/src/command/edit.cpp
@@ -36,12 +36,6 @@
 
 #include "../config.h"
 
-#include <algorithm>
-
-#include <wx/clipbrd.h>
-#include <wx/fontdlg.h>
-#include <wx/tokenzr.h>
-
 #include "command.h"
 
 #include "../ass_dialogue.h"
@@ -62,15 +56,21 @@
 #include "../utils.h"
 #include "../video_context.h"
 
-#include <boost/algorithm/string/predicate.hpp>
+#include <libaegisub/of_type_adaptor.h>
+
+#include <algorithm>
 #include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
 #include <boost/format.hpp>
 #include <boost/range/adaptor/filtered.hpp>
 #include <boost/range/adaptor/reversed.hpp>
 #include <boost/range/adaptor/sliced.hpp>
 #include <boost/range/adaptor/transformed.hpp>
+#include <boost/tokenizer.hpp>
 
-#include <libaegisub/of_type_adaptor.h>
+#include <wx/clipbrd.h>
+#include <wx/fontdlg.h>
 
 namespace {
 	using namespace boost::adaptors;
@@ -94,16 +94,15 @@ struct validate_sel_multiple : public Command {
 
 template<typename Paster>
 void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) {
-	wxString data = GetClipboard();
-	if (!data) return;
+	std::string data = GetClipboard();
+	if (data.empty()) return;
 
 	AssDialogue *first = nullptr;
 	SubtitleSelection newsel;
 
-	wxStringTokenizer token(data, "\r\n", wxTOKEN_STRTOK);
-	while (token.HasMoreTokens()) {
-		// Convert data into an AssDialogue
-		wxString curdata = token.GetNextToken().Trim(true).Trim(false);
+	boost::char_separator<char> sep("\r\n");
+	for (auto curdata : boost::tokenizer<boost::char_separator<char>>(data, sep)) {
+		boost::trim(curdata);
 		AssDialogue *curdiag;
 		try {
 			// Try to interpret the line as an ASS line
@@ -166,7 +165,7 @@ T get_value(boost::ptr_vector<AssDialogueBlock> const& blocks, int blockn, T ini
 }
 
 /// Get the block index in the text of the position
-int block_at_pos(wxString const& text, int pos) {
+int block_at_pos(std::string const& text, int pos) {
 	int n = 0;
 	int max = text.size() - 1;
 	for (int i = 0; i <= pos && i <= max; ++i) {
@@ -214,7 +213,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector<AssDialogueBlock> &blocks, std
 	std::string insert(tag + value);
 	int shift = insert.size();
 	if (plain || blockn < 0) {
-		line->Text = line->Text.get().Left(start) + "{" + to_wx(insert) + "}" + line->Text.get().Mid(start);
+		line->Text = line->Text.get().substr(0, start) + "{" + insert + "}" + line->Text.get().substr(start);
 		shift += 2;
 		blocks = line->ParseTags();
 	}
@@ -253,7 +252,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector<AssDialogueBlock> &blocks, std
 
 void commit_text(agi::Context const * const c, wxString const& desc, int sel_start = -1, int sel_end = -1, int *commit_id = 0) {
 	SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
-	wxString text = c->selectionController->GetActiveLine()->Text;
+	std::string text = c->selectionController->GetActiveLine()->Text;
 	for_each(sel.begin(), sel.end(), [&](AssDialogue *d) { d->Text = text; });
 
 	int new_commit_id = c->ass->Commit(desc, AssFile::COMMIT_DIAG_TEXT, commit_id ? *commit_id : -1, sel.size() == 1 ? *sel.begin() : 0);
@@ -265,7 +264,7 @@ void commit_text(agi::Context const * const c, wxString const& desc, int sel_sta
 
 void toggle_override_tag(const agi::Context *c, bool (AssStyle::*field), const char *tag, wxString const& undo_msg) {
 	AssDialogue *const line = c->selectionController->GetActiveLine();
-	AssStyle const* const style = c->ass->GetStyle(from_wx(line->Style));
+	AssStyle const* const style = c->ass->GetStyle(line->Style);
 	bool state = style ? style->*field : AssStyle().*field;
 
 	boost::ptr_vector<AssDialogueBlock> blocks(line->ParseTags());
@@ -284,7 +283,7 @@ void toggle_override_tag(const agi::Context *c, bool (AssStyle::*field), const c
 
 void show_color_picker(const agi::Context *c, agi::Color (AssStyle::*field), const char *tag, const char *alt, const char *alpha) {
 	AssDialogue *const line = c->selectionController->GetActiveLine();
-	AssStyle const* const style = c->ass->GetStyle(from_wx(line->Style));
+	AssStyle const* const style = c->ass->GetStyle(line->Style);
 	agi::Color color = (style ? style->*field : AssStyle().*field);
 
 	boost::ptr_vector<AssDialogueBlock> blocks(line->ParseTags());
@@ -412,7 +411,7 @@ struct edit_font : public Command {
 		boost::ptr_vector<AssDialogueBlock> blocks(line->ParseTags());
 		const int blockn = block_at_pos(line->Text, c->textSelectionController->GetInsertionPoint());
 
-		const AssStyle *style = c->ass->GetStyle(from_wx(line->Style));
+		const AssStyle *style = c->ass->GetStyle(line->Style);
 		const AssStyle default_style;
 		if (!style)
 			style = &default_style;
@@ -426,7 +425,7 @@ struct edit_font : public Command {
 			get_value(blocks, blockn, style->italic, "\\i") ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL,
 			get_value(blocks, blockn, style->bold, "\\b") ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL,
 			get_value(blocks, blockn, style->underline, "\\u"),
-			to_wx(get_value(blocks, blockn, from_wx(style->font), "\\fn")));
+			to_wx(get_value(blocks, blockn, style->font, "\\fn")));
 
 		const wxFont font = wxGetFontFromUser(c->parent, startfont);
 		if (!font.Ok() || font == startfont) return;
@@ -459,14 +458,14 @@ struct edit_find_replace : public Command {
 	}
 };
 
-static wxString get_entry_data(AssDialogue *d) { return d->GetEntryData(); }
+static std::string get_entry_data(AssDialogue *d) { return d->GetEntryData(); }
 static void copy_lines(agi::Context *c) {
 	SubtitleSelection sel = c->selectionController->GetSelectedSet();
 	SetClipboard(join(c->ass->Line
 		| agi::of_type<AssDialogue>()
 		| filtered([&](AssDialogue *d) { return sel.count(d); })
 		| transformed(get_entry_data),
-		wxS("\r\n")));
+		"\r\n"));
 }
 
 static void delete_lines(agi::Context *c, wxString const& commit_message) {
@@ -668,11 +667,11 @@ static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDi
 }
 
 static void combine_karaoke(AssDialogue *first, AssDialogue *second) {
-	first->Text = wxString::Format("%s{\\k%d}%s", first->Text.get(), (second->Start - first->End) / 10, second->Text.get());
+	first->Text = first->Text.get() + "{\\k" + std::to_string((second->Start - first->End) / 10) + "}" + second->Text.get();
 }
 
 static void combine_concat(AssDialogue *first, AssDialogue *second) {
-	first->Text = first->Text + " " + second->Text;
+	first->Text = first->Text.get() + " " + second->Text.get();
 }
 
 static void combine_drop(AssDialogue *, AssDialogue *) { }
@@ -836,9 +835,9 @@ void split_lines(agi::Context *c, bool estimate) {
 	AssDialogue *n2 = new AssDialogue(*n1);
 	c->ass->Line.insert(++c->ass->Line.iterator_to(*n1), *n2);
 
-	wxString orig = n1->Text;
-	n1->Text = orig.Left(pos).Trim(true); // Trim off trailing whitespace
-	n2->Text = orig.Mid(pos).Trim(false); // Trim off leading whitespace
+	std::string orig = n1->Text;
+	n1->Text = boost::trim_right_copy(orig.substr(0, pos));
+	n2->Text = boost::trim_left_copy(orig.substr(pos));
 
 	if (estimate && orig.size()) {
 		double splitPos = double(pos) / orig.size();
@@ -948,7 +947,7 @@ struct edit_clear : public Command {
 	}
 };
 
-wxString get_text(AssDialogueBlock &d) { return d.GetText(); }
+std::string get_text(AssDialogueBlock &d) { return d.GetText(); }
 struct edit_clear_text : public Command {
 	CMD_NAME("edit/clear/text")
 	STR_DISP("Clear Text")
@@ -961,7 +960,7 @@ struct edit_clear_text : public Command {
 		line->Text = join(blocks
 			| filtered([](AssDialogueBlock const& b) { return b.GetType() != BLOCK_PLAIN; })
 			| transformed(get_text),
-			wxS(""));
+			"");
 		c->ass->Commit(_("clear line"), AssFile::COMMIT_DIAG_TEXT, -1, line);
 	}
 };
@@ -977,7 +976,7 @@ struct edit_insert_original : public Command {
 		int sel_start = c->textSelectionController->GetSelectionStart();
 		int sel_end = c->textSelectionController->GetSelectionEnd();
 
-		line->Text = line->Text.get().Left(sel_start) + c->initialLineState->GetInitialText() + line->Text.get().Mid(sel_end);
+		line->Text = line->Text.get().substr(0, sel_start) + c->initialLineState->GetInitialText() + line->Text.get().substr(sel_end);
 		c->ass->Commit(_("insert original"), AssFile::COMMIT_DIAG_TEXT, -1, line);
 	}
 };
diff --git a/aegisub/src/command/keyframe.cpp b/aegisub/src/command/keyframe.cpp
index 43f700ec30d429256edd2b8c88463769201c746a..1075e8c49d040b8fd31611d98255a25433228e03 100644
--- a/aegisub/src/command/keyframe.cpp
+++ b/aegisub/src/command/keyframe.cpp
@@ -36,22 +36,20 @@
 
 #include "../config.h"
 
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-
 #include "command.h"
 
-#include "../compat.h"
 #include "../include/aegisub/context.h"
 #include "../options.h"
 #include "../video_context.h"
 
+#include <boost/filesystem/path.hpp>
+#include <wx/filedlg.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-keyframed Keyframe commands.
 /// @{
 
-
 /// Closes the currently open keyframes list.
 struct keyframe_close : public Command {
 	CMD_NAME("keyframe/close")
@@ -78,22 +76,20 @@ struct keyframe_open : public Command {
 	STR_HELP("Opens a keyframe list file")
 
 	void operator()(agi::Context *c) {
-		wxString path = to_wx(OPT_GET("Path/Last/Keyframes")->GetString());
-		wxString filename = wxFileSelector(
+		agi::fs::path filename = wxFileSelector(
 			_("Open keyframes file"),
-			path,
+			to_wx(OPT_GET("Path/Last/Keyframes")->GetString()),
 			""
 			,".txt",
 			_("All Supported Formats") + " (*.txt, *.pass, *.stats, *.log)|*.txt;*.pass;*.stats;*.log|" + _("All Files") + " (*.*)|*.*",
 			wxFD_FILE_MUST_EXIST | wxFD_OPEN);
 
 		if (filename.empty()) return;
-		OPT_SET("Path/Last/Keyframes")->SetString(from_wx(wxFileName(filename).GetPath()));
+		OPT_SET("Path/Last/Keyframes")->SetString(filename.parent_path().string());
 		c->videoController->LoadKeyframes(filename);
 	}
 };
 
-
 /// Saves the current keyframe list.
 struct keyframe_save : public Command {
 	CMD_NAME("keyframe/save")
@@ -107,10 +103,10 @@ struct keyframe_save : public Command {
 	}
 
 	void operator()(agi::Context *c) {
-		wxString path = to_wx(OPT_GET("Path/Last/Keyframes")->GetString());
-		wxString filename = wxFileSelector(_("Save keyframes file"),path,"","*.key.txt","Text files (*.txt)|*.txt",wxFD_OVERWRITE_PROMPT | wxFD_SAVE);
+		std::string path = OPT_GET("Path/Last/Keyframes")->GetString();
+		agi::fs::path filename = wxFileSelector(_("Save keyframes file"),to_wx(path),"","*.key.txt","Text files (*.txt)|*.txt",wxFD_OVERWRITE_PROMPT | wxFD_SAVE);
 		if (filename.empty()) return;
-		OPT_SET("Path/Last/Keyframes")->SetString(from_wx(wxFileName(filename).GetPath()));
+		OPT_SET("Path/Last/Keyframes")->SetString(filename.parent_path().string());
 		c->videoController->SaveKeyframes(filename);
 	}
 };
diff --git a/aegisub/src/command/recent.cpp b/aegisub/src/command/recent.cpp
index 6c6d4b33eb59e2c1e5e9ae7e57f962dab561d056..b448e4ea396665a2acde14680d98b0fbad79f431 100644
--- a/aegisub/src/command/recent.cpp
+++ b/aegisub/src/command/recent.cpp
@@ -34,11 +34,6 @@
 
 #include "../config.h"
 
-#include <sstream>
-
-#include <wx/event.h>
-#include <wx/msgdlg.h>
-
 #include "command.h"
 
 #include "../audio_controller.h"
@@ -49,6 +44,9 @@
 #include "../options.h"
 #include "../video_context.h"
 
+#include <wx/event.h>
+#include <wx/msgdlg.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-recent MRU (Most Recently Used) commands.
@@ -68,7 +66,7 @@ struct recent_audio_entry : public Command {
 
 	void operator()(agi::Context *c, int id) {
 		try {
-			c->audioController->OpenAudio(to_wx(config::mru->GetEntry("Audio", id)));
+			c->audioController->OpenAudio(config::mru->GetEntry("Audio", id));
 		}
 		catch (agi::UserCancelException const&) { }
 		catch (agi::Exception const& e) {
@@ -84,7 +82,7 @@ struct recent_keyframes_entry : public Command {
 	STR_HELP("Open recent keyframes")
 
 	void operator()(agi::Context *c, int id) {
-		c->videoController->LoadKeyframes(to_wx(config::mru->GetEntry("Keyframes", id)));
+		c->videoController->LoadKeyframes(config::mru->GetEntry("Keyframes", id));
 	}
 };
 
@@ -95,7 +93,7 @@ struct recent_subtitle_entry : public Command {
 	STR_HELP("Open recent subtitles")
 
 	void operator()(agi::Context *c, int id) {
-		wxGetApp().frame->LoadSubtitles(to_wx(config::mru->GetEntry("Subtitle", id)));
+		wxGetApp().frame->LoadSubtitles(config::mru->GetEntry("Subtitle", id));
 	}
 };
 
@@ -106,7 +104,7 @@ struct recent_timecodes_entry : public Command {
 	STR_HELP("Open recent timecodes")
 
 	void operator()(agi::Context *c, int id) {
-		c->videoController->LoadTimecodes(to_wx(config::mru->GetEntry("Timecodes", id)));
+		c->videoController->LoadTimecodes(config::mru->GetEntry("Timecodes", id));
 	}
 };
 
@@ -117,7 +115,7 @@ struct recent_video_entry : public Command {
 	STR_HELP("Open recent videos")
 
 	void operator()(agi::Context *c, int id) {
-		c->videoController->SetVideo(to_wx(config::mru->GetEntry("Video", id)));
+		c->videoController->SetVideo(config::mru->GetEntry("Video", id));
 	}
 };
 
@@ -132,12 +130,7 @@ public:
 	void operator()(agi::Context *c) {
 		T::operator()(c, id);
 	}
-	mru_wrapper(int id) : id(id) {
-		std::stringstream ss;
-		ss << T::name();
-		ss << id;
-		full_name = ss.str();
-	}
+	mru_wrapper(int id) : id(id) , full_name(T::name() + std::to_string(id)) { }
 };
 }
 /// @}
diff --git a/aegisub/src/command/subtitle.cpp b/aegisub/src/command/subtitle.cpp
index 6e06434545c0f169a9970de252ed7a05c45d6d1e..70011c37e26dfe8fc1cc1c2d034de882f06545c0 100644
--- a/aegisub/src/command/subtitle.cpp
+++ b/aegisub/src/command/subtitle.cpp
@@ -36,13 +36,6 @@
 
 #include "../config.h"
 
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-#include <wx/msgdlg.h>
-#include <wx/choicdlg.h>
-
-#include <libaegisub/charset_conv.h>
-
 #include "command.h"
 
 #include "../ass_dialogue.h"
@@ -64,6 +57,12 @@
 #include "../utils.h"
 #include "../video_context.h"
 
+#include <wx/filedlg.h>
+#include <wx/msgdlg.h>
+#include <wx/choicdlg.h>
+
+#include <libaegisub/charset_conv.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-subtitle Subtitle commands.
@@ -110,7 +109,6 @@ struct subtitle_find : public Command {
 	}
 };
 
-
 /// Find next match of last word.
 struct subtitle_find_next : public Command {
 	CMD_NAME("subtitle/find/next")
@@ -263,11 +261,15 @@ struct subtitle_open : public Command {
 	STR_HELP("Opens a subtitles file")
 
 	void operator()(agi::Context *c) {
-		wxString path = to_wx(OPT_GET("Path/Last/Subtitles")->GetString());
-		wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",SubtitleFormat::GetWildcards(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-		if (!filename.empty()) {
+		std::string filename = from_wx(wxFileSelector(
+			_("Open subtitles file"),
+			to_wx(OPT_GET("Path/Last/Subtitles")->GetString()),
+			"","",
+			to_wx(SubtitleFormat::GetWildcards(0)),
+			wxFD_OPEN | wxFD_FILE_MUST_EXIST));
+
+		if (!filename.empty())
 			wxGetApp().frame->LoadSubtitles(filename);
-		}
 	}
 };
 
@@ -284,7 +286,6 @@ struct subtitle_open_autosave : public Command {
 	}
 };
 
-
 /// Opens a subtitles file with a specific charset.
 struct subtitle_open_charset : public Command {
 	CMD_NAME("subtitle/open/charset")
@@ -293,21 +294,17 @@ struct subtitle_open_charset : public Command {
 	STR_HELP("Opens a subtitles file with a specific charset")
 
 	void operator()(agi::Context *c) {
-		// Initialize charsets
 		wxString path = to_wx(OPT_GET("Path/Last/Subtitles")->GetString());
+		wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",to_wx(SubtitleFormat::GetWildcards(0)),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+		if (filename.empty()) return;
 
-		// Get options and load
-		wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",SubtitleFormat::GetWildcards(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
-		if (!filename.empty()) {
-			wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
-			if (!charset.empty()) {
-				wxGetApp().frame->LoadSubtitles(filename,charset);
-			}
-		}
+		wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
+		if (charset.empty()) return;
+
+		wxGetApp().frame->LoadSubtitles(from_wx(filename), from_wx(charset));
 	}
 };
 
-
 /// Opens the subtitles from the current video file.
 struct subtitle_open_video : public Command {
 	CMD_NAME("subtitle/open/video")
@@ -339,12 +336,14 @@ struct subtitle_properties : public Command {
 	}
 };
 
-static void save_subtitles(agi::Context *c, wxString filename) {
+static void save_subtitles(agi::Context *c, agi::fs::path filename) {
 	if (filename.empty()) {
 		c->videoController->Stop();
 		wxString path = to_wx(OPT_GET("Path/Last/Subtitles")->GetString());
-		wxFileName origPath(c->ass->filename);
-		filename = wxFileSelector(_("Save subtitles file"), path, origPath.GetName() + ".ass", "ass", "Advanced Substation Alpha (*.ass)|*.ass", wxFD_SAVE | wxFD_OVERWRITE_PROMPT, c->parent);
+		filename = wxFileSelector(_("Save subtitles file"), path,
+			c->ass->filename.stem().wstring() + L".ass", "ass",
+			"Advanced Substation Alpha (*.ass)|*.ass",
+			wxFD_SAVE | wxFD_OVERWRITE_PROMPT, c->parent);
 		if (filename.empty()) return;
 	}
 
@@ -403,7 +402,7 @@ struct subtitle_select_all : public Command {
 		SubtitleSelection sel;
 		transform(c->ass->Line.begin(), c->ass->Line.end(),
 			inserter(sel, sel.begin()), cast<AssDialogue*>());
-		sel.erase(0);
+		sel.erase(nullptr);
 		c->selectionController->SetSelectedSet(sel);
 	}
 };
diff --git a/aegisub/src/command/time.cpp b/aegisub/src/command/time.cpp
index 2b3fc8c591757fae8dc27b7fea77a326b9a2a97d..a0223685b17b80752e5f3aeb3e650f7bb8b7a4ef 100644
--- a/aegisub/src/command/time.cpp
+++ b/aegisub/src/command/time.cpp
@@ -36,8 +36,6 @@
 
 #include "../config.h"
 
-#include <algorithm>
-
 #include "command.h"
 
 #include "../ass_dialogue.h"
@@ -54,6 +52,8 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+
 namespace {
 	using cmd::Command;
 
@@ -73,8 +73,7 @@ namespace {
 			size_t found = 0;
 			for (auto diag : c->ass->Line | agi::of_type<AssDialogue>()) {
 				if (sel.count(diag)) {
-					++found;
-					if (found == sel.size())
+					if (++found == sel.size())
 						return true;
 				}
 				else if (found)
@@ -197,15 +196,13 @@ struct time_snap_scene : public validate_video_loaded {
 		VideoContext *con = c->videoController;
 		if (!con->IsLoaded() || !con->KeyFramesLoaded()) return;
 
-		// Get frames
 		int curFrame = con->GetFrameN();
 		int prev = 0;
 		int next = 0;
 
 		const std::vector<int> &keyframes = con->GetKeyFrames();
-		if (curFrame < keyframes.front()) {
+		if (curFrame < keyframes.front())
 			next = keyframes.front();
-		}
 		else if (curFrame >= keyframes.back()) {
 			prev = keyframes.back();
 			next = con->GetLength();
@@ -222,17 +219,14 @@ struct time_snap_scene : public validate_video_loaded {
 			}
 		}
 
-		// Get times
 		int start_ms = con->TimeAtFrame(prev,agi::vfr::START);
 		int end_ms = con->TimeAtFrame(next-1,agi::vfr::END);
 
-		// Update rows
 		for (auto line : c->selectionController->GetSelectedSet()) {
 			line->Start = start_ms;
 			line->End = end_ms;
 		}
 
-		// Commit
 		c->ass->Commit(_("snap to scene"), AssFile::COMMIT_DIAG_TIME);
 	}
 };
diff --git a/aegisub/src/command/timecode.cpp b/aegisub/src/command/timecode.cpp
index 60f728755108d9d9be7ae4112a4b081d679e02a5..bc7feaa414f62ee6e89face2ef8e8bfd7030b893 100644
--- a/aegisub/src/command/timecode.cpp
+++ b/aegisub/src/command/timecode.cpp
@@ -36,16 +36,15 @@
 
 #include "../config.h"
 
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-
 #include "command.h"
 
-#include "../compat.h"
 #include "../include/aegisub/context.h"
 #include "../options.h"
 #include "../video_context.h"
 
+#include <boost/filesystem/path.hpp>
+#include <wx/filedlg.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-timecode Timecode commands.
@@ -79,15 +78,14 @@ struct timecode_open : public Command {
 	void operator()(agi::Context *c) {
 		wxString path = to_wx(OPT_GET("Path/Last/Timecodes")->GetString());
 		wxString str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
-		wxString filename = wxFileSelector(_("Open Timecodes File"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+		agi::fs::path filename = wxFileSelector(_("Open Timecodes File"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 		if (!filename.empty()) {
 			c->videoController->LoadTimecodes(filename);
-			OPT_SET("Path/Last/Timecodes")->SetString(from_wx(wxFileName(filename).GetPath()));
+			OPT_SET("Path/Last/Timecodes")->SetString(filename.parent_path().string());
 		}
 	}
 };
 
-
 /// Saves a VFR timecodes v2 file.
 struct timecode_save : public Command {
 	CMD_NAME("timecode/save")
@@ -103,10 +101,10 @@ struct timecode_save : public Command {
 	void operator()(agi::Context *c) {
 		wxString path = to_wx(OPT_GET("Path/Last/Timecodes")->GetString());
 		wxString str = _("All Supported Formats") + " (*.txt)|*.txt|" + _("All Files") + " (*.*)|*.*";
-		wxString filename = wxFileSelector(_("Save Timecodes File"),path,"","",str,wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+		agi::fs::path filename = wxFileSelector(_("Save Timecodes File"),path,"","",str,wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
 		if (!filename.empty()) {
 			c->videoController->SaveTimecodes(filename);
-			OPT_SET("Path/Last/Timecodes")->SetString(from_wx(wxFileName(filename).GetPath()));
+			OPT_SET("Path/Last/Timecodes")->SetString(filename.parent_path().string());
 		}
 	}
 };
diff --git a/aegisub/src/command/tool.cpp b/aegisub/src/command/tool.cpp
index 874e25455495620498e8908607c3e963abd7956a..7a8691c0396223f5d8613a8e9dab321ddd214b08 100644
--- a/aegisub/src/command/tool.cpp
+++ b/aegisub/src/command/tool.cpp
@@ -36,9 +36,6 @@
 
 #include "../config.h"
 
-#include <wx/msgdlg.h>
-#include <wx/utils.h>
-
 #include "command.h"
 
 #include "../compat.h"
@@ -56,6 +53,11 @@
 #include "../standard_paths.h"
 #include "../video_context.h"
 
+#include <libaegisub/fs.h>
+
+#include <wx/msgdlg.h>
+#include <wx/utils.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-tool Various tool and utilities
@@ -70,7 +72,7 @@ struct tool_assdraw : public Command {
 	STR_HELP("Launch ASSDraw3 tool for vector drawing")
 
 	void operator()(agi::Context *) {
-		wxExecute("\"" + StandardPaths::DecodePath("?data/ASSDraw3.exe") + "\"");
+		wxExecute("\"" + StandardPaths::DecodePath("?data/ASSDraw3.exe").wstring() + "\"");
 	}
 };
 
@@ -87,7 +89,6 @@ struct tool_export : public Command {
 	}
 };
 
-
 /// Open fonts collector.
 struct tool_font_collector : public Command {
 	CMD_NAME("tool/font_collector")
@@ -100,7 +101,6 @@ struct tool_font_collector : public Command {
 	}
 };
 
-
 /// Selects lines based on defined criteria.
 struct tool_line_select : public Command {
 	CMD_NAME("tool/line/select")
@@ -113,7 +113,6 @@ struct tool_line_select : public Command {
 	}
 };
 
-
 /// Changes resolution and modifies subtitles to conform to change.
 struct tool_resampleres : public Command {
 	CMD_NAME("tool/resampleres")
@@ -129,7 +128,6 @@ struct tool_resampleres : public Command {
 	}
 };
 
-
 /// Open styling assistant.
 struct tool_style_assistant : public Command {
 	CMD_NAME("tool/style/assistant")
@@ -186,7 +184,6 @@ struct tool_style_manager : public Command {
 	}
 };
 
-
 /// Open Kanji timer.
 struct tool_time_kanji : public Command {
 	CMD_NAME("tool/time/kanji")
@@ -199,7 +196,6 @@ struct tool_time_kanji : public Command {
 	}
 };
 
-
 /// Launch timing post-processor.
 struct tool_time_postprocess : public Command {
 	CMD_NAME("tool/time/postprocess")
@@ -212,7 +208,6 @@ struct tool_time_postprocess : public Command {
 	}
 };
 
-
 /// Open translation assistant.
 struct tool_translation_assistant : public Command {
 	CMD_NAME("tool/translation_assistant")
@@ -314,10 +309,9 @@ namespace cmd {
 		reg(new tool_time_kanji);
 		reg(new tool_time_postprocess);
 		reg(new tool_translation_assistant);
-#ifdef __WINDOWS__
-		if (wxFileName::FileExists(StandardPaths::DecodePath("?data/ASSDraw3.exe"))) {
+#ifdef _WIN32
+		if (agi::fs::FileExists(StandardPaths::DecodePath("?data/ASSDraw3.exe")))
 			reg(new tool_assdraw);
-		}
 #endif
 		reg(new tool_translation_assistant_commit);
 		reg(new tool_translation_assistant_preview);
diff --git a/aegisub/src/command/video.cpp b/aegisub/src/command/video.cpp
index 4850ebdbb052d69638161ddf487ccb575658c86c..7608e24e6ee5c99524e9563b1a62a983f79f183d 100644
--- a/aegisub/src/command/video.cpp
+++ b/aegisub/src/command/video.cpp
@@ -38,12 +38,6 @@
 
 #include "command.h"
 
-#include <wx/clipbrd.h>
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-#include <wx/msgdlg.h>
-#include <wx/textdlg.h>
-
 #include "../ass_dialogue.h"
 #include "../ass_time.h"
 #include "../compat.h"
@@ -57,7 +51,6 @@
 #include "../main.h"
 #include "../options.h"
 #include "../selection_controller.h"
-#include "../standard_paths.h"
 #include "../utils.h"
 #include "../video_box.h"
 #include "../video_context.h"
@@ -65,6 +58,20 @@
 #include "../video_frame.h"
 #include "../video_slider.h"
 
+#include <libaegisub/fs.h>
+#include <libaegisub/path.h>
+#include <libaegisub/util.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/format.hpp>
+
+#include <wx/clipbrd.h>
+#include <wx/filedlg.h>
+#include <wx/msgdlg.h>
+#include <wx/textdlg.h>
+
 namespace {
 	using cmd::Command;
 /// @defgroup cmd-video Video commands.
@@ -118,46 +125,30 @@ struct video_aspect_custom : public validator_video_loaded {
 	void operator()(agi::Context *c) {
 		c->videoController->Stop();
 
-		wxString value = wxGetTextFromUser(_("Enter aspect ratio in either:\n  decimal (e.g. 2.35)\n  fractional (e.g. 16:9)\n  specific resolution (e.g. 853x480)"),_("Enter aspect ratio"),AegiFloatToString(c->videoController->GetAspectRatioValue()));
-		if (value.IsEmpty()) return;
+		std::string value = from_wx(wxGetTextFromUser(
+			_("Enter aspect ratio in either:\n  decimal (e.g. 2.35)\n  fractional (e.g. 16:9)\n  specific resolution (e.g. 853x480)"),
+			_("Enter aspect ratio"),
+			std::to_wstring(c->videoController->GetAspectRatioValue())));
+		if (value.empty()) return;
 
-		value.MakeLower();
-
-		// Process text
-		double numval;
-		if (value.ToDouble(&numval)) {
+		double numval = 0;
+		if (agi::util::try_parse(value, &numval)) {
 			//Nothing to see here, move along
 		}
 		else {
-			double a,b;
-			int pos=0;
-			bool scale=false;
-
-			//Why bloat using Contains when we can just check the output of Find?
-			pos = value.Find(':');
-			if (pos==wxNOT_FOUND) pos = value.Find('/');
-			if (pos==wxNOT_FOUND&&value.Contains('x')) {
-				pos = value.Find('x');
-				scale=true;
-			}
-
-			if (pos>0) {
-				wxString num = value.Left(pos);
-				wxString denum = value.Mid(pos+1);
-				if (num.ToDouble(&a) && denum.ToDouble(&b) && b!=0) {
-					numval = a/b;
-					if (scale) c->videoDisplay->SetZoom(b / c->videoController->GetHeight());
-				}
+			std::vector<std::string> chunks;
+			split(chunks, value, boost::is_any_of(":/xX"));
+			if (chunks.size() == 2) {
+				double num, den;
+				if (agi::util::try_parse(chunks[0], &num) && agi::util::try_parse(chunks[1], &den))
+					numval = num / den;
 			}
-			else numval = 0.0;
 		}
 
-		// Sanity check
-		if (numval < 0.5 || numval > 5.0) wxMessageBox(_("Invalid value! Aspect ratio must be between 0.5 and 5.0."),_("Invalid Aspect Ratio"),wxOK | wxICON_ERROR | wxCENTER);
-
-		// Set value
+		if (numval < 0.5 || numval > 5.0)
+			wxMessageBox(_("Invalid value! Aspect ratio must be between 0.5 and 5.0."),_("Invalid Aspect Ratio"),wxOK | wxICON_ERROR | wxCENTER);
 		else {
-			c->videoController->SetAspectRatio(4,numval);
+			c->videoController->SetAspectRatio(4, numval);
 			wxGetApp().frame->SetDisplayMode(1,-1);
 		}
 	}
@@ -473,19 +464,18 @@ struct video_frame_prev_large : public validator_video_loaded {
 
 static void save_snapshot(agi::Context *c, bool raw) {
 	static const agi::OptionValue* ssPath = OPT_GET("Path/Screenshot");
-	wxString option = to_wx(ssPath->GetString());
-	wxFileName videoFile(c->videoController->GetVideoName());
-	wxString basepath;
+	std::string option = ssPath->GetString();
+	agi::fs::path basepath;
 
 	// Is it a path specifier and not an actual fixed path?
 	if (option[0] == '?') {
 		// If dummy video is loaded, we can't save to the video location
-		if (option.StartsWith("?video") && (c->videoController->GetVideoName().Find("?dummy") != wxNOT_FOUND)) {
+		if (boost::starts_with(option, "?video") && boost::starts_with(c->videoController->GetVideoName().string(), "?dummy")) {
 			// So try the script location instead
 			option = "?script";
 		}
 		// Find out where the ?specifier points to
-		basepath = StandardPaths::DecodePath(option);
+		basepath = config::path->Decode(option);
 		// If where ever that is isn't defined, we can't save there
 		if ((basepath == "\\") || (basepath == "/")) {
 			// So save to the current user's home dir instead
@@ -493,17 +483,18 @@ static void save_snapshot(agi::Context *c, bool raw) {
 		}
 	}
 	// Actual fixed (possibly relative) path, decode it
-	else basepath = DecodeRelativePath(option,StandardPaths::DecodePath("?user/"));
-	basepath += "/" + videoFile.GetName();
+	else
+		basepath = config::path->MakeAbsolute(option, "?user/");
+	basepath /= c->videoController->GetVideoName().stem();
 
 	// Get full path
 	int session_shot_count = 1;
-	wxString path;
+	std::string path;
 	do {
-		path = wxString::Format("%s_%03d_%d.png", basepath, session_shot_count++, c->videoController->GetFrameN());
-	} while (wxFileName::FileExists(path));
+		path = str(boost::format("%s_%03d_%d.png") % basepath % session_shot_count++ % c->videoController->GetFrameN());
+	} while (agi::fs::FileExists(path));
 
-	c->videoController->GetFrame(c->videoController->GetFrameN(), raw)->GetImage().SaveFile(path,wxBITMAP_TYPE_PNG);
+	c->videoController->GetFrame(c->videoController->GetFrameN(), raw)->GetImage().SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
 }
 
 /// Save the current video frame, with subtitles (if any)
@@ -585,10 +576,10 @@ struct video_open : public Command {
 		wxString path = to_wx(OPT_GET("Path/Last/Video")->GetString());
 		wxString str = _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|"
 					 + _("All Files") + " (*.*)|*.*";
-		wxString filename = wxFileSelector(_("Open video file"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+		agi::fs::path filename = wxFileSelector(_("Open video file"),path,"","",str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 		if (!filename.empty()) {
 			c->videoController->SetVideo(filename);
-			OPT_SET("Path/Last/Video")->SetString(from_wx(wxFileName(filename).GetPath()));
+			OPT_SET("Path/Last/Video")->SetString(filename.parent_path().string());
 		}
 	}
 };
@@ -601,7 +592,7 @@ struct video_open_dummy : public Command {
 	STR_HELP("Opens a video clip with solid color")
 
 	void operator()(agi::Context *c) {
-		wxString fn = DialogDummyVideo::CreateDummyVideo(c->parent);
+		std::string fn = DialogDummyVideo::CreateDummyVideo(c->parent);
 		if (!fn.empty())
 			c->videoController->SetVideo(fn);
 	}
diff --git a/aegisub/src/compat.cpp b/aegisub/src/compat.cpp
index c6b0bf96d4c3c502ed9b8eabe5408c780487d225..7ea800e1d22816d25687816f766bb8f2f116157e 100644
--- a/aegisub/src/compat.cpp
+++ b/aegisub/src/compat.cpp
@@ -1,20 +1,21 @@
 #include "compat.h"
+
 #include "options.h"
 
 #include <algorithm>
 
-template<typename T>
-wxArrayString to_wxAS(T const& src) {
+wxArrayString lagi_MRU_wxAS(std::string const& list) {
+	auto const& vec = *config::mru->Get(list);
 	wxArrayString ret;
-	ret.reserve(src.size());
-	transform(src.begin(), src.end(), std::back_inserter(ret), (wxString (*)(std::string const&))to_wx);
+	ret.reserve(vec.size());
+	transform(vec.begin(), vec.end(), std::back_inserter(ret),
+		[](agi::fs::path const& p) { return p.wstring(); });
 	return ret;
 }
 
-wxArrayString lagi_MRU_wxAS(const wxString &list) {
-	return to_wxAS(*config::mru->Get(from_wx(list)));
-}
-
 wxArrayString to_wx(std::vector<std::string> const& vec) {
-	return to_wxAS(vec);
+	wxArrayString ret;
+	ret.reserve(vec.size());
+	transform(vec.begin(), vec.end(), std::back_inserter(ret), (wxString (*)(std::string const&))to_wx);
+	return ret;
 }
diff --git a/aegisub/src/compat.h b/aegisub/src/compat.h
index b4a179aba2c7757a877761e92328734deaac7723..3d34e1e9430e6484da7f0b4db3d70b796053d598 100644
--- a/aegisub/src/compat.h
+++ b/aegisub/src/compat.h
@@ -1,3 +1,5 @@
+#pragma once
+
 #include <string>
 #include <vector>
 
@@ -14,4 +16,4 @@ wxArrayString to_wx(std::vector<std::string> const& vec);
 inline agi::Color from_wx(wxColour color) { return agi::Color(color.Red(), color.Green(), color.Blue(), 255 - color.Alpha()); }
 inline std::string from_wx(wxString const& str) { return std::string(str.utf8_str()); }
 
-wxArrayString lagi_MRU_wxAS(const wxString &list);
+wxArrayString lagi_MRU_wxAS(std::string const& list);
diff --git a/aegisub/src/dialog_attachments.cpp b/aegisub/src/dialog_attachments.cpp
index 920078f407716e9b55e6aba6e61a2f2cf4d7e9d2..dbb7dd92473355127e6c6799e957506fcd934f21 100644
--- a/aegisub/src/dialog_attachments.cpp
+++ b/aegisub/src/dialog_attachments.cpp
@@ -39,7 +39,6 @@
 #include <wx/button.h>
 #include <wx/dirdlg.h>
 #include <wx/filedlg.h>
-#include <wx/filename.h>
 #include <wx/listctrl.h>
 #include <wx/sizer.h>
 
@@ -53,95 +52,70 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
-enum {
-	BUTTON_ATTACH_FONT = 1300,
-	BUTTON_ATTACH_GRAPHICS,
-	BUTTON_EXTRACT,
-	BUTTON_DELETE,
-	ATTACHMENT_LIST
-};
-
 DialogAttachments::DialogAttachments(wxWindow *parent, AssFile *ass)
-: wxDialog(parent,-1,_("Attachment List"),wxDefaultPosition,wxDefaultSize,wxDEFAULT_DIALOG_STYLE)
+: wxDialog(parent, -1, _("Attachment List"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
 , ass(ass)
 {
 	SetIcon(GETICON(attach_button_16));
 
-	listView = new wxListView(this,ATTACHMENT_LIST,wxDefaultPosition,wxSize(500,200));
+	listView = new wxListView(this, -1, wxDefaultPosition, wxSize(500, 200));
 	UpdateList();
 
-	// Buttons
-	extractButton = new wxButton(this,BUTTON_EXTRACT,_("E&xtract"));
-	deleteButton = new wxButton(this,BUTTON_DELETE,_("&Delete"));
+	auto attachFont = new wxButton(this, -1, _("Attach &Font"));
+	auto attachGraphics = new wxButton(this, -1, _("Attach &Graphics"));
+	extractButton = new wxButton(this, -1, _("E&xtract"));
+	deleteButton = new wxButton(this, -1, _("&Delete"));
 	extractButton->Enable(false);
 	deleteButton->Enable(false);
 
-	// Buttons sizer
-	wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
-	buttonSizer->Add(new wxButton(this,BUTTON_ATTACH_FONT,_("Attach &Font")),1,0,0);
-	buttonSizer->Add(new wxButton(this,BUTTON_ATTACH_GRAPHICS,_("Attach &Graphics")),1,0,0);
-	buttonSizer->Add(extractButton,1,0,0);
-	buttonSizer->Add(deleteButton,1,0,0);
-	buttonSizer->Add(new HelpButton(this,"Attachment Manager"),1,wxLEFT,5);
-	buttonSizer->Add(new wxButton(this,wxID_CANCEL,_("&Close")),1,0,0);
-
-	// Main sizer
-	wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
-	mainSizer->Add(listView,1,wxTOP | wxLEFT | wxRIGHT | wxEXPAND,5);
-	mainSizer->Add(buttonSizer,0,wxALL | wxEXPAND,5);
+	auto buttonSizer = new wxBoxSizer(wxHORIZONTAL);
+	buttonSizer->Add(attachFont, 1);
+	buttonSizer->Add(attachGraphics, 1);
+	buttonSizer->Add(extractButton, 1);
+	buttonSizer->Add(deleteButton, 1);
+	buttonSizer->Add(new HelpButton(this, "Attachment Manager"), 1, wxLEFT, 5);
+	buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("&Close")), 1);
+
+	auto mainSizer = new wxBoxSizer(wxVERTICAL);
+	mainSizer->Add(listView, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5);
+	mainSizer->Add(buttonSizer, 0, wxALL | wxEXPAND, 5);
 	SetSizerAndFit(mainSizer);
 	CenterOnParent();
+
+	attachFont->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogAttachments::OnAttachFont, this);
+	attachGraphics->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogAttachments::OnAttachGraphics, this);
+	extractButton->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogAttachments::OnExtract, this);
+	deleteButton->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogAttachments::OnDelete, this);
+
+	listView->Bind(wxEVT_COMMAND_LIST_ITEM_SELECTED, &DialogAttachments::OnListClick, this);
+	listView->Bind(wxEVT_COMMAND_LIST_ITEM_DESELECTED, &DialogAttachments::OnListClick, this);
+	listView->Bind(wxEVT_COMMAND_LIST_ITEM_FOCUSED, &DialogAttachments::OnListClick, this);
 }
 
 void DialogAttachments::UpdateList() {
 	listView->ClearAll();
 
-	// Insert list columns
 	listView->InsertColumn(0, _("Attachment name"), wxLIST_FORMAT_LEFT, 280);
 	listView->InsertColumn(1, _("Size"), wxLIST_FORMAT_LEFT, 100);
 	listView->InsertColumn(2, _("Group"), wxLIST_FORMAT_LEFT, 100);
 
-	// Fill list
 	for (auto attach : ass->Line | agi::of_type<AssAttachment>()) {
 		int row = listView->GetItemCount();
-		listView->InsertItem(row,attach->GetFileName(true));
-		listView->SetItem(row,1,PrettySize(attach->GetSize()));
-		listView->SetItem(row,2,attach->GroupHeader());
-		listView->SetItemPtrData(row,wxPtrToUInt(attach));
+		listView->InsertItem(row, to_wx(attach->GetFileName(true)));
+		listView->SetItem(row, 1, PrettySize(attach->GetSize()));
+		listView->SetItem(row, 2, to_wx(attach->GroupHeader()));
+		listView->SetItemPtrData(row, wxPtrToUInt(attach));
 	}
 }
 
-BEGIN_EVENT_TABLE(DialogAttachments,wxDialog)
-	EVT_BUTTON(BUTTON_ATTACH_FONT,DialogAttachments::OnAttachFont)
-	EVT_BUTTON(BUTTON_ATTACH_GRAPHICS,DialogAttachments::OnAttachGraphics)
-	EVT_BUTTON(BUTTON_EXTRACT,DialogAttachments::OnExtract)
-	EVT_BUTTON(BUTTON_DELETE,DialogAttachments::OnDelete)
-	EVT_LIST_ITEM_SELECTED(ATTACHMENT_LIST,DialogAttachments::OnListClick)
-	EVT_LIST_ITEM_DESELECTED(ATTACHMENT_LIST,DialogAttachments::OnListClick)
-	EVT_LIST_ITEM_FOCUSED(ATTACHMENT_LIST,DialogAttachments::OnListClick)
-END_EVENT_TABLE()
-
-void DialogAttachments::AttachFile(wxFileDialog &diag, AssEntryGroup group, wxString const& commit_msg) {
+void DialogAttachments::AttachFile(wxFileDialog &diag, wxString const& commit_msg) {
 	if (diag.ShowModal() == wxID_CANCEL) return;
 
-	wxArrayString filenames;
-	diag.GetFilenames(filenames);
-
 	wxArrayString paths;
 	diag.GetPaths(paths);
 
-	// Create attachments
-	for (size_t i = 0; i < filenames.size(); ++i) {
-		AssAttachment *newAttach = new AssAttachment(filenames[i], group);
-		try {
-			newAttach->Import(paths[i]);
-		}
-		catch (...) {
-			delete newAttach;
-			return;
-		}
-		ass->InsertLine(newAttach);
-	}
+	for (auto const& fn : paths)
+		ass->InsertAttachment(agi::fs::path(fn));
 
 	ass->Commit(commit_msg, AssFile::COMMIT_ATTACHMENT);
 
@@ -154,43 +128,41 @@ void DialogAttachments::OnAttachFont(wxCommandEvent &) {
 		to_wx(OPT_GET("Path/Fonts Collector Destination")->GetString()), "", "Font Files (*.ttf)|*.ttf",
 		wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
 
-	AttachFile(diag, ENTRY_FONT, _("attach font file"));
+	AttachFile(diag, _("attach font file"));
 }
 
 void DialogAttachments::OnAttachGraphics(wxCommandEvent &) {
 	wxFileDialog diag(this,
 		_("Choose file to be attached"),
 		"", "",
-		"Graphic Files (*.bmp,*.gif,*.jpg,*.ico,*.wmf)|*.bmp;*.gif;*.jpg;*.ico;*.wmf",
+		"Graphic Files (*.bmp, *.gif, *.jpg, *.ico, *.wmf)|*.bmp;*.gif;*.jpg;*.ico;*.wmf",
 		wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
 
-	AttachFile(diag, ENTRY_GRAPHIC, _("attach graphics file"));
+	AttachFile(diag, _("attach graphics file"));
 }
 
 void DialogAttachments::OnExtract(wxCommandEvent &) {
 	int i = listView->GetFirstSelected();
 	if (i == -1) return;
 
-	wxString path;
+	std::string path;
 	bool fullPath = false;
 
 	// Multiple or single?
 	if (listView->GetNextSelected(i) != -1)
-		path = wxDirSelector(_("Select the path to save the files to:"),to_wx(OPT_GET("Path/Fonts Collector Destination")->GetString())) + "/";
+		path = from_wx(wxDirSelector(_("Select the path to save the files to:"), to_wx(OPT_GET("Path/Fonts Collector Destination")->GetString()))) + "/";
 	else {
-		// Default path
-		wxString defPath = ((AssAttachment*)wxUIntToPtr(listView->GetItemData(i)))->GetFileName();
-		path = wxFileSelector(
+		path = from_wx(wxFileSelector(
 			_("Select the path to save the file to:"),
 			to_wx(OPT_GET("Path/Fonts Collector Destination")->GetString()),
-			defPath,
+			to_wx(((AssAttachment*)wxUIntToPtr(listView->GetItemData(i)))->GetFileName()),
 			".ttf",
 			"Font Files (*.ttf)|*.ttf",
 			wxFD_SAVE | wxFD_OVERWRITE_PROMPT,
-			this);
+			this));
 		fullPath = true;
 	}
-	if (!path) return;
+	if (path.empty()) return;
 
 	// Loop through items in list
 	while (i != -1) {
@@ -201,7 +173,7 @@ void DialogAttachments::OnExtract(wxCommandEvent &) {
 }
 
 void DialogAttachments::OnDelete(wxCommandEvent &) {
-	int i = listView->GetFirstSelected();
+	auto i = listView->GetFirstSelected();
 	if (i == -1) return;
 
 	while (i != -1) {
diff --git a/aegisub/src/dialog_attachments.h b/aegisub/src/dialog_attachments.h
index a90288a07581dabff1ba719c96b329a97f4607dd..3a444c9bb39a9d7c4d6408e2f0e9bdbdf2648e67 100644
--- a/aegisub/src/dialog_attachments.h
+++ b/aegisub/src/dialog_attachments.h
@@ -39,8 +39,6 @@ class wxListEvent;
 
 #include <wx/dialog.h>
 
-#include "ass_entry.h"
-
 class DialogAttachments : public wxDialog {
 	AssFile *ass;
 
@@ -55,10 +53,8 @@ class DialogAttachments : public wxDialog {
 	void OnListClick(wxListEvent &event);
 
 	void UpdateList();
-	void AttachFile(wxFileDialog &diag, AssEntryGroup group, wxString const& commit_msg);
+	void AttachFile(wxFileDialog &diag, wxString const& commit_msg);
 
 public:
 	DialogAttachments(wxWindow *parent, AssFile *ass);
-
-	DECLARE_EVENT_TABLE()
 };
diff --git a/aegisub/src/dialog_automation.cpp b/aegisub/src/dialog_automation.cpp
index 33b379823c1e9ac74dd153a5c914a28e852a7652..869d7c15a782639c7caa6dfd317eb2359e6ef482 100644
--- a/aegisub/src/dialog_automation.cpp
+++ b/aegisub/src/dialog_automation.cpp
@@ -32,7 +32,6 @@
 /// @ingroup secondary_ui
 ///
 
-
 #include "config.h"
 
 #include "dialog_automation.h"
@@ -51,10 +50,10 @@
 
 #include <wx/button.h>
 #include <wx/filedlg.h>
-#include <wx/filename.h>
 #include <wx/listctrl.h>
 #include <wx/log.h>
 #include <wx/msgdlg.h>
+#include <wx/sizer.h>
 
 using std::placeholders::_1;
 
@@ -147,9 +146,9 @@ void DialogAutomation::RebuildList()
 
 void DialogAutomation::SetScriptInfo(int i, Automation4::Script *script)
 {
-	list->SetItem(i, 1, script->GetName());
-	list->SetItem(i, 2, wxFileName(script->GetFilename()).GetFullName());
-	list->SetItem(i, 3, script->GetDescription());
+	list->SetItem(i, 1, to_wx(script->GetName()));
+	list->SetItem(i, 2, script->GetPrettyFilename().wstring());
+	list->SetItem(i, 3, to_wx(script->GetDescription()));
 	if (!script->GetLoadedState())
 		list->SetItemBackgroundColour(i, wxColour(255,128,128));
 	else
@@ -178,10 +177,10 @@ void DialogAutomation::UpdateDisplay()
 }
 
 template<class Container>
-static bool has_file(Container const& c, wxFileName const& fn)
+static bool has_file(Container const& c, agi::fs::path const& fn)
 {
 	return any_of(c.begin(), c.end(),
-		[&](const Automation4::Script *s) { return fn.SameAs(s->GetFilename()); });
+		[&](const Automation4::Script *s) { return fn == s->GetFilename(); });
 }
 
 void DialogAutomation::OnAdd(wxCommandEvent &)
@@ -190,7 +189,7 @@ void DialogAutomation::OnAdd(wxCommandEvent &)
 		_("Add Automation script"),
 		to_wx(OPT_GET("Path/Last/Automation")->GetString()),
 		"",
-		Automation4::ScriptFactory::GetWildcardStr(),
+		to_wx(Automation4::ScriptFactory::GetWildcardStr()),
 		wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
 
 	if (diag.ShowModal() == wxID_CANCEL) return;
@@ -199,15 +198,15 @@ void DialogAutomation::OnAdd(wxCommandEvent &)
 	diag.GetPaths(fnames);
 
 	for (auto const& fname : fnames) {
-		wxFileName fnpath(fname);
-		OPT_SET("Path/Last/Automation")->SetString(from_wx(fnpath.GetPath()));
+		agi::fs::path fnpath(fname);
+		OPT_SET("Path/Last/Automation")->SetString(fnpath.parent_path().string());
 
 		if (has_file(local_manager->GetScripts(), fnpath) || has_file(global_manager->GetScripts(), fnpath)) {
 			wxLogError("Script '%s' is already loaded", fname);
 			continue;
 		}
 
-		local_manager->Add(Automation4::ScriptFactory::CreateFromFile(fname, true));
+		local_manager->Add(Automation4::ScriptFactory::CreateFromFile(fnpath, true));
 	}
 }
 
@@ -234,19 +233,19 @@ void DialogAutomation::OnReload(wxCommandEvent &)
 }
 
 static wxString fac_to_str(const Automation4::ScriptFactory* f) {
-	return wxString::Format("- %s (%s)", f->GetEngineName(), f->GetFilenamePattern());
+	return wxString::Format("- %s (%s)", to_wx(f->GetEngineName()), to_wx(f->GetFilenamePattern()));
 }
 
 static wxString cmd_to_str(const cmd::Command *f, agi::Context *c) {
-	return wxString::Format(_("    Macro: %s (%s)"), f->StrDisplay(c), f->name());
+	return wxString::Format(_("    Macro: %s (%s)"), f->StrDisplay(c), to_wx(f->name()));
 }
 
 static wxString filt_to_str(const Automation4::ExportFilter* f) {
-	return wxString::Format(_("    Export filter: %s"), f->GetName());
+	return wxString::Format(_("    Export filter: %s"), to_wx(f->GetName()));
 }
 
 static wxString form_to_str(const SubtitleFormat* f) {
-	return wxString::Format(_("    Subtitle format handler: %s"), f->GetName());
+	return wxString::Format(_("    Subtitle format handler: %s"), to_wx(f->GetName()));
 }
 
 void DialogAutomation::OnInfo(wxCommandEvent &)
@@ -272,7 +271,7 @@ void DialogAutomation::OnInfo(wxCommandEvent &)
 			ei->script->GetDescription(),
 			ei->script->GetAuthor(),
 			ei->script->GetVersion(),
-			ei->script->GetFilename(),
+			ei->script->GetFilename().wstring(),
 			ei->script->GetLoadedState() ? _("Correctly loaded") : _("Failed to load")));
 
 		transform(ei->script->GetMacros(), append_info, std::bind(cmd_to_str, _1, context));
diff --git a/aegisub/src/dialog_autosave.cpp b/aegisub/src/dialog_autosave.cpp
index 75beee9b182281b901eb486688a176a8ffd74929..5808ea2250f22bf0df1310a300407cfe5ba7e88a 100644
--- a/aegisub/src/dialog_autosave.cpp
+++ b/aegisub/src/dialog_autosave.cpp
@@ -90,7 +90,7 @@ DialogAutosave::DialogAutosave(wxWindow *parent)
 }
 
 void DialogAutosave::Populate(std::map<wxString, AutosaveFile> &files_map, std::string const& path, wxString const& filter, wxString const& name_fmt) {
-	wxString directory(StandardPaths::DecodePath(to_wx(path)));
+	wxString directory(StandardPaths::DecodePath(path).wstring());
 
 	wxDir dir;
 	if (!dir.Open(directory)) return;
@@ -130,12 +130,12 @@ void DialogAutosave::OnSelectFile(wxCommandEvent&) {
 	version_list->SetSelection(0);
 }
 
-wxString DialogAutosave::ChosenFile() const {
+std::string DialogAutosave::ChosenFile() const {
 	int sel_file = file_list->GetSelection();
 	if (sel_file < 0) return "";
 
 	int sel_version = version_list->GetSelection();
 	if (sel_version < 0) return "";
 
-	return files[sel_file].versions[sel_version].filename;
+	return from_wx(files[sel_file].versions[sel_version].filename);
 }
diff --git a/aegisub/src/dialog_autosave.h b/aegisub/src/dialog_autosave.h
index 4e9b9d15b9cbc5f55f37effaa8c5a9164d6f0e93..3d0187ba0fc1f962d16d5caa61a32c98612ec8a4 100644
--- a/aegisub/src/dialog_autosave.h
+++ b/aegisub/src/dialog_autosave.h
@@ -49,5 +49,5 @@ class DialogAutosave : public wxDialog {
 
 public:
 	DialogAutosave(wxWindow *parent);
-	wxString ChosenFile() const;
+	std::string ChosenFile() const;
 };
diff --git a/aegisub/src/dialog_detached_video.cpp b/aegisub/src/dialog_detached_video.cpp
index 0a64683ee043d98e8342637d096e64c98255124c..0291a26906c5b5dda3c47e47acb9301a47b38f54 100644
--- a/aegisub/src/dialog_detached_video.cpp
+++ b/aegisub/src/dialog_detached_video.cpp
@@ -38,7 +38,6 @@
 
 #include "include/aegisub/context.h"
 #include "include/aegisub/hotkey.h"
-
 #include "options.h"
 #include "persist_location.h"
 #include "utils.h"
@@ -46,7 +45,8 @@
 #include "video_context.h"
 #include "video_display.h"
 
-#include <wx/filename.h>
+#include <boost/filesystem/path.hpp>
+
 #include <wx/sizer.h>
 #include <wx/display.h> /// Must be included last.
 
@@ -60,7 +60,7 @@ DialogDetachedVideo::DialogDetachedVideo(agi::Context *context)
 	// Set obscure stuff
 	SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES);
 
-	SetTitle(wxString::Format(_("Video: %s"), wxFileName(context->videoController->GetVideoName()).GetFullName()));
+	SetTitle(wxString::Format(_("Video: %s"), context->videoController->GetVideoName().filename().wstring()));
 
 	old_display->Unload();
 
diff --git a/aegisub/src/dialog_dummy_video.cpp b/aegisub/src/dialog_dummy_video.cpp
index 151982ae444fb899fcef2e16f1e0b1eec182a198..09c45fba186d87a5e40159cd1af1dc4b73426a61 100644
--- a/aegisub/src/dialog_dummy_video.cpp
+++ b/aegisub/src/dialog_dummy_video.cpp
@@ -158,7 +158,7 @@ void DialogDummyVideo::UpdateLengthDisplay() {
 	length_display->SetLabel(wxString::Format(_("Resulting duration: %s"), AssTime(length / fps * 1000).GetAssFormated(true)));
 }
 
-wxString DialogDummyVideo::CreateDummyVideo(wxWindow *parent) {
+std::string DialogDummyVideo::CreateDummyVideo(wxWindow *parent) {
 	DialogDummyVideo dlg(parent);
 	if (dlg.ShowModal() != wxID_OK)
 		return "";
diff --git a/aegisub/src/dialog_dummy_video.h b/aegisub/src/dialog_dummy_video.h
index 4a06d1bd4d47e2e56f0648472f5914da06747db1..667d4904520c4d86dd6860599abaa41f3809c43b 100644
--- a/aegisub/src/dialog_dummy_video.h
+++ b/aegisub/src/dialog_dummy_video.h
@@ -46,5 +46,5 @@ class DialogDummyVideo : public wxDialog {
 	void UpdateLengthDisplay();
 
 public:
-	static wxString CreateDummyVideo(wxWindow *parent);
+	static std::string CreateDummyVideo(wxWindow *parent);
 };
diff --git a/aegisub/src/dialog_export.cpp b/aegisub/src/dialog_export.cpp
index 117a7eefbb18220040fcef557739c195dced116d..61ec52e03fc60f46c53ac8685af0e87506679862 100644
--- a/aegisub/src/dialog_export.cpp
+++ b/aegisub/src/dialog_export.cpp
@@ -40,11 +40,15 @@
 #include "ass_file.h"
 #include "compat.h"
 #include "include/aegisub/context.h"
-#include "charset_conv.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
 #include "subtitle_format.h"
 
+#include <libaegisub/charset_conv.h>
+
+#include <algorithm>
+#include <boost/tokenizer.hpp>
+
 #include <wx/button.h>
 #include <wx/checklst.h>
 #include <wx/choice.h>
@@ -53,7 +57,6 @@
 #include <wx/sizer.h>
 #include <wx/stattext.h>
 #include <wx/textctrl.h>
-#include <wx/tokenzr.h>
 
 // Swap the items at idx and idx + 1
 static void swap(wxCheckListBox *list, int idx, int sel_dir) {
@@ -78,21 +81,18 @@ DialogExport::DialogExport(agi::Context *c)
 	SetIcon(GETICON(export_menu_16));
 	SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
 
-	wxArrayString filters = exporter->GetAllFilterNames();
-	filter_list = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(200, 100), filters);
+	std::vector<std::string> filters = exporter->GetAllFilterNames();
+	filter_list = new wxCheckListBox(this, -1, wxDefaultPosition, wxSize(200, 100), to_wx(filters));
 	filter_list->Bind(wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, [=](wxCommandEvent&) { RefreshOptions(); });
 	filter_list->Bind(wxEVT_COMMAND_LISTBOX_SELECTED, &DialogExport::OnChange, this);
 
 	// Get selected filters
-	wxString selected = c->ass->GetScriptInfo("Export filters");
-	wxStringTokenizer token(selected, "|");
-	while (token.HasMoreTokens()) {
-		wxString cur = token.GetNextToken();
-		if (!cur.empty()) {
-			int idx = filters.Index(cur);
-			if (idx != wxNOT_FOUND)
-				filter_list->Check(idx);
-		}
+	std::string selected = c->ass->GetScriptInfo("Export filters");
+	boost::char_separator<char> sep("|");
+	for (auto const& token : boost::tokenizer<boost::char_separator<char>>(selected, sep)) {
+		auto it = find(begin(filters), end(filters), token);
+		if (it != end(filters))
+			filter_list->Check(distance(begin(filters), it));
 	}
 
 	wxButton *btn_up = new wxButton(this, -1, _("Move &Up"), wxDefaultPosition, wxSize(90, -1));
@@ -119,7 +119,7 @@ DialogExport::DialogExport(agi::Context *c)
 	wxSizer *charset_list_sizer = new wxBoxSizer(wxHORIZONTAL);
 	charset_list_sizer->Add(charset_list_label, wxSizerFlags().Center().Border(wxRIGHT));
 	charset_list_sizer->Add(charset_list, wxSizerFlags(1).Expand());
-	if (!charset_list->SetStringSelection(c->ass->GetScriptInfo("Export Encoding")))
+	if (!charset_list->SetStringSelection(to_wx(c->ass->GetScriptInfo("Export Encoding"))))
 		charset_list->SetStringSelection("Unicode (UTF-8)");
 
 	wxSizer *top_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Filters"));
@@ -148,30 +148,32 @@ DialogExport::DialogExport(agi::Context *c)
 }
 
 DialogExport::~DialogExport() {
-	wxString infoList;
+	std::string infoList;
 	for (size_t i = 0; i < filter_list->GetCount(); ++i) {
-		if (filter_list->IsChecked(i))
-			infoList += filter_list->GetString(i) + "|";
+		if (filter_list->IsChecked(i)) {
+			if (!infoList.empty())
+				infoList += "|";
+			infoList += from_wx(filter_list->GetString(i));
+		}
 	}
-	if (!infoList.empty()) infoList.RemoveLast();
 	c->ass->SetScriptInfo("Export filters", infoList);
 }
 
 void DialogExport::OnProcess(wxCommandEvent &) {
 	if (!TransferDataFromWindow()) return;
 
-	wxString filename = wxFileSelector(_("Export subtitles file"), "", "", "", SubtitleFormat::GetWildcards(1), wxFD_SAVE | wxFD_OVERWRITE_PROMPT, this);
+	auto filename = wxFileSelector(_("Export subtitles file"), "", "", "", to_wx(SubtitleFormat::GetWildcards(1)), wxFD_SAVE | wxFD_OVERWRITE_PROMPT, this);
 	if (filename.empty()) return;
 
 	for (size_t i = 0; i < filter_list->GetCount(); ++i) {
 		if (filter_list->IsChecked(i))
-			exporter->AddFilter(filter_list->GetString(i));
+			exporter->AddFilter(from_wx(filter_list->GetString(i)));
 	}
 
 	try {
 		wxBusyCursor busy;
-		c->ass->SetScriptInfo("Export Encoding", charset_list->GetStringSelection());
-		exporter->Export(filename, charset_list->GetStringSelection(), this);
+		c->ass->SetScriptInfo("Export Encoding", from_wx(charset_list->GetStringSelection()));
+		exporter->Export(from_wx(filename), from_wx(charset_list->GetStringSelection()), this);
 	}
 	catch (agi::UserCancelException const&) {
 	}
@@ -184,6 +186,9 @@ void DialogExport::OnProcess(wxCommandEvent &) {
 	catch (agi::Exception const& err) {
 		wxMessageBox(to_wx(err.GetMessage()), "Error exporting subtitles", wxOK | wxICON_ERROR | wxCENTER, this);
 	}
+	catch (std::exception const& err) {
+		wxMessageBox(to_wx(err.what()), "Error exporting subtitles", wxOK | wxICON_ERROR | wxCENTER, this);
+	}
 	catch (...) {
 		wxMessageBox("Unknown error", "Error exporting subtitles", wxOK | wxICON_ERROR | wxCENTER, this);
 	}
@@ -192,11 +197,9 @@ void DialogExport::OnProcess(wxCommandEvent &) {
 }
 
 void DialogExport::OnChange(wxCommandEvent &) {
-	int n = filter_list->GetSelection();
-	if (n != wxNOT_FOUND) {
-		wxString name = filter_list->GetString(n);
-		filter_description->SetValue(exporter->GetDescription(name));
-	}
+	wxString sel = filter_list->GetStringSelection();
+	if (!sel.empty())
+		filter_description->SetValue(to_wx(exporter->GetDescription(from_wx(sel))));
 }
 
 void DialogExport::SetAll(bool new_value) {
@@ -210,7 +213,7 @@ void DialogExport::SetAll(bool new_value) {
 
 void DialogExport::RefreshOptions() {
 	for (size_t i = 0; i < filter_list->GetCount(); ++i) {
-		if (wxSizer *sizer = exporter->GetSettingsSizer(filter_list->GetString(i)))
+		if (wxSizer *sizer = exporter->GetSettingsSizer(from_wx(filter_list->GetString(i))))
 			opt_sizer->Show(sizer, filter_list->IsChecked(i), true);
 	}
 	Layout();
diff --git a/aegisub/src/dialog_export_ebu3264.cpp b/aegisub/src/dialog_export_ebu3264.cpp
index cfb8b96523ac1be862e20c63f8d09d0babbb26d4..ca833977df89a07f5ad058a2da46cb3c119b6367 100644
--- a/aegisub/src/dialog_export_ebu3264.cpp
+++ b/aegisub/src/dialog_export_ebu3264.cpp
@@ -23,16 +23,19 @@
 
 #include "dialog_export_ebu3264.h"
 
-#include <libaegisub/charset_conv.h>
-
+#include "compat.h"
 #include "options.h"
 #include "text_file_writer.h"
 
+#include <libaegisub/charset_conv.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp>
+
 #include <wx/checkbox.h>
 #include <wx/combobox.h>
 #include <wx/msgdlg.h>
 #include <wx/radiobox.h>
-#include <wx/regex.h>
 #include <wx/sizer.h>
 #include <wx/spinctrl.h>
 #include <wx/statbox.h>
@@ -41,11 +44,10 @@
 #include <wx/valgen.h>
 
 namespace {
-	const char timecode_regex[] = "([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2})";
+	const boost::regex timecode_regex("([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2}):([[:digit:]]{2})");
 
 	/// Validator for SMPTE timecodes
 	class TimecodeValidator : public wxValidator {
-		wxRegEx re;
 		EbuTimecode *value;
 
 		wxTextCtrl *GetCtrl() const { return dynamic_cast<wxTextCtrl*>(GetWindow()); }
@@ -61,29 +63,24 @@ namespace {
 			wxTextCtrl *ctrl = GetCtrl();
 			if (!ctrl) return false;
 
-			wxString str = ctrl->GetValue();
-
-			if (re.Matches(str)) {
-				long h, m, s, f;
-				re.GetMatch(str, 1).ToLong(&h);
-				re.GetMatch(str, 2).ToLong(&m);
-				re.GetMatch(str, 3).ToLong(&s);
-				re.GetMatch(str, 4).ToLong(&f);
-				value->h = h;
-				value->m = m;
-				value->s = s;
-				value->f = f;
-				return true;
-			}
+			std::string str = from_wx(ctrl->GetValue());
+			boost::smatch result;
+			if (!regex_match(str, result, timecode_regex))
+				return false;
+
+			value->h = boost::lexical_cast<int>(result.str(1));
+			value->m = boost::lexical_cast<int>(result.str(2));
+			value->s = boost::lexical_cast<int>(result.str(3));
+			value->f = boost::lexical_cast<int>(result.str(4));
 
-			return false;
+			return true;
 		}
 
 		bool Validate(wxWindow *parent) {
 			wxTextCtrl *ctrl = GetCtrl();
 			if (!ctrl) return false;
 
-			if (!re.Matches(ctrl->GetValue())) {
+			if (!regex_match(from_wx(ctrl->GetValue()), timecode_regex)) {
 				wxMessageBox(_("Time code offset in incorrect format. Ensure it is entered as four groups of two digits separated by colons."), _("EBU STL export"), wxICON_EXCLAMATION);
 				return false;
 			}
@@ -93,19 +90,8 @@ namespace {
 		wxObject *Clone() const { return new TimecodeValidator(*this); }
 
 	public:
-		TimecodeValidator(EbuTimecode *target)
-		: re(timecode_regex)
-		, value(target)
-		{
-			assert(target);
-		}
-
-		TimecodeValidator(TimecodeValidator const& other)
-		: wxValidator()
-		, re(timecode_regex)
-		, value(other.value)
-		{
-		}
+		TimecodeValidator(EbuTimecode *target) : value(target) { assert(target); }
+		TimecodeValidator(TimecodeValidator const& other) : value(other.value) { }
 	};
 } // namespace {
 
@@ -228,13 +214,13 @@ agi::vfr::Framerate EbuExportSettings::GetFramerate() const {
 
 agi::charset::IconvWrapper *EbuExportSettings::GetTextEncoder() const {
 	switch (text_encoding) {
-		case iso6937_2: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-6937-2");
-		case iso8859_5: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-5");
-		case iso8859_6: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-6");
-		case iso8859_7: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-7");
-		case iso8859_8: return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-8");
-		case utf8:      return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "utf-8");
-		default:        return new agi::charset::IconvWrapper(wxSTRING_ENCODING, "ISO-8859-1");
+		case iso6937_2: return new agi::charset::IconvWrapper("utf-8", "ISO-6937-2");
+		case iso8859_5: return new agi::charset::IconvWrapper("utf-8", "ISO-8859-5");
+		case iso8859_6: return new agi::charset::IconvWrapper("utf-8", "ISO-8859-6");
+		case iso8859_7: return new agi::charset::IconvWrapper("utf-8", "ISO-8859-7");
+		case iso8859_8: return new agi::charset::IconvWrapper("utf-8", "ISO-8859-8");
+		case utf8:      return new agi::charset::IconvWrapper("utf-8", "utf-8");
+		default:        return new agi::charset::IconvWrapper("utf-8", "ISO-8859-1");
 	}
 }
 
diff --git a/aegisub/src/dialog_fonts_collector.cpp b/aegisub/src/dialog_fonts_collector.cpp
index 3a39b519978dc93496c0c8a5e13f66f63a993d66..13db7c8eb4bd5d67aa3f93e3975c3fd35d7e78a9 100644
--- a/aegisub/src/dialog_fonts_collector.cpp
+++ b/aegisub/src/dialog_fonts_collector.cpp
@@ -37,7 +37,8 @@
 #include "standard_paths.h"
 #include "utils.h"
 
-#include <libaegisub/scoped_ptr.h>
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
 
 #include <wx/button.h>
 #include <wx/config.h>
@@ -65,88 +66,95 @@ enum FcMode {
 wxDEFINE_EVENT(EVT_ADD_TEXT, wxThreadEvent);
 wxDEFINE_EVENT(EVT_COLLECTION_DONE, wxThreadEvent);
 
-/// @class FontsCollectorThread
-/// @brief Worker thread for the font collector dialog
-class FontsCollectorThread : public wxThread {
-	AssFile *subs;           ///< Subtitle file to process
-	wxString destination;    ///< Path to write fonts to for modes 2 and 3
-	wxEvtHandler *collector; ///< Parent dialog
-	FcMode oper;             ///< Copying mode
-
-	void Collect() {
-		using namespace std::placeholders;
-
-		FontCollectorStatusCallback callback(std::bind(&FontsCollectorThread::AppendText, this, _1, _2));
+void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMode oper, wxEvtHandler *collector) {
+	agi::dispatch::Background().Async([=]{
+		auto AppendText = [&](wxString text, int colour) {
+			wxThreadEvent event(EVT_ADD_TEXT);
+			event.SetPayload(std::make_pair(colour, text));
+			collector->AddPendingEvent(event);
+		};
 
 #ifdef WITH_FONTCONFIG
-		FontConfigFontFileLister lister(callback);
+		FontConfigFontFileLister lister(AppendText);
 #else
 		AppendText(_("Aegisub was built without any font file listers enabled"), 2);
 		struct DummyLister : public FontFileLister {
-			CollectionResult GetFontPaths(wxString const&, int, bool, std::set<wxUniChar> const&) { return CollectionResult(); }
+			CollectionResult GetFontPaths(std::string const&, int, bool, std::set<wxUniChar> const&) { return CollectionResult(); }
 		} lister;
 #endif
-		std::vector<wxString> paths = FontCollector(callback, lister).GetFontPaths(subs);
-		if (paths.empty()) return;
+		auto paths = FontCollector(AppendText, lister).GetFontPaths(subs);
+		if (paths.empty()) {
+			collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
+			return;
+		}
 
 		// Copy fonts
 		switch (oper) {
 			case CheckFontsOnly:
+				collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
 				return;
 			case SymlinkToFolder:
-				AppendText(_("Symlinking fonts to folder...\n"));
+				AppendText(_("Symlinking fonts to folder...\n"), 0);
 				break;
 			case CopyToScriptFolder:
 			case CopyToFolder:
-				AppendText(_("Copying fonts to folder...\n"));
+				AppendText(_("Copying fonts to folder...\n"), 0);
 				break;
 			case CopyToZip:
-				AppendText(_("Copying fonts to archive...\n"));
+				AppendText(_("Copying fonts to archive...\n"), 0);
 				break;
 		}
 
 		// Open zip stream if saving to compressed archive
-		agi::scoped_ptr<wxFFileOutputStream> out;
-		agi::scoped_ptr<wxZipOutputStream> zip;
+		std::unique_ptr<wxFFileOutputStream> out;
+		std::unique_ptr<wxZipOutputStream> zip;
 		if (oper == CopyToZip) {
-			out.reset(new wxFFileOutputStream(destination));
+			out.reset(new wxFFileOutputStream(destination.wstring()));
 			zip.reset(new wxZipOutputStream(*out));
 		}
 
 		int64_t total_size = 0;
 		bool allOk = true;
-		for (wxString const& path : paths) {
+		for (auto path : paths) {
+			path.make_preferred();
+
 			int ret = 0;
-			wxFileName cur_fn(path);
-			total_size += cur_fn.GetSize().GetValue();
+			total_size += agi::fs::Size(path);
 
 			switch (oper) {
 				case SymlinkToFolder:
 				case CopyToScriptFolder:
 				case CopyToFolder: {
-					wxString dest = destination + cur_fn.GetFullName();
-					if (wxFileName::FileExists(dest))
+					auto dest = destination/path.filename();
+					if (agi::fs::FileExists(dest))
 						ret = 2;
 #ifndef _WIN32
 					else if (oper == SymlinkToFolder) {
 						// returns 0 on success, -1 on error...
-						if (symlink(cur_fn.GetFullPath().utf8_str(), dest.utf8_str()))
+						if (symlink(path.c_str(), dest.c_str()))
 							ret = 0;
 						else
 							ret = 3;
 					}
 #endif
-					else
-						ret = wxCopyFile(path, dest, true);
+					else {
+						try {
+							agi::fs::Copy(path, dest);
+							ret = true;
+						}
+						catch (...) {
+							ret = false;
+						}
+					}
 				}
 				break;
 
 				case CopyToZip: {
-					wxFFileInputStream in(path);
+					wxFFileInputStream in(path.wstring());
 					if (!in.IsOk())
 						ret = false;
 					else {
-						ret = zip->PutNextEntry(cur_fn.GetFullName());
+						ret = zip->PutNextEntry(path.filename().wstring());
 						zip->Write(in);
 					}
 				}
@@ -154,13 +162,13 @@ class FontsCollectorThread : public wxThread {
 			}
 
 			if (ret == 1)
-				AppendText(wxString::Format(_("* Copied %s.\n"), path), 1);
+				AppendText(wxString::Format(_("* Copied %s.\n"), path.wstring()), 1);
 			else if (ret == 2)
-				AppendText(wxString::Format(_("* %s already exists on destination.\n"), wxFileName(path).GetFullName()), 3);
+				AppendText(wxString::Format(_("* %s already exists on destination.\n"), path.filename().wstring()), 3);
 			else if (ret == 3)
-				AppendText(wxString::Format(_("* Symlinked %s.\n"), path), 1);
+				AppendText(wxString::Format(_("* Symlinked %s.\n"), path.wstring()), 1);
 			else {
-				AppendText(wxString::Format(_("* Failed to copy %s.\n"), path), 2);
+				AppendText(wxString::Format(_("* Failed to copy %s.\n"), path.wstring()), 2);
 				allOk = false;
 			}
 		}
@@ -173,36 +181,11 @@ class FontsCollectorThread : public wxThread {
 		if (total_size > 32 * 1024 * 1024)
 			AppendText(_("\nOver 32 MB of fonts were copied. Some of the fonts may not be loaded by the player if they are all attached to a Matroska file."), 2);
 
-		AppendText("\n");
-	}
-
-	/// @brief Tell the dialog to add text to the textbox
-	/// @param text Text to add
-	/// @param colour 0: neutral; 1: success; 2: error
-	void AppendText(wxString text, int colour=0) {
-		wxThreadEvent event(EVT_ADD_TEXT);
-		event.SetPayload(std::make_pair(colour, text));
-		collector->AddPendingEvent(event);
-	}
+		AppendText("\n", 0);
 
-	/// Do the font collection
-	wxThread::ExitCode Entry() {
-		Collect();
 		collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
-		return 0;
-	}
-public:
-	FontsCollectorThread(AssFile *subs, wxString destination, FcMode oper, wxEvtHandler *collector)
-	: wxThread(wxTHREAD_DETACHED)
-	, subs(subs)
-	, destination(destination)
-	, collector(collector)
-	, oper(oper)
-	{
-		Create();
-		Run();
-	}
-};
+	});
+}
 
 DialogFontsCollector::DialogFontsCollector(agi::Context *c)
 : wxDialog(c->parent, -1, _("Fonts Collector"))
@@ -222,13 +205,13 @@ DialogFontsCollector::DialogFontsCollector(agi::Context *c)
 	collection_mode = new wxRadioBox(this, -1, _("Action"), wxDefaultPosition, wxDefaultSize, countof(modes), modes, 1);
 	collection_mode->SetSelection(mid<int>(0, OPT_GET("Tool/Fonts Collector/Action")->GetInt(), 4));
 
-	if (!subs->filename)
+	if (StandardPaths::DecodePath("?script") == "?script")
 		collection_mode->Enable(2, false);
 
 	wxStaticBoxSizer *destination_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Destination"));
 
 	dest_label = new wxStaticText(this, -1, " ");
-	dest_ctrl = new wxTextCtrl(this, -1, StandardPaths::DecodePath(to_wx(OPT_GET("Path/Fonts Collector Destination")->GetString())));
+	dest_ctrl = new wxTextCtrl(this, -1, StandardPaths::DecodePath(OPT_GET("Path/Fonts Collector Destination")->GetString()).wstring());
 	dest_browse_button = new wxButton(this, -1, _("&Browse..."));
 
 	wxSizer *dest_browse_sizer = new wxBoxSizer(wxHORIZONTAL);
@@ -280,40 +263,31 @@ void DialogFontsCollector::OnStart(wxCommandEvent &) {
 	collection_log->ClearAll();
 	collection_log->SetReadOnly(true);
 
-	wxString dest;
+	agi::fs::path dest;
 	int action = collection_mode->GetSelection();
 	OPT_SET("Tool/Fonts Collector/Action")->SetInt(action);
 	if (action != CheckFontsOnly) {
-		if (action == CopyToScriptFolder)
-			dest = "?script/";
-		else
-			dest = dest_ctrl->GetValue();
-		dest = StandardPaths::DecodePath(dest);
-		wxFileName folder = dest;
+		dest = StandardPaths::DecodePath(action == CopyToScriptFolder ? "?script/" : from_wx(dest_ctrl->GetValue()));
 
 		if (action != CopyToZip) {
-			if (dest.Last() != '/' && dest.Last() != '\\') {
-				dest += wxFileName::GetPathSeparator();
-				folder = dest;
-			}
-
-			if (folder.FileExists())
+			if (agi::fs::FileExists(dest))
 				wxMessageBox(_("Invalid destination."), _("Error"), wxOK | wxICON_ERROR | wxCENTER, this);
-			if (!folder.DirExists())
-				folder.Mkdir(0777, wxPATH_MKDIR_FULL);
-			if (!folder.DirExists()) {
+			try {
+				agi::fs::CreateDirectory(dest);
+			}
+			catch (agi::Exception const&) {
 				wxMessageBox(_("Could not create destination folder."), _("Error"), wxOK | wxICON_ERROR | wxCENTER, this);
 				return;
 			}
 		}
-		else if (folder.IsDir() || folder.GetName().empty()) {
+		else if (agi::fs::DirectoryExists(dest) || dest.filename().empty()) {
 			wxMessageBox(_("Invalid path for .zip file."), _("Error"), wxOK | wxICON_ERROR | wxCENTER, this);
 			return;
 		}
 	}
 
 	if (action != CheckFontsOnly)
-		OPT_SET("Path/Fonts Collector Destination")->SetString(from_wx(dest));
+		OPT_SET("Path/Fonts Collector Destination")->SetString(dest.string());
 
 	// Disable the UI while it runs as we don't support canceling
 	EnableCloseButton(false);
@@ -324,7 +298,7 @@ void DialogFontsCollector::OnStart(wxCommandEvent &) {
 	collection_mode->Enable(false);
 	dest_label->Enable(false);
 
-	new FontsCollectorThread(subs, dest, static_cast<FcMode>(action), GetEventHandler());
+	FontsCollectorThread(subs, dest, static_cast<FcMode>(action), GetEventHandler());
 }
 
 void DialogFontsCollector::OnBrowse(wxCommandEvent &) {
@@ -397,7 +371,7 @@ void DialogFontsCollector::OnCollectionComplete(wxThreadEvent &) {
 	start_btn->Enable();
 	close_btn->Enable();
 	collection_mode->Enable();
-	if (!subs->filename)
+	if (StandardPaths::DecodePath("?script") == "?script")
 		collection_mode->Enable(2, false);
 
 	wxCommandEvent evt;
diff --git a/aegisub/src/dialog_jumpto.h b/aegisub/src/dialog_jumpto.h
index e5c0fa443335b445d0d55d3f4b77cd95fe01e789..026bc0027284fca07ae25ba9a468e7fb7a19e081 100644
--- a/aegisub/src/dialog_jumpto.h
+++ b/aegisub/src/dialog_jumpto.h
@@ -32,8 +32,9 @@
 /// @ingroup secondary_ui
 ///
 
-class TimeEdit;
 namespace agi { struct Context; }
+class TimeEdit;
+class wxTextCtrl;
 
 class DialogJumpTo : public wxDialog {
 	agi::Context *c;       ///< Project context
diff --git a/aegisub/src/dialog_kara_timing_copy.cpp b/aegisub/src/dialog_kara_timing_copy.cpp
index f9d26abb3d324cfa9ee5e45184b8c31a1324d3b9..9ec86c4cfc41da4fbd89586ff18982ed4e100dbb 100644
--- a/aegisub/src/dialog_kara_timing_copy.cpp
+++ b/aegisub/src/dialog_kara_timing_copy.cpp
@@ -37,19 +37,6 @@
 
 #include "dialog_kara_timing_copy.h"
 
-#include <deque>
-
-#include <wx/checkbox.h>
-#include <wx/combobox.h>
-#include <wx/dcclient.h>
-#include <wx/listctrl.h>
-#include <wx/msgdlg.h>
-#include <wx/regex.h>
-#include <wx/settings.h>
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-#include <wx/string.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_karaoke.h"
@@ -62,6 +49,18 @@
 #include "selection_controller.h"
 #include "utils.h"
 
+#include <deque>
+
+#include <wx/checkbox.h>
+#include <wx/combobox.h>
+#include <wx/dcclient.h>
+#include <wx/listctrl.h>
+#include <wx/msgdlg.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/string.h>
+
 #define TEXT_LABEL_SOURCE _("Source: ")
 #define TEXT_LABEL_DEST _("Dest: ")
 
@@ -81,14 +80,14 @@ class KaraokeLineMatchDisplay : public wxControl {
 
 	struct MatchGroup {
 		std::vector<MatchSyllable> src;
-		wxString dst;
+		std::string dst;
 		int last_render_width;
 		MatchGroup() : last_render_width(0) { }
 	};
 
 	std::vector<MatchGroup> matched_groups;
 	std::deque<MatchSyllable> unmatched_source;
-	wxString unmatched_destination;
+	std::string unmatched_destination;
 
 	int last_total_matchgroup_render_width;
 
@@ -103,7 +102,7 @@ public:
 	/// Start processing a new line pair
 	void SetInputData(AssDialogue *src, AssDialogue *dst);
 	/// Build and return the output line from the matched syllables
-	wxString GetOutputLine() const;
+	std::string GetOutputLine() const;
 
 	/// Number of syllables not yet matched from source
 	size_t GetRemainingSource() const { return unmatched_source.size(); }
@@ -159,7 +158,7 @@ wxSize KaraokeLineMatchDisplay::GetBestSize() const
 	return wxSize(min_width * 2, h_src + h_dst + 7);
 }
 
-int DrawBoxedText(wxDC &dc, const wxString &txt, int x, int y)
+int DrawBoxedText(wxDC &dc, const std::string &txt, int x, int y)
 {
 	int tw, th;
 	// Assume the pen, brush and font properties have already been set in the DC.
@@ -176,9 +175,10 @@ int DrawBoxedText(wxDC &dc, const wxString &txt, int x, int y)
 	}
 	else
 	{
-		dc.GetTextExtent(txt, &tw, &th);
+		wxString wxtxt(to_wx(txt));
+		dc.GetTextExtent(wxtxt, &tw, &th);
 		dc.DrawRectangle(x, y-2, tw+4, th+4);
-		dc.DrawText(txt, x+2, y);
+		dc.DrawText(wxtxt, x+2, y);
 		return tw+3;
 	}
 }
@@ -267,7 +267,7 @@ void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &)
 		// Matched source syllables
 		int syl_x = next_x;
 		for (auto const& syl : grp.src)
-			syl_x += DrawBoxedText(dc, to_wx(syl.text), syl_x, y_line1);
+			syl_x += DrawBoxedText(dc, syl.text, syl_x, y_line1);
 
 		// Matched destination text
 		{
@@ -306,11 +306,11 @@ void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &)
 			dc.SetBrush(wxBrush(inner_back));
 		}
 
-		syl_x += DrawBoxedText(dc, to_wx(unmatched_source[j].text), syl_x, y_line1);
+		syl_x += DrawBoxedText(dc, unmatched_source[j].text, syl_x, y_line1);
 	}
 
 	// Remaining destination
-	wxString txt = unmatched_destination.Left(destination_sel_length);
+	std::string txt = unmatched_destination.substr(0, destination_sel_length);
 	if (!txt.empty())
 	{
 		dc.SetTextBackground(sel_back);
@@ -319,7 +319,7 @@ void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &)
 		next_x += DrawBoxedText(dc, txt, next_x, y_line2);
 	}
 
-	txt = unmatched_destination.Mid(destination_sel_length);
+	txt = unmatched_destination.substr(destination_sel_length);
 	if (!txt.empty())
 	{
 		dc.SetTextBackground(inner_back);
@@ -344,30 +344,22 @@ void KaraokeLineMatchDisplay::SetInputData(AssDialogue *src, AssDialogue *dst)
 		source_sel_length = 1;
 	}
 
-	unmatched_destination.clear();
-	destination_sel_length = 0;
-	if (dst)
-	{
-		unmatched_destination = dst->GetStrippedText();
-		if (!unmatched_destination.empty())
-		{
-			destination_sel_length = 1;
-		}
-	}
+	unmatched_destination = dst ? dst->GetStrippedText() : "";
+	destination_sel_length = std::max<size_t>(1, unmatched_destination.size());
 
 	Refresh(true);
 }
 
-wxString KaraokeLineMatchDisplay::GetOutputLine() const
+std::string KaraokeLineMatchDisplay::GetOutputLine() const
 {
-	wxString res;
+	std::string res;
 
 	for (auto const& match : matched_groups)
 	{
 		int duration = 0;
 		for (auto const& syl : match.src)
 			duration += syl.duration;
-		res = wxString::Format("%s{\\k%d}%s", res, duration / 10, match.dst);
+		res += "{\\k" + std::to_string(duration / 10) + "}" + match.dst;
 	}
 
 	return res;
@@ -415,15 +407,12 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
 
 	// We'll first see if we can do something with the first unmatched source syllable
 	wxString src(to_wx(unmatched_source[0].text).Lower());
-	wxString dst(unmatched_destination);
+	wxString dst(to_wx(unmatched_destination));
 	source_sel_length = 1; // we're working on the first, assume it was matched
 	destination_sel_length = 0;
 
 	// Quick escape: If the source syllable is empty, return with first source syllable and empty destination
-	if (src.empty())
-	{
-		return;
-	}
+	if (src.empty()) return;
 
 	// Try to match the next source syllable against the destination.  Do it
 	// "inverted": try all kana from the table and prefix-match them against
@@ -456,19 +445,16 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
 
 	// The source might be empty now: That's good!
 	// That means we managed to match it all against destination text
-	if (src.empty())
-	{
-		// destination_sel_length already has the appropriate value
-		// and source_sel_length was alredy 1
-		return;
-	}
+	if (src.empty()) return;
+	// destination_sel_length already has the appropriate value
+	// and source_sel_length was already 1
 
 	// Now the source syllable might consist of just whitespace.
 	// Eat all whitespace at the start of the destination.
 	if (StringEmptyOrWhitespace(src))
 	{
-		while (destination_sel_length < unmatched_destination.size() && IsWhitespace(unmatched_destination[destination_sel_length]))
-			++destination_sel_length;
+		wxString str(to_wx(unmatched_destination.substr(destination_sel_length)));
+		destination_sel_length += std::distance(str.begin(), std::find_if_not(str.begin(), str.end(), IsWhitespace));
 		// Now we've eaten all spaces in the destination as well
 		// so the selection lengths should be good
 		return;
@@ -497,7 +483,7 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
 	// syllables, not the middle of them.
 	// If a match is found, make a guess at how much source and destination
 	// should be selected based on the distances it was found at.
-	dst = unmatched_destination;
+	dst = to_wx(unmatched_destination);
 	for (size_t lookahead = 0; lookahead < KANA_SEARCH_DISTANCE; ++lookahead)
 	{
 		// Eat dst at the beginning, don't test for the first character being kana
@@ -567,13 +553,10 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
 
 bool KaraokeLineMatchDisplay::AcceptMatch()
 {
-	MatchGroup match;
+	// Completely empty match
+	if (source_sel_length == 0 && destination_sel_length == 0) return false;
 
-	if (source_sel_length == 0 && destination_sel_length == 0)
-	{
-		// Completely empty match
-		return false;
-	}
+	MatchGroup match;
 
 	assert(source_sel_length <= unmatched_source.size());
 	copy(unmatched_source.begin(), unmatched_source.begin() + source_sel_length, back_inserter(match.src));
@@ -581,11 +564,11 @@ bool KaraokeLineMatchDisplay::AcceptMatch()
 	source_sel_length = 0;
 
 	assert(destination_sel_length <= unmatched_destination.size());
-	match.dst = unmatched_destination.Left(destination_sel_length);
-	unmatched_destination = unmatched_destination.Mid(destination_sel_length);
+	match.dst = unmatched_destination.substr(0, destination_sel_length);
+	unmatched_destination.erase(0, destination_sel_length);
 	destination_sel_length = 0;
 
-	matched_groups.push_back(match);
+	matched_groups.emplace_back(std::move(match));
 
 	IncreaseSourceMatch();
 	IncreseDestinationMatch();
@@ -729,8 +712,8 @@ void DialogKanjiTimer::OnStart(wxCommandEvent &) {
 	else if (SourceStyle->GetValue() == DestStyle->GetValue())
 		wxMessageBox(_("The source and destination styles must be different."),_("Error"),wxICON_EXCLAMATION | wxOK);
 	else {
-		currentSourceLine = FindNextStyleMatch(&*subs->Line.begin(), SourceStyle->GetValue());
-		currentDestinationLine = FindNextStyleMatch(&*subs->Line.begin(), DestStyle->GetValue());
+		currentSourceLine = FindNextStyleMatch(&*subs->Line.begin(), from_wx(SourceStyle->GetValue()));
+		currentDestinationLine = FindNextStyleMatch(&*subs->Line.begin(), from_wx(DestStyle->GetValue()));
 		ResetForNewLine();
 	}
 	LinesToChange.clear();
@@ -750,12 +733,12 @@ void DialogKanjiTimer::OnUnlink(wxCommandEvent &) {
 }
 
 void DialogKanjiTimer::OnSkipSource(wxCommandEvent &) {
-	currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue());
+	currentSourceLine = FindNextStyleMatch(currentSourceLine, from_wx(SourceStyle->GetValue()));
 	ResetForNewLine();
 }
 
 void DialogKanjiTimer::OnSkipDest(wxCommandEvent &) {
-	currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue());
+	currentDestinationLine = FindNextStyleMatch(currentDestinationLine, from_wx(DestStyle->GetValue()));
 	ResetForNewLine();
 }
 
@@ -763,8 +746,8 @@ void DialogKanjiTimer::OnGoBack(wxCommandEvent &) {
 	if (LinesToChange.size())
 		LinesToChange.pop_back(); //If we go back, then take out the modified line we saved.
 
-	currentSourceLine = FindPrevStyleMatch(currentSourceLine, SourceStyle->GetValue());
-	currentDestinationLine = FindPrevStyleMatch(currentDestinationLine, DestStyle->GetValue());
+	currentSourceLine = FindPrevStyleMatch(currentSourceLine, from_wx(SourceStyle->GetValue()));
+	currentDestinationLine = FindPrevStyleMatch(currentDestinationLine, from_wx(DestStyle->GetValue()));
 	ResetForNewLine();
 }
 
@@ -776,8 +759,8 @@ void DialogKanjiTimer::OnAccept(wxCommandEvent &) {
 	else if (AssDialogue *destLine = dynamic_cast<AssDialogue*>(currentDestinationLine)) {
 		LinesToChange.push_back(std::make_pair(destLine, display->GetOutputLine()));
 
-		currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue());
-		currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue());
+		currentSourceLine = FindNextStyleMatch(currentSourceLine, from_wx(SourceStyle->GetValue()));
+		currentDestinationLine = FindNextStyleMatch(currentDestinationLine, from_wx(DestStyle->GetValue()));
 		ResetForNewLine();
 	}
 }
@@ -844,7 +827,7 @@ void DialogKanjiTimer::TryAutoMatch()
 }
 
 template<typename Iterator>
-static AssEntry *find_next(Iterator from, Iterator to, wxString const& style_name) {
+static AssEntry *find_next(Iterator from, Iterator to, std::string const& style_name) {
 	for (++from; from != to; ++from)
 	{
 		AssDialogue *dlg = dynamic_cast<AssDialogue*>(&*from);
@@ -855,13 +838,13 @@ static AssEntry *find_next(Iterator from, Iterator to, wxString const& style_nam
 	return nullptr;
 }
 
-AssEntry *DialogKanjiTimer::FindNextStyleMatch(AssEntry *search_from, const wxString &search_style)
+AssEntry *DialogKanjiTimer::FindNextStyleMatch(AssEntry *search_from, const std::string &search_style)
 {
 	if (!search_from) return search_from;
 	return find_next(subs->Line.iterator_to(*search_from), subs->Line.end(), search_style);
 }
 
-AssEntry *DialogKanjiTimer::FindPrevStyleMatch(AssEntry *search_from, const wxString &search_style)
+AssEntry *DialogKanjiTimer::FindPrevStyleMatch(AssEntry *search_from, const std::string &search_style)
 {
 	if (!search_from) return search_from;
 	return find_next(EntryList::reverse_iterator(subs->Line.iterator_to(*search_from)), subs->Line.rend(), search_style);
diff --git a/aegisub/src/dialog_kara_timing_copy.h b/aegisub/src/dialog_kara_timing_copy.h
index 05cb4bec65ee5e4e6303d225be436b9ea239e473..8e1d7cffd3a388f372dd164c6aec9df2b4fa812d 100644
--- a/aegisub/src/dialog_kara_timing_copy.h
+++ b/aegisub/src/dialog_kara_timing_copy.h
@@ -53,7 +53,7 @@ class DialogKanjiTimer : public wxDialog {
 	wxComboBox *SourceStyle, *DestStyle;
 	wxCheckBox *Interpolate;
 
-	std::vector<std::pair<AssDialogue*, wxString>> LinesToChange;
+	std::vector<std::pair<AssDialogue*, std::string>> LinesToChange;
 
 	AssEntry *currentSourceLine;
 	AssEntry *currentDestinationLine;
@@ -71,8 +71,8 @@ class DialogKanjiTimer : public wxDialog {
 	void ResetForNewLine();
 	void TryAutoMatch();
 
-	AssEntry *FindNextStyleMatch(AssEntry *search_from, const wxString &search_style);
-	AssEntry *FindPrevStyleMatch(AssEntry *search_from, const wxString &search_style);
+	AssEntry *FindNextStyleMatch(AssEntry *search_from, const std::string &search_style);
+	AssEntry *FindPrevStyleMatch(AssEntry *search_from, const std::string &search_style);
 
 public:
 	DialogKanjiTimer(agi::Context *context);
diff --git a/aegisub/src/dialog_progress.cpp b/aegisub/src/dialog_progress.cpp
index 67c733483f0dbc7d9225d359baf572d1201bb6ad..c860edc0a69ad4194550d2e6827d8578598256a5 100644
--- a/aegisub/src/dialog_progress.cpp
+++ b/aegisub/src/dialog_progress.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,35 +21,25 @@
 
 #include "dialog_progress.h"
 
-#include <libaegisub/exception.h>
-
 #include "compat.h"
 #include "utils.h"
 
+#include <libaegisub/dispatch.h>
+#include <libaegisub/exception.h>
+
+#include <mutex>
 #include <wx/button.h>
 #include <wx/gauge.h>
 #include <wx/sizer.h>
 #include <wx/stattext.h>
 #include <wx/textctrl.h>
 
-wxDEFINE_EVENT(EVT_TITLE, wxThreadEvent);
-wxDEFINE_EVENT(EVT_MESSAGE, wxThreadEvent);
-wxDEFINE_EVENT(EVT_PROGRESS, wxThreadEvent);
-wxDEFINE_EVENT(EVT_INDETERMINATE, wxThreadEvent);
-wxDEFINE_EVENT(EVT_LOG, wxThreadEvent);
-wxDEFINE_EVENT(EVT_COMPLETE, wxThreadEvent);
+using agi::dispatch::Main;
 
 class DialogProgressSink : public agi::ProgressSink {
 	DialogProgress *dialog;
 	bool cancelled;
-	wxMutex cancelled_mutex;
-
-	template<class T>
-	void SafeQueue(wxEventType type, T const& value) {
-		wxThreadEvent *evt = new wxThreadEvent(type);
-		evt->SetPayload(value);
-		wxQueueEvent(dialog, evt);
-	}
+	std::mutex cancelled_mutex;
 
 public:
 	DialogProgressSink(DialogProgress *dialog)
@@ -59,62 +49,33 @@ public:
 	}
 
 	void SetTitle(std::string const& title) {
-		SafeQueue(EVT_TITLE, to_wx(title));
+		Main().Async([=]{ dialog->title->SetLabelText(to_wx(title)); });
 	}
 
 	void SetMessage(std::string const& msg) {
-		SafeQueue(EVT_MESSAGE, to_wx(msg));
+		Main().Async([=]{ dialog->text->SetLabelText(to_wx(msg)); });
 	}
 
 	void SetProgress(int64_t cur, int64_t max) {
-		SafeQueue(EVT_PROGRESS, int(double(cur) / max * 100));
+		Main().Async([=]{ dialog->gauge->SetValue(mid<int>(0, double(cur) / max * 100, 100)); });
 	}
 
 	void Log(std::string const& str) {
-		SafeQueue(EVT_LOG, to_wx(str));
+		Main().Async([=]{ dialog->pending_log += to_wx(str); });
 	}
 
 	bool IsCancelled() {
-		wxMutexLocker l(cancelled_mutex);
+		std::lock_guard<std::mutex> lock(cancelled_mutex);
 		return cancelled;
 	}
 
 	void Cancel() {
-		wxMutexLocker l(cancelled_mutex);
+		std::lock_guard<std::mutex> lock(cancelled_mutex);
 		cancelled = true;
 	}
 
 	void SetIndeterminate() {
-		wxQueueEvent(dialog, new wxThreadEvent(EVT_INDETERMINATE));
-	}
-};
-
-class TaskRunner : public wxThread {
-	std::function<void(agi::ProgressSink*)> task;
-	agi::ProgressSink *ps;
-	wxDialog *dialog;
-
-public:
-	TaskRunner(std::function<void(agi::ProgressSink*)> task, agi::ProgressSink *ps, wxDialog *dialog, int priority)
-	: task(task)
-	, ps(ps)
-	, dialog(dialog)
-	{
-		Create();
-		if (priority != -1)
-			SetPriority(priority);
-		Run();
-	}
-
-	wxThread::ExitCode Entry() {
-		try {
-			task(ps);
-		}
-		catch (agi::Exception const& e) {
-			ps->Log(e.GetChainedMessage());
-		}
-		wxQueueEvent(dialog, new wxThreadEvent(EVT_COMPLETE));
-		return 0;
+		Main().Async([=]{ dialog->pulse_timer.Start(1000); });
 	}
 };
 
@@ -149,19 +110,38 @@ DialogProgress::DialogProgress(wxWindow *parent, wxString const& title_text, wxS
 	Bind(wxEVT_SHOW, &DialogProgress::OnShow, this);
 	Bind(wxEVT_TIMER, [=](wxTimerEvent&) { gauge->Pulse(); });
 	Bind(wxEVT_IDLE, &DialogProgress::OnIdle, this);
-
-	Bind(EVT_TITLE, [=](wxThreadEvent& e) { title->SetLabelText(e.GetPayload<wxString>()); });
-	Bind(EVT_MESSAGE, [=](wxThreadEvent& e) { text->SetLabelText(e.GetPayload<wxString>()); });
-	Bind(EVT_PROGRESS, [=](wxThreadEvent& e) { gauge->SetValue(mid(0, e.GetPayload<int>(), 100)); });
-	Bind(EVT_INDETERMINATE, [=](wxThreadEvent &) { pulse_timer.Start(1000); });
-	Bind(EVT_COMPLETE, &DialogProgress::OnComplete, this);
-	Bind(EVT_LOG, [=](wxThreadEvent& e) { pending_log += e.GetPayload<wxString>(); });
 }
 
 void DialogProgress::Run(std::function<void(agi::ProgressSink*)> task, int priority) {
 	DialogProgressSink ps(this);
 	this->ps = &ps;
-	new TaskRunner(task, &ps, this, priority);
+
+	agi::dispatch::Background().Async([=]{
+		try {
+			task(this->ps);
+		}
+		catch (agi::Exception const& e) {
+			this->ps->Log(e.GetChainedMessage());
+		}
+
+		Main().Async([this]{
+			pulse_timer.Stop();
+
+			// Unbind the cancel handler so that the default behavior happens (i.e. the
+			// dialog is closed) as there's no longer a task to cancel
+			Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogProgress::OnCancel, this, wxID_CANCEL);
+
+			// If it ran to completion and there is debug output, leave the window open
+			// so the user can read the debug output and switch the cancel button to a
+			// close button
+			bool cancelled = this->ps->IsCancelled();
+			if (cancelled || (log_output->IsEmpty() && !pending_log))
+				EndModal(!cancelled);
+			else
+				cancel_button->SetLabelText(_("Close"));
+		});
+	});
+
 	if (!ShowModal())
 		throw agi::UserCancelException("Cancelled by user");
 }
@@ -197,23 +177,6 @@ void DialogProgress::OnIdle(wxIdleEvent&) {
 	pending_log.clear();
 }
 
-void DialogProgress::OnComplete(wxThreadEvent &) {
-	pulse_timer.Stop();
-
-	// Unbind the cancel handler so that the default behavior happens (i.e. the
-	// dialog is closed) as there's no longer a task to cancel
-	Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogProgress::OnCancel, this, wxID_CANCEL);
-
-	// If it ran to completion and there is debug output, leave the window open
-	// so the user can read the debug output and switch the cancel button to a
-	// close button
-	bool cancelled = ps->IsCancelled();
-	if (cancelled || (log_output->IsEmpty() && !pending_log))
-		EndModal(!cancelled);
-	else
-		cancel_button->SetLabelText(_("Close"));
-}
-
 void DialogProgress::OnCancel(wxCommandEvent &) {
 	ps->Cancel();
 	cancel_button->Enable(false);
diff --git a/aegisub/src/dialog_progress.h b/aegisub/src/dialog_progress.h
index 9ea46256478986e2e6773372ae9bc60c82b4a033..52618d855d1e60296b6c587467543af16e59fbaf 100644
--- a/aegisub/src/dialog_progress.h
+++ b/aegisub/src/dialog_progress.h
@@ -21,7 +21,6 @@
 #include <wx/timer.h>
 
 #include <libaegisub/background_runner.h>
-#include <libaegisub/scoped_ptr.h>
 
 class DialogProgressSink;
 class wxButton;
@@ -32,6 +31,7 @@ class wxTextCtrl;
 /// @class DialogProgress
 /// @brief Progress-bar dialog box for displaying during long operations
 class DialogProgress : public wxDialog, public agi::BackgroundRunner {
+	friend class DialogProgressSink;
 	DialogProgressSink *ps;
 
 	wxStaticText *title;
@@ -44,8 +44,6 @@ class DialogProgress : public wxDialog, public agi::BackgroundRunner {
 
 	wxString pending_log;
 
-	void OnComplete(wxThreadEvent &evt);
-
 	void OnShow(wxShowEvent&);
 	void OnCancel(wxCommandEvent &);
 	void OnIdle(wxIdleEvent&);
diff --git a/aegisub/src/dialog_properties.cpp b/aegisub/src/dialog_properties.cpp
index f0fb68a1aee1013beb9eae0db3c58a81169efe02..f7f18854cb94c11958f18d66d5f36b55a427bb84 100644
--- a/aegisub/src/dialog_properties.cpp
+++ b/aegisub/src/dialog_properties.cpp
@@ -34,22 +34,26 @@
 
 #include "config.h"
 
-#include <algorithm>
-
-#include <wx/button.h>
-#include <wx/dialog.h>
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-
 #include "dialog_properties.h"
 
 #include "ass_file.h"
+#include "compat.h"
 #include "help_button.h"
 #include "include/aegisub/context.h"
 #include "libresrc/libresrc.h"
 #include "validators.h"
 #include "video_context.h"
 
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/combobox.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
 DialogProperties::DialogProperties(agi::Context *c)
 : wxDialog(c->parent, -1, _("Script Properties"))
 , c(c)
@@ -74,8 +78,8 @@ DialogProperties::DialogProperties(agi::Context *c)
 
 	// Resolution box
 	wxSizer *ResSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Resolution"));
-	ResX = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(c->ass->GetScriptInfo("PlayResX")));
-	ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(c->ass->GetScriptInfo("PlayResY")));
+	ResX = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(to_wx(c->ass->GetScriptInfo("PlayResX"))));
+	ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(to_wx(c->ass->GetScriptInfo("PlayResY"))));
 	wxStaticText *ResText = new wxStaticText(this,-1,"x");
 
 	wxButton *FromVideo = new wxButton(this,-1,_("From &video"));
@@ -105,13 +109,13 @@ DialogProperties::DialogProperties(agi::Context *c)
 
 	wxString coll_opts[] = { _("Normal"), _("Reverse") };
 	collision = new wxComboBox(this, -1, "", wxDefaultPosition, wxDefaultSize, 2, coll_opts, wxCB_READONLY);
-	collision->SetSelection(c->ass->GetScriptInfo("Collisions").Lower() == "reverse");
+	collision->SetSelection(boost::iequals(c->ass->GetScriptInfo("Collisions"), "reverse"));
 	optionsGrid->Add(new wxStaticText(this,-1,_("Collision: ")),0,wxALIGN_CENTER_VERTICAL,0);
 	optionsGrid->Add(collision,1,wxEXPAND,0);
 
 	ScaleBorder = new wxCheckBox(this,-1,_("Scale Border and Shadow"));
 	ScaleBorder->SetToolTip(_("Scale border and shadow together with script/render resolution. If this is unchecked, relative border and shadow size will depend on renderer."));
-	ScaleBorder->SetValue(c->ass->GetScriptInfo("ScaledBorderAndShadow").Lower() == "yes");
+	ScaleBorder->SetValue(boost::iequals(c->ass->GetScriptInfo("ScaledBorderAndShadow"), "yes"));
 	optionsGrid->AddSpacer(0);
 	optionsGrid->Add(ScaleBorder,1,wxEXPAND,0);
 	optionsGrid->AddGrowableCol(1,1);
@@ -133,8 +137,8 @@ DialogProperties::DialogProperties(agi::Context *c)
 	CenterOnParent();
 }
 
-void DialogProperties::AddProperty(wxSizer *sizer, wxString const& label, wxString const& property) {
-	wxTextCtrl *ctrl = new wxTextCtrl(this, -1, c->ass->GetScriptInfo(property), wxDefaultPosition, wxSize(200, 20));
+void DialogProperties::AddProperty(wxSizer *sizer, wxString const& label, std::string const& property) {
+	wxTextCtrl *ctrl = new wxTextCtrl(this, -1, to_wx(c->ass->GetScriptInfo(property)), wxDefaultPosition, wxSize(200, 20));
 	sizer->Add(new wxStaticText(this, -1, label), wxSizerFlags().Center().Left());
 	sizer->Add(ctrl, wxSizerFlags(1).Expand());
 	properties.push_back(std::make_pair(property, ctrl));
@@ -143,21 +147,21 @@ void DialogProperties::AddProperty(wxSizer *sizer, wxString const& label, wxStri
 void DialogProperties::OnOK(wxCommandEvent &) {
 	int count = 0;
 	for (auto const& prop : properties)
-		count += SetInfoIfDifferent(prop.first, prop.second->GetValue());
+		count += SetInfoIfDifferent(prop.first, from_wx(prop.second->GetValue()));
 
-	count += SetInfoIfDifferent("PlayResX", ResX->GetValue());
-	count += SetInfoIfDifferent("PlayResY", ResY->GetValue());
-	count += SetInfoIfDifferent("WrapStyle", wxString::Format("%d", WrapStyle->GetSelection()));
-	wxString col[2] = { "Normal", "Reverse" };
+	count += SetInfoIfDifferent("PlayResX", from_wx(ResX->GetValue()));
+	count += SetInfoIfDifferent("PlayResY", from_wx(ResY->GetValue()));
+	count += SetInfoIfDifferent("WrapStyle", std::to_string(WrapStyle->GetSelection()));
+	const char *col[2] = { "Normal", "Reverse" };
 	count += SetInfoIfDifferent("Collisions", col[collision->GetSelection()]);
-	count += SetInfoIfDifferent("ScaledBorderAndShadow", ScaleBorder->GetValue()? "yes" : "no");
+	count += SetInfoIfDifferent("ScaledBorderAndShadow", ScaleBorder->GetValue() ? "yes" : "no");
 
 	if (count) c->ass->Commit(_("property changes"), AssFile::COMMIT_SCRIPTINFO);
 
 	EndModal(!!count);
 }
 
-int DialogProperties::SetInfoIfDifferent(wxString key,wxString value) {
+int DialogProperties::SetInfoIfDifferent(std::string const& key, std::string const&value) {
 	if (c->ass->GetScriptInfo(key) != value) {
 		c->ass->SetScriptInfo(key, value);
 		return 1;
diff --git a/aegisub/src/dialog_properties.h b/aegisub/src/dialog_properties.h
index 85fee5bfec55725d3c0459bbd60cfe0d387be64b..608226d336ff44ea97ec6aeda46488605f1c7e6d 100644
--- a/aegisub/src/dialog_properties.h
+++ b/aegisub/src/dialog_properties.h
@@ -33,19 +33,19 @@
 ///
 
 #include <vector>
-
-#include <wx/checkbox.h>
-#include <wx/combobox.h>
-#include <wx/textctrl.h>
+#include <wx/dialog.h>
 
 class AssFile;
 namespace agi { struct Context; }
+class wxCheckBox;
+class wxComboBox;
+class wxTextCtrl;
 
 class DialogProperties : public wxDialog {
 	agi::Context *c; ///< Project this dialog is adjusting the properties of
 
 	/// Pairs of a script property and a text control for that property
-	std::vector<std::pair<wxString, wxTextCtrl*>> properties;
+	std::vector<std::pair<std::string, wxTextCtrl*>> properties;
 
 	// Things that effect rendering
 	wxComboBox *WrapStyle;   ///< Wrapping style for long lines
@@ -62,13 +62,13 @@ class DialogProperties : public wxDialog {
 	/// @param key Name of field
 	/// @param value New value
 	/// @return Did the value actually need to be changed?
-	int SetInfoIfDifferent(wxString key, wxString value);
+	int SetInfoIfDifferent(std::string const& key, std::string const& value);
 
 	/// Add a property with label and text box for updating the property
 	/// @param sizer Sizer to add the label and control to
 	/// @param label Label text to use
 	/// @param property Script info property name
-	void AddProperty(wxSizer *sizer, wxString const& label, wxString const& property);
+	void AddProperty(wxSizer *sizer, wxString const& label, std::string const& property);
 
 public:
 	/// Constructor
diff --git a/aegisub/src/dialog_resample.cpp b/aegisub/src/dialog_resample.cpp
index d0a8f247ee4fce1acf6362f9073c2abc48205a49..08f70d5daa82d8d7256a79e9aa2c622a13951d1a 100644
--- a/aegisub/src/dialog_resample.cpp
+++ b/aegisub/src/dialog_resample.cpp
@@ -21,16 +21,6 @@
 
 #include "dialog_resample.h"
 
-#include <algorithm>
-#include <functional>
-
-#include <wx/checkbox.h>
-#include <wx/sizer.h>
-#include <wx/spinctrl.h>
-#include <wx/statbox.h>
-#include <wx/stattext.h>
-#include <wx/valgen.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
@@ -41,6 +31,17 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+#include <functional>
+
+#include <wx/checkbox.h>
+#include <wx/sizer.h>
+#include <wx/spinctrl.h>
+#include <wx/statbox.h>
+#include <wx/stattext.h>
+#include <wx/valgen.h>
+
 enum {
 	LEFT = 0,
 	RIGHT = 1,
@@ -201,7 +202,7 @@ namespace {
 
 	void resample_line(resample_state *state, AssEntry &line) {
 		AssDialogue *diag = dynamic_cast<AssDialogue*>(&line);
-		if (diag && !(diag->Comment && (diag->Effect.get().StartsWith("template") || diag->Effect.get().StartsWith("code")))) {
+		if (diag && !(diag->Comment && (boost::starts_with(diag->Effect.get(), "template") || boost::starts_with(diag->Effect.get(), "code")))) {
 			boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
 
 			for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
@@ -248,8 +249,8 @@ void ResampleResolution(AssFile *ass, ResampleSettings const& settings) {
 
 	for_each(ass->Line.begin(), ass->Line.end(), std::bind(resample_line, &state, std::placeholders::_1));
 
-	ass->SetScriptInfo("PlayResX", wxString::Format("%d", settings.script_x));
-	ass->SetScriptInfo("PlayResY", wxString::Format("%d", settings.script_y));
+	ass->SetScriptInfo("PlayResX", std::to_string(settings.script_x));
+	ass->SetScriptInfo("PlayResY", std::to_string(settings.script_y));
 
 	ass->Commit(_("resolution resampling"), AssFile::COMMIT_SCRIPTINFO | AssFile::COMMIT_DIAG_FULL);
 }
diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp
index 2f1aaf1db6aad1e9181d3f8c40127100dbbf6863..a8d932f9cc14fe7dd095c4e155e51a765a207a2f 100644
--- a/aegisub/src/dialog_search_replace.cpp
+++ b/aegisub/src/dialog_search_replace.cpp
@@ -52,8 +52,8 @@ DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
 
 	settings->field = static_cast<SearchReplaceSettings::Field>(OPT_GET("Tool/Search Replace/Field")->GetInt());
 	settings->limit_to = static_cast<SearchReplaceSettings::Limit>(OPT_GET("Tool/Search Replace/Affect")->GetInt());
-	settings->find = recent_find.empty() ? wxString() : recent_find.front();
-	settings->replace_with = recent_replace.empty() ? wxString() : recent_replace.front();
+	settings->find = recent_find.empty() ? std::string() : from_wx(recent_find.front());
+	settings->replace_with = recent_replace.empty() ? std::string() : from_wx(recent_replace.front());
 	settings->match_case = OPT_GET("Tool/Search Replace/Match Case")->GetBool();
 	settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool();
 	settings->ignore_comments = OPT_GET("Tool/Search Replace/Skip Comments")->GetBool();
@@ -61,12 +61,12 @@ DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
 	settings->exact_match = false;
 
 	auto find_sizer = new wxFlexGridSizer(2, 2, 5, 15);
-	find_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), recent_find, wxCB_DROPDOWN, wxGenericValidator(&settings->find));
+	find_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), recent_find, wxCB_DROPDOWN, StringBinder(&settings->find));
 	find_sizer->Add(new wxStaticText(this, -1, _("Find what:")), wxSizerFlags().Center().Left());
 	find_sizer->Add(find_edit);
 
 	if (has_replace) {
-		replace_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Replace"), wxCB_DROPDOWN, wxGenericValidator(&settings->replace_with));
+		replace_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Replace"), wxCB_DROPDOWN, StringBinder(&settings->replace_with));
 		find_sizer->Add(new wxStaticText(this, -1, _("Replace with:")), wxSizerFlags().Center().Left());
 		find_sizer->Add(replace_edit);
 	}
@@ -128,11 +128,17 @@ void DialogSearchReplace::FindReplace(bool (SearchReplaceEngine::*func)()) {
 		return;
 
 	c->search->Configure(*settings);
-	(c->search->*func)();
+	try {
+		(c->search->*func)();
+	}
+	catch (std::exception const& e) {
+		wxMessageBox(to_wx(e.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, this);
+		return;
+	}
 
-	config::mru->Add("Find", from_wx(settings->find));
+	config::mru->Add("Find", settings->find);
 	if (has_replace)
-		config::mru->Add("Replace", from_wx(settings->replace_with));
+		config::mru->Add("Replace", settings->replace_with);
 
 	OPT_SET("Tool/Search Replace/Match Case")->SetBool(settings->match_case);
 	OPT_SET("Tool/Search Replace/RegExp")->SetBool(settings->use_regex);
diff --git a/aegisub/src/dialog_selected_choices.cpp b/aegisub/src/dialog_selected_choices.cpp
index d354550cf264e384d1f608fea326a9ec6cdcec6a..0705534f2e257aca78b81dc7d191545578ccc040 100644
--- a/aegisub/src/dialog_selected_choices.cpp
+++ b/aegisub/src/dialog_selected_choices.cpp
@@ -21,6 +21,9 @@
 #include "dialog_selected_choices.h"
 
 #include <numeric>
+#include <wx/button.h>
+#include <wx/listbox.h>
+#include <wx/sizer.h>
 
 SelectedChoicesDialog::SelectedChoicesDialog(wxWindow *parent, wxString const& message, wxString const& caption, wxArrayString const& choices) {
 	Create(parent, message, caption, choices);
diff --git a/aegisub/src/dialog_selected_choices.h b/aegisub/src/dialog_selected_choices.h
index 6bb869a0f5402214c169da661d7b95ce4c56ed1b..7671b86e5cbc265b310da8bbbd8c0beea4e738a2 100644
--- a/aegisub/src/dialog_selected_choices.h
+++ b/aegisub/src/dialog_selected_choices.h
@@ -18,11 +18,8 @@
 /// @brief wxMultiChoiceDialog with Select All and Select None
 /// @ingroup
 
-#include <wx/button.h>
+#include <wx/config.h>
 #include <wx/choicdlg.h>
-#include <wx/event.h>
-#include <wx/listbox.h>
-#include <wx/sizer.h>
 
 /// @class SelectedChoicesDialog
 /// @brief wxMultiChoiceDialog with Select All and Select None
diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp
index 46fc778dbede8028ed75e9862c96aefb5740d1a5..6de8242c7ebb21051195145a1ab1da6d5118fb60 100644
--- a/aegisub/src/dialog_selection.cpp
+++ b/aegisub/src/dialog_selection.cpp
@@ -38,13 +38,15 @@
 #include <libaegisub/of_type_adaptor.h>
 
 #include <algorithm>
+#include <boost/algorithm/string/find.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 #include <wx/checkbox.h>
 #include <wx/combobox.h>
 #include <wx/msgdlg.h>
 #include <wx/radiobox.h>
 #include <wx/radiobut.h>
-#include <wx/regex.h>
+#include <wx/sizer.h>
 #include <wx/textctrl.h>
 
 enum {
@@ -60,12 +62,10 @@ enum {
 	MODE_REGEXP
 };
 
-static std::set<AssDialogue*> process(wxString const& match_text, bool match_case, int mode, bool invert, bool comments, bool dialogue, int field_n, AssFile *ass) {
-	wxRegEx re;
-
+static std::set<AssDialogue*> process(std::string const& match_text, bool match_case, int mode, bool invert, bool comments, bool dialogue, int field_n, AssFile *ass) {
 	SearchReplaceSettings settings = {
 		match_text,
-		wxString(),
+		std::string(),
 		static_cast<SearchReplaceSettings::Field>(field_n),
 		SearchReplaceSettings::Limit::ALL,
 		match_case,
@@ -75,7 +75,7 @@ static std::set<AssDialogue*> process(wxString const& match_text, bool match_cas
 		mode == MODE_EXACT
 	};
 
-	auto predicate = SearchReplaceEngine::GetMatcher(settings, &re);
+	auto predicate = SearchReplaceEngine::GetMatcher(settings);
 
 	std::set<AssDialogue*> matches;
 	for (auto diag : ass->Line | agi::of_type<AssDialogue>()) {
@@ -173,7 +173,7 @@ void DialogSelection::Process(wxCommandEvent&) {
 
 	try {
 		matches = process(
-			match_text->GetValue(), case_sensitive->IsChecked(),
+			from_wx(match_text->GetValue()), case_sensitive->IsChecked(),
 			match_mode->GetSelection(), select_unmatching_lines->GetValue(),
 			apply_to_comments->IsChecked(), apply_to_dialogue->IsChecked(),
 			dialogue_field->GetSelection(), con->ass);
diff --git a/aegisub/src/dialog_shift_times.cpp b/aegisub/src/dialog_shift_times.cpp
index da1f62b8e98db1701e5ad73a3f5db8d39376b6ac..6b8737789ac3a938bf8a64ff9ddd25f42f4ba26f 100644
--- a/aegisub/src/dialog_shift_times.cpp
+++ b/aegisub/src/dialog_shift_times.cpp
@@ -23,25 +23,6 @@
 
 #include "dialog_shift_times.h"
 
-#include <algorithm>
-#include <vector>
-
-#include <wx/filefn.h>
-#include <wx/filename.h>
-#include <wx/listbox.h>
-#include <wx/radiobox.h>
-#include <wx/radiobut.h>
-#include <wx/sizer.h>
-#include <wx/textctrl.h>
-
-#include <libaegisub/io.h>
-#include <libaegisub/log.h>
-#include <libaegisub/of_type_adaptor.h>
-
-#include <libaegisub/cajun/elements.h>
-#include <libaegisub/cajun/reader.h>
-#include <libaegisub/cajun/writer.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_time.h"
@@ -54,6 +35,24 @@
 #include "timeedit_ctrl.h"
 #include "video_context.h"
 
+#include <libaegisub/fs.h>
+#include <libaegisub/io.h>
+#include <libaegisub/log.h>
+#include <libaegisub/of_type_adaptor.h>
+
+#include <libaegisub/cajun/elements.h>
+#include <libaegisub/cajun/reader.h>
+#include <libaegisub/cajun/writer.h>
+
+#include <algorithm>
+#include <vector>
+
+#include <wx/listbox.h>
+#include <wx/radiobox.h>
+#include <wx/radiobut.h>
+#include <wx/sizer.h>
+#include <wx/textctrl.h>
+
 static wxString get_history_string(json::Object &obj) {
 	wxString filename = to_wx(obj["filename"]);
 	if (filename.empty())
@@ -99,7 +98,7 @@ static wxString get_history_string(json::Object &obj) {
 DialogShiftTimes::DialogShiftTimes(agi::Context *context)
 : wxDialog(context->parent, -1, _("Shift Times"))
 , context(context)
-, history_filename(from_wx(StandardPaths::DecodePath("?user/shift_history.json")))
+, history_filename(StandardPaths::DecodePath("?user/shift_history.json"))
 , history(new json::Array)
 , timecodes_loaded_slot(context->videoController->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this))
 , selected_set_changed_slot(context->selectionController->AddSelectionListener(&DialogShiftTimes::OnSelectedSetChanged, this))
@@ -235,7 +234,7 @@ void DialogShiftTimes::OnSelectedSetChanged() {
 
 
 void DialogShiftTimes::OnClear(wxCommandEvent &) {
-	wxRemoveFile(to_wx(history_filename));
+	agi::fs::Remove(history_filename);
 	history_box->Clear();
 	history->clear();
 }
@@ -256,7 +255,7 @@ void DialogShiftTimes::OnHistoryClick(wxCommandEvent &evt) {
 
 	json::Object& obj = (*history)[entry];
 	if (obj["is by time"]) {
-		shift_time->SetTime(AssTime(to_wx(obj["amount"])));
+		shift_time->SetTime(AssTime((std::string)obj["amount"]));
 		shift_by_time->SetValue(true);
 		OnByTime(evt);
 	}
@@ -279,7 +278,7 @@ void DialogShiftTimes::OnHistoryClick(wxCommandEvent &evt) {
 
 void DialogShiftTimes::SaveHistory(json::Array const& shifted_blocks) {
 	json::Object new_entry;
-	new_entry["filename"] = from_wx(wxFileName(context->ass->filename).GetFullName());
+	new_entry["filename"] = context->ass->filename.filename().string();
 	new_entry["is by time"] = shift_by_time->GetValue();
 	new_entry["is backward"] = shift_backward->GetValue();
 	new_entry["amount"] = from_wx(shift_by_time->GetValue() ? shift_time->GetValue() : shift_frames->GetValue());
@@ -292,7 +291,7 @@ void DialogShiftTimes::SaveHistory(json::Array const& shifted_blocks) {
 	try {
 		json::Writer::Write(*history, agi::io::Save(history_filename).Get());
 	}
-	catch (agi::FileSystemError const& e) {
+	catch (agi::fs::FileSystemError const& e) {
 		LOG_E("dialog_shift_times/save_history") << "Cannot save shift times history: " << e.GetChainedMessage();
 	}
 }
@@ -302,7 +301,7 @@ void DialogShiftTimes::LoadHistory() {
 	history_box->Freeze();
 
 	try {
-		agi::scoped_ptr<std::istream> file(agi::io::Open(history_filename));
+		std::unique_ptr<std::istream> file(agi::io::Open(history_filename));
 		json::UnknownElement root;
 		json::Reader::Read(root, *file);
 		*history = root;
@@ -310,7 +309,7 @@ void DialogShiftTimes::LoadHistory() {
 		for (auto& history_entry : *history)
 			history_box->Append(get_history_string(history_entry));
 	}
-	catch (agi::FileSystemError const& e) {
+	catch (agi::fs::FileSystemError const& e) {
 		LOG_D("dialog_shift_times/load_history") << "Cannot load shift times history: " << e.GetChainedMessage();
 	}
 	catch (...) {
diff --git a/aegisub/src/dialog_shift_times.h b/aegisub/src/dialog_shift_times.h
index 124b0487267c0551230bc244fba427f2930166a2..06a4cc3c6c8569a7c0c6109500fc6c4ed10c1fdf 100644
--- a/aegisub/src/dialog_shift_times.h
+++ b/aegisub/src/dialog_shift_times.h
@@ -19,15 +19,17 @@
 /// @ingroup secondary_ui
 ///
 
-#include <deque>
-
-#include <wx/dialog.h>
+#include "selection_controller.h"
 
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/scoped_ptr.h>
 #include <libaegisub/signal.h>
 #include <libaegisub/vfr.h>
 
-#include "selection_controller.h"
+#include <boost/filesystem/path.hpp>
+#include <deque>
+
+#include <wx/dialog.h>
 
 class AssDialogue;
 class TimeEdit;
@@ -44,7 +46,7 @@ namespace json {
 class DialogShiftTimes : public wxDialog {
 	agi::Context *context;
 
-	std::string history_filename;
+	agi::fs::path history_filename;
 	agi::scoped_ptr<json::Array> history;
 	agi::vfr::Framerate fps;
 	agi::signal::Connection timecodes_loaded_slot;
diff --git a/aegisub/src/dialog_spellchecker.cpp b/aegisub/src/dialog_spellchecker.cpp
index f7489d9b08b36081f0965add63185fcf496832b4..7d0cc0c9393c8eccc4f3c1cc4f9fa9866dab1769 100644
--- a/aegisub/src/dialog_spellchecker.cpp
+++ b/aegisub/src/dialog_spellchecker.cpp
@@ -242,7 +242,7 @@ bool DialogSpellChecker::FindNext() {
 bool DialogSpellChecker::CheckLine(AssDialogue *active_line, int start_pos, int *commit_id) {
 	if (active_line->Comment && skip_comments->GetValue()) return false;
 
-	std::string text = from_wx(active_line->Text);
+	std::string text = active_line->Text;
 	auto tokens = agi::ass::TokenizeDialogueBody(text);
 	agi::ass::SplitWords(text, tokens);
 
@@ -277,7 +277,7 @@ bool DialogSpellChecker::CheckLine(AssDialogue *active_line, int start_pos, int
 		}
 
 		text.replace(word_start, word_len, auto_rep->second);
-		active_line->Text = to_wx(text);
+		active_line->Text = text;
 		*commit_id = context->ass->Commit(_("spell check replace"), AssFile::COMMIT_DIAG_TEXT, *commit_id);
 		word_start += auto_rep->second.size();
 	}
@@ -288,9 +288,9 @@ void DialogSpellChecker::Replace() {
 	AssDialogue *active_line = context->selectionController->GetActiveLine();
 
 	// Only replace if the user hasn't changed the selection to something else
-	if (active_line->Text.get().Mid(word_start, word_len) == orig_word->GetValue()) {
-		wxString text = active_line->Text;
-		text.replace(word_start, word_len, replace_word->GetValue());
+	if (to_wx(active_line->Text.get().substr(word_start, word_len)) == orig_word->GetValue()) {
+		std::string text = active_line->Text;
+		text.replace(word_start, word_len, from_wx(replace_word->GetValue()));
 		active_line->Text = text;
 		context->ass->Commit(_("spell check replace"), AssFile::COMMIT_DIAG_TEXT);
 		context->textSelectionController->SetInsertionPoint(word_start + replace_word->GetValue().size());
diff --git a/aegisub/src/dialog_style_editor.cpp b/aegisub/src/dialog_style_editor.cpp
index 5fce0d29f85d7d24d6ae83832a51185763633b2c..f0d621af1ed8232eb1f2385c880913c6d6d2aa92 100644
--- a/aegisub/src/dialog_style_editor.cpp
+++ b/aegisub/src/dialog_style_editor.cpp
@@ -86,13 +86,10 @@ class StyleRenamer {
 		found_any = false;
 		do_replace = replace;
 
-		wxString wx_old(to_wx(source_name));
-		wxString wx_new(to_wx(new_name));
-
 		for (auto diag : c->ass->Line | agi::of_type<AssDialogue>()) {
-			if (diag->Style == wx_old) {
+			if (diag->Style == source_name) {
 				if (replace)
-					diag->Style = wx_new;
+					diag->Style = new_name;
 				else
 					found_any = true;
 			}
@@ -328,7 +325,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
 
 		SubsPreview->SetToolTip(_("Preview of current style"));
 		SubsPreview->SetStyle(*style);
-		SubsPreview->SetText(PreviewText->GetValue());
+		SubsPreview->SetText(from_wx(PreviewText->GetValue()));
 		PreviewText->SetToolTip(_("Text to be used for the preview"));
 		previewButton->SetToolTip(_("Color of preview background"));
 
@@ -524,7 +521,7 @@ void DialogStyleEditor::OnChildFocus(wxChildFocusEvent &event) {
 }
 
 void DialogStyleEditor::OnPreviewTextChange (wxCommandEvent &event) {
-	SubsPreview->SetText(PreviewText->GetValue());
+	SubsPreview->SetText(from_wx(PreviewText->GetValue()));
 	event.Skip();
 }
 
diff --git a/aegisub/src/dialog_style_manager.cpp b/aegisub/src/dialog_style_manager.cpp
index bce13eed0877fb8183f6c3e9152b9fdf7e446c08..a5e72935997d13e483ebdbf74b931b8d1b69a6e1 100644
--- a/aegisub/src/dialog_style_manager.cpp
+++ b/aegisub/src/dialog_style_manager.cpp
@@ -35,20 +35,6 @@
 
 #include "dialog_style_manager.h"
 
-#include <algorithm>
-#include <functional>
-
-#include <wx/bmpbuttn.h>
-#include <wx/clipbrd.h>
-#include <wx/dir.h>
-#include <wx/filedlg.h>
-#include <wx/filename.h>
-#include <wx/intl.h>
-#include <wx/msgdlg.h>
-#include <wx/textdlg.h>
-#include <wx/tokenzr.h>
-#include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
@@ -65,8 +51,23 @@
 #include "subtitle_format.h"
 #include "utils.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/tokenizer.hpp>
+#include <functional>
+
+#include <wx/bmpbuttn.h>
+#include <wx/filedlg.h>
+#include <wx/filename.h>
+#include <wx/intl.h>
+#include <wx/msgdlg.h>
+#include <wx/sizer.h>
+#include <wx/textdlg.h>
+#include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
+
 using std::placeholders::_1;
 
 namespace {
@@ -122,10 +123,11 @@ std::string unique_name(Func name_checker, std::string const& source_name) {
 
 template<class Func1, class Func2>
 void add_styles(Func1 name_checker, Func2 style_adder) {
-	wxStringTokenizer st(GetClipboard(), '\n');
-	while (st.HasMoreTokens()) {
+	boost::char_separator<char> sep("\n");
+	for (auto tok : boost::tokenizer<boost::char_separator<char>>(GetClipboard(), sep)) {
+		boost::trim(tok);
 		try {
-			AssStyle *s = new AssStyle(st.GetNextToken().Trim(true));
+			AssStyle *s = new AssStyle(tok);
 			s->name = unique_name(name_checker, s->name);
 			style_adder(s);
 		}
@@ -284,7 +286,7 @@ void DialogStyleManager::LoadCurrentStyles(int commit_type) {
 		AssDialogue *dia = c->selectionController->GetActiveLine();
 		CurrentList->DeselectAll();
 		if (dia && commit_type != AssFile::COMMIT_NEW)
-			CurrentList->SetStringSelection(dia->Style);
+			CurrentList->SetStringSelection(to_wx(dia->Style));
 		else
 			CurrentList->SetSelection(0);
 	}
@@ -295,7 +297,7 @@ void DialogStyleManager::LoadCurrentStyles(int commit_type) {
 void DialogStyleManager::OnActiveLineChanged(AssDialogue *new_line) {
 	if (new_line) {
 		CurrentList->DeselectAll();
-		CurrentList->SetStringSelection(new_line->Style);
+		CurrentList->SetStringSelection(to_wx(new_line->Style));
 		UpdateButtons();
 	}
 }
@@ -310,8 +312,9 @@ void DialogStyleManager::UpdateStorage() {
 }
 
 void DialogStyleManager::OnChangeCatalog() {
-	c->ass->SetScriptInfo("Last Style Storage", CatalogList->GetStringSelection());
-	Store.Load(CatalogList->GetStringSelection());
+	std::string catalog(from_wx(CatalogList->GetStringSelection()));
+	c->ass->SetScriptInfo("Last Style Storage", catalog);
+	Store.Load(catalog);
 	UpdateStorage();
 }
 
@@ -319,14 +322,8 @@ void DialogStyleManager::LoadCatalog() {
 	CatalogList->Clear();
 
 	// Get saved style catalogs
-	wxDir dir(StandardPaths::DecodePath("?user/catalog/"));
-	if (dir.IsOpened()) {
-		wxString curfile;
-		if (dir.GetFirst(&curfile, "*.sty", wxDIR_FILES))
-			CatalogList->Append(wxFileName(curfile).GetName());
-		while (dir.GetNext(&curfile))
-			CatalogList->Append(wxFileName(curfile).GetName());
-	}
+	for (auto const& file : agi::fs::DirectoryIterator(StandardPaths::DecodePath("?user/catalog/"), "*.sty"))
+		CatalogList->Append(agi::fs::path(file).stem().wstring());
 
 	// Create a default storage if there are none
 	if (CatalogList->IsListEmpty()) {
@@ -337,11 +334,11 @@ void DialogStyleManager::LoadCatalog() {
 	}
 
 	// Set to default if available
-	wxString pickStyle = c->ass->GetScriptInfo("Last Style Storage");
+	std::string pickStyle = c->ass->GetScriptInfo("Last Style Storage");
 	if (pickStyle.empty())
 		pickStyle = "Default";
 
-	int opt = CatalogList->FindString(pickStyle, false);
+	int opt = CatalogList->FindString(to_wx(pickStyle), false);
 	if (opt != wxNOT_FOUND)
 		CatalogList->SetSelection(opt);
 	else
@@ -390,7 +387,7 @@ void DialogStyleManager::OnCatalogDelete() {
 	wxString message = wxString::Format(_("Are you sure you want to delete the storage \"%s\" from the catalog?"), name);
 	int option = wxMessageBox(message, _("Confirm delete"), wxYES_NO | wxICON_EXCLAMATION , this);
 	if (option == wxYES) {
-		wxRemoveFile(StandardPaths::DecodePath("?user/catalog/" + name + ".sty"));
+		agi::fs::Remove(StandardPaths::DecodePath("?user/catalog/" + from_wx(name) + ".sty"));
 		CatalogList->Delete(CatalogList->GetSelection());
 		CatalogList->SetSelection(0);
 		OnChangeCatalog();
@@ -462,7 +459,7 @@ void DialogStyleManager::CopyToClipboard(wxListBox *list, T const& v) {
 		if (i) data += "\r\n";
 		AssStyle *s = v[selections[i]];
 		s->UpdateData();
-		data += s->GetEntryData();
+		data += to_wx(s->GetEntryData());
 	}
 
 	SetClipboard(data);
@@ -566,14 +563,14 @@ void DialogStyleManager::OnCurrentDelete() {
 void DialogStyleManager::OnCurrentImport() {
 	// Get file name
 	wxString path = to_wx(OPT_GET("Path/Last/Subtitles")->GetString());
-	wxString filename = wxFileSelector(_("Open subtitles file"),path,"","",SubtitleFormat::GetWildcards(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+	wxString filename = wxFileSelector(_("Open subtitles file"), path, "", "", to_wx(SubtitleFormat::GetWildcards(0)), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (!filename) return;
 
 	OPT_SET("Path/Last/Subtitles")->SetString(from_wx(wxFileName(filename).GetPath()));
 
 	AssFile temp;
 	try {
-		temp.Load(filename);
+		temp.Load(from_wx(filename));
 	}
 	catch (agi::Exception const& err) {
 		wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, this);
@@ -758,7 +755,7 @@ void DialogStyleManager::MoveStyles(bool storage, int type) {
 		do_move(styleMap, type, first, last, false);
 
 		// Replace styles
-		int curn = 0;
+		size_t curn = 0;
 		for (auto it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
 			if (!dynamic_cast<AssStyle*>(&*it)) continue;
 
diff --git a/aegisub/src/dialog_styling_assistant.cpp b/aegisub/src/dialog_styling_assistant.cpp
index 1eb03cc9701b8852c61b92da0ffa7e47ad56c098..00a0f8a198c60ba5d1e5a46da8c56f884c27a7e6 100644
--- a/aegisub/src/dialog_styling_assistant.cpp
+++ b/aegisub/src/dialog_styling_assistant.cpp
@@ -48,7 +48,7 @@
 
 static void add_hotkey(wxSizer *sizer, wxWindow *parent, const char *command, wxString const& text) {
 	sizer->Add(new wxStaticText(parent, -1, text));
-	sizer->Add(new wxStaticText(parent, -1, hotkey::get_hotkey_str_first("Styling Assistant", command)));
+	sizer->Add(new wxStaticText(parent, -1, to_wx(hotkey::get_hotkey_str_first("Styling Assistant", command))));
 }
 
 DialogStyling::DialogStyling(agi::Context *context)
@@ -158,12 +158,12 @@ void DialogStyling::OnActiveLineChanged(AssDialogue *new_line) {
 	if (!new_line) return;
 	active_line = new_line;
 
-	current_line_text->SetValue(active_line->Text);
-	style_name->SetValue(active_line->Style);
+	current_line_text->SetValue(to_wx(active_line->Text));
+	style_name->SetValue(to_wx(active_line->Style));
 	style_name->SetSelection(0, active_line->Style.get().size());
 	style_name->SetFocus();
 
-	style_list->SetStringSelection(active_line->Style);
+	style_list->SetStringSelection(to_wx(active_line->Style));
 
 	if (auto_seek->IsChecked() && IsActive())
 		c->videoController->JumpToTime(active_line->Start);
@@ -172,7 +172,7 @@ void DialogStyling::OnActiveLineChanged(AssDialogue *new_line) {
 void DialogStyling::Commit(bool next) {
 	if (!c->ass->GetStyle(from_wx(style_name->GetValue()))) return;
 
-	active_line->Style = style_name->GetValue();
+	active_line->Style = from_wx(style_name->GetValue());
 	c->ass->Commit(_("styling assistant"), AssFile::COMMIT_DIAG_META);
 
 	if (next) cmd::call("grid/line/next", c);
diff --git a/aegisub/src/dialog_timing_processor.cpp b/aegisub/src/dialog_timing_processor.cpp
index 9913540667896082f9d1a96ad567259a3b108deb..b088820285ee27c9991aa0d2526ff7cf0758167d 100644
--- a/aegisub/src/dialog_timing_processor.cpp
+++ b/aegisub/src/dialog_timing_processor.cpp
@@ -291,15 +291,15 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &) {
 	EndModal(0);
 }
 
-static bool bad_line(std::set<wxString> *styles, AssDialogue *d) {
+static bool bad_line(std::set<std::string> *styles, AssDialogue *d) {
 	return !d || d->Comment || styles->find(d->Style) == styles->end();
 }
 
 std::vector<AssDialogue*> DialogTimingProcessor::SortDialogues() {
-	std::set<wxString> styles;
+	std::set<std::string> styles;
 	for (size_t i = 0; i < StyleList->GetCount(); ++i) {
 		if (StyleList->IsChecked(i))
-			styles.insert(StyleList->GetString(i));
+			styles.insert(from_wx(StyleList->GetString(i)));
 	}
 
 	std::vector<AssDialogue*> sorted;
diff --git a/aegisub/src/dialog_translation.cpp b/aegisub/src/dialog_translation.cpp
index 10ae3e80f961eaa4ed18fe1fd11a99f13dd19666..5b732fb556d3dfc5d071afcc1263fc2b178fce15 100644
--- a/aegisub/src/dialog_translation.cpp
+++ b/aegisub/src/dialog_translation.cpp
@@ -50,7 +50,7 @@
 
 static void add_hotkey(wxSizer *sizer, wxWindow *parent, const char *command, wxString const& text) {
 	sizer->Add(new wxStaticText(parent, -1, text));
-	sizer->Add(new wxStaticText(parent, -1, hotkey::get_hotkey_str_first("Translation Assistant", command)));
+	sizer->Add(new wxStaticText(parent, -1, to_wx(hotkey::get_hotkey_str_first("Translation Assistant", command))));
 }
 
 // Skip over override blocks, comments, and whitespace between blocks
diff --git a/aegisub/src/dialog_version_check.cpp b/aegisub/src/dialog_version_check.cpp
index f35f615716e05ed467c2e3507fc697ef08b898a9..1437190c72f670b14dd8628518258a1547f002cb 100644
--- a/aegisub/src/dialog_version_check.cpp
+++ b/aegisub/src/dialog_version_check.cpp
@@ -32,14 +32,26 @@
 /// @ingroup configuration_ui
 ///
 
-
 #include "config.h"
 
 #ifdef WITH_UPDATE_CHECKER
 
 #include "dialog_version_check.h"
 
-#include <wx/app.h>
+#ifdef _MSC_VER
+#pragma warning(disable : 4250) // 'boost::asio::basic_socket_iostream<Protocol>' : inherits 'std::basic_ostream<_Elem,_Traits>::std::basic_ostream<_Elem,_Traits>::_Add_vtordisp2' via dominance
+#endif
+
+#include "compat.h"
+#include "options.h"
+#include "string_codec.h"
+#include "version.h"
+
+#include <libaegisub/dispatch.h>
+#include <libaegisub/exception.h>
+#include <libaegisub/line_iterator.h>
+#include <libaegisub/scoped_ptr.h>
+
 #include <wx/button.h>
 #include <wx/checkbox.h>
 #include <wx/dialog.h>
@@ -47,176 +59,141 @@
 #include <wx/hyperlink.h>
 #include <wx/intl.h>
 #include <wx/platinfo.h>
-#include <wx/protocol/http.h>
 #include <wx/sizer.h>
 #include <wx/statline.h>
 #include <wx/stattext.h>
 #include <wx/string.h>
 #include <wx/textctrl.h>
-#include <wx/thread.h>
-#include <wx/tokenzr.h>
-#include <wx/txtstrm.h>
 
 #include <algorithm>
+#include <ctime>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/format.hpp>
 #include <functional>
 #include <memory>
 #include <set>
 #include <vector>
 
-#include <wx/sstream.h>
-
-#include "compat.h"
-#include "options.h"
-#include "string_codec.h"
-#include "version.h"
-
-#include <libaegisub/exception.h>
-#include <libaegisub/scoped_ptr.h>
-
 #ifdef __APPLE__
 #include <CoreFoundation/CoreFoundation.h>
 #endif
 
-/* *** Public API is implemented here *** */
-
 // Allocate global lock mutex declared in header
-wxMutex VersionCheckLock;
-
-class AegisubVersionCheckerThread : public wxThread {
-	bool interactive;
-	void DoCheck();
-	void PostErrorEvent(const wxString &error_text);
-	ExitCode Entry();
-public:
-	AegisubVersionCheckerThread(bool interactive);
-};
-
-// Public API for version checker
-void PerformVersionCheck(bool interactive)
-{
-	new AegisubVersionCheckerThread(interactive);
-}
-
-/* *** The actual implementation begins here *** */
+std::mutex VersionCheckLock;
 
+namespace {
 struct AegisubUpdateDescription {
-	wxString url;
-	wxString friendly_name;
-	wxString description;
+	std::string url;
+	std::string friendly_name;
+	std::string description;
+
+	AegisubUpdateDescription(std::string const& url, std::string const& friendly_name, std::string const& description)
+	: url(url), friendly_name(friendly_name), description(description) { }
 };
 
-class AegisubVersionCheckResultEvent : public wxEvent {
-	wxString main_text;
-	std::vector<AegisubUpdateDescription> updates;
+class VersionCheckerResultDialog : public wxDialog {
+	void OnCloseButton(wxCommandEvent &evt);
+	void OnRemindMeLater(wxCommandEvent &evt);
+	void OnClose(wxCloseEvent &evt);
+
+	wxCheckBox *automatic_check_checkbox;
 
 public:
-	AegisubVersionCheckResultEvent(wxString message = wxString());
+	VersionCheckerResultDialog(wxString const& main_text, const std::vector<AegisubUpdateDescription> &updates);
 
+	bool ShouldPreventAppExit() const { return false; }
+};
 
-	wxEvent *Clone() const
-	{
-		return new AegisubVersionCheckResultEvent(*this);
-	}
+VersionCheckerResultDialog::VersionCheckerResultDialog(wxString const& main_text, const std::vector<AegisubUpdateDescription> &updates)
+: wxDialog(0, -1, _("Version Checker"))
+{
+	const int controls_width = 500;
 
-	const wxString & GetMainText() const { return main_text; }
-
-	// If there are no updates in the list, either none were found or an error occurred,
-	// either way it means "failure" if it's empty
-	const std::vector<AegisubUpdateDescription> & GetUpdates() const { return updates; }
-	void AddUpdate(const wxString &url, const wxString &friendly_name, const wxString &description)
-	{
-		updates.push_back(AegisubUpdateDescription());
-		AegisubUpdateDescription &desc = updates.back();
-		desc.url = url;
-		desc.friendly_name = friendly_name;
-		desc.description = description;
-	}
-};
+	wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
 
-wxDEFINE_EVENT(AEGISUB_EVENT_VERSIONCHECK_RESULT, AegisubVersionCheckResultEvent);
+	wxStaticText *text = new wxStaticText(this, -1, main_text);
+	text->Wrap(controls_width);
+	main_sizer->Add(text, 0, wxBOTTOM|wxEXPAND, 6);
 
-AegisubVersionCheckResultEvent::AegisubVersionCheckResultEvent(wxString message)
-: wxEvent(0, AEGISUB_EVENT_VERSIONCHECK_RESULT)
-, main_text(message)
-{
-}
+	for (auto const& update : updates) {
+		main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND|wxALL, 6);
 
+		text = new wxStaticText(this, -1, to_wx(update.friendly_name));
+		wxFont boldfont = text->GetFont();
+		boldfont.SetWeight(wxFONTWEIGHT_BOLD);
+		text->SetFont(boldfont);
+		main_sizer->Add(text, 0, wxEXPAND|wxBOTTOM, 6);
 
-DEFINE_SIMPLE_EXCEPTION_NOINNER(VersionCheckError, agi::Exception, "versioncheck")
+		wxTextCtrl *descbox = new wxTextCtrl(this, -1, to_wx(update.description), wxDefaultPosition, wxSize(controls_width,60), wxTE_MULTILINE|wxTE_READONLY);
+		main_sizer->Add(descbox, 0, wxEXPAND|wxBOTTOM, 6);
 
-static void register_event_handler();
+		main_sizer->Add(new wxHyperlinkCtrl(this, -1, to_wx(update.url), to_wx(update.url)), 0, wxALIGN_LEFT|wxBOTTOM, 6);
+	}
 
-AegisubVersionCheckerThread::AegisubVersionCheckerThread(bool interactive)
-: wxThread(wxTHREAD_DETACHED)
-, interactive(interactive)
-{
-	register_event_handler();
+	automatic_check_checkbox = new wxCheckBox(this, -1, _("&Auto Check for Updates"));
+	automatic_check_checkbox->SetValue(OPT_GET("App/Auto/Check For Updates")->GetBool());
 
-#ifndef __APPLE__
-	if (!wxSocketBase::IsInitialized())
-		wxSocketBase::Initialize();
-#endif
+	wxButton *remind_later_button = 0;
+	if (updates.size() > 0)
+		remind_later_button = new wxButton(this, wxID_NO, _("Remind me again in a &week"));
 
-	Create();
-	Run();
-}
+	wxButton *close_button = new wxButton(this, wxID_OK, _("&Close"));
+	SetAffirmativeId(wxID_OK);
+	SetEscapeId(wxID_OK);
 
-wxThread::ExitCode AegisubVersionCheckerThread::Entry()
-{
-	if (!interactive)
-	{
-		// Automatic checking enabled?
-		if (!OPT_GET("App/Auto/Check For Updates")->GetBool())
-			return 0;
-
-		// Is it actually time for a check?
-		time_t next_check = OPT_GET("Version/Next Check")->GetInt();
-		if (next_check > wxDateTime::GetTimeNow())
-			return 0;
-	}
+	if (updates.size())
+		main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND|wxALL, 6);
+	main_sizer->Add(automatic_check_checkbox, 0, wxEXPAND|wxBOTTOM, 6);
 
-	if (VersionCheckLock.TryLock() != wxMUTEX_NO_ERROR) return 0;
+	wxStdDialogButtonSizer *button_sizer = new wxStdDialogButtonSizer();
+	button_sizer->AddButton(close_button);
+	if (remind_later_button)
+		button_sizer->AddButton(remind_later_button);
+	button_sizer->Realize();
+	main_sizer->Add(button_sizer, 0, wxEXPAND, 0);
 
-	try {
-		DoCheck();
-	}
-	catch (const agi::Exception &e) {
-		PostErrorEvent(wxString::Format(
-			_("There was an error checking for updates to Aegisub:\n%s\n\nIf other applications can access the Internet fine, this is probably a temporary server problem on our end."),
-			e.GetMessage()));
-	}
-	catch (...) {
-		PostErrorEvent(_("An unknown error occurred while checking for updates to Aegisub."));
-	}
+	wxSizer *outer_sizer = new wxBoxSizer(wxVERTICAL);
+	outer_sizer->Add(main_sizer, 0, wxALL|wxEXPAND, 12);
 
-	VersionCheckLock.Unlock();
+	SetSizerAndFit(outer_sizer);
+	Centre();
+	Show();
 
-	// While Options isn't perfectly thread safe, this should still be okay.
-	// Traversing the std::map to find the key-value pair doesn't modify any data as long as
-	// the key already exists (which it does at this point), and modifying the value only
-	// touches that specific key-value pair and will never cause a rebalancing of the tree,
-	// because the tree only depends on the keys.
-	// Lastly, writing options to disk only happens when Options.Save() is called.
-	time_t new_next_check_time = wxDateTime::GetTimeNow() + 60*60; // in one hour
-	OPT_SET("Version/Next Check")->SetInt((int)new_next_check_time);
+	Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&VersionCheckerResultDialog::Close, this, false), wxID_OK);
+	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &VersionCheckerResultDialog::OnRemindMeLater, this, wxID_NO);
+	Bind(wxEVT_CLOSE_WINDOW, &VersionCheckerResultDialog::OnClose, this);
+}
 
-	return 0;
+void VersionCheckerResultDialog::OnRemindMeLater(wxCommandEvent &) {
+	// In one week
+	time_t new_next_check_time = time(nullptr) + 7*24*60*60;
+	OPT_SET("Version/Next Check")->SetInt(new_next_check_time);
+
+	Close();
 }
 
-void AegisubVersionCheckerThread::PostErrorEvent(const wxString &error_text)
-{
-	if (interactive)
-		wxTheApp->AddPendingEvent(AegisubVersionCheckResultEvent(error_text));
+void VersionCheckerResultDialog::OnClose(wxCloseEvent &) {
+	OPT_SET("App/Auto/Check For Updates")->SetBool(automatic_check_checkbox->GetValue());
+	Destroy();
 }
 
+DEFINE_SIMPLE_EXCEPTION_NOINNER(VersionCheckError, agi::Exception, "versioncheck")
 
-static const char * GetOSShortName()
-{
+void PostErrorEvent(bool interactive, wxString const& error_text) {
+	if (interactive) {
+		agi::dispatch::Main().Async([=]{
+			new VersionCheckerResultDialog(error_text, std::vector<AegisubUpdateDescription>());
+		});
+	}
+}
+
+static const char * GetOSShortName() {
 	int osver_maj, osver_min;
 	wxOperatingSystemId osid = wxGetOsVersion(&osver_maj, &osver_min);
 
-	if (osid & wxOS_WINDOWS_NT)
-	{
+	if (osid & wxOS_WINDOWS_NT) {
 		if (osver_maj == 5 && osver_min == 0)
 			return "win2k";
 		else if (osver_maj == 5 && osver_min == 1)
@@ -234,8 +211,7 @@ static const char * GetOSShortName()
 	}
 	// CF returns 0x10 for some reason, which wx has recently started
 	// turning into 10
-	else if (osid & wxOS_MAC_OSX_DARWIN && (osver_maj == 0x10 || osver_maj == 10))
-	{
+	else if (osid & wxOS_MAC_OSX_DARWIN && (osver_maj == 0x10 || osver_maj == 10)) {
 		// ugliest hack in the world? nah.
 		static char osxstring[] = "osx00";
 		char minor = osver_min >> 4;
@@ -268,13 +244,11 @@ static const char * GetOSShortName()
 		return "unknown";
 }
 
-
 #ifdef WIN32
 typedef BOOL (WINAPI * PGetUserPreferredUILanguages)(DWORD dwFlags, PULONG pulNumLanguages, wchar_t *pwszLanguagesBuffer, PULONG pcchLanguagesBuffer);
 
 // Try using Win 6+ functions if available
-static wxString GetUILanguage()
-{
+static wxString GetUILanguage() {
 	agi::scoped_holder<HMODULE, BOOL (__stdcall *)(HMODULE)> kernel32(LoadLibraryW(L"kernel32.dll"), FreeLibrary);
 	if (!kernel32) return "";
 
@@ -292,20 +266,17 @@ static wxString GetUILanguage()
 	// We got at least one language, just treat it as the only, and a null-terminated string
 	return &output[0];
 }
-static wxString GetSystemLanguage()
-{
+
+static wxString GetSystemLanguage() {
 	wxString res = GetUILanguage();
 	if (!res)
-	{
 		// On an old version of Windows, let's just return the LANGID as a string
 		res = wxString::Format("x-win%04x", GetUserDefaultUILanguage());
-	}
 
 	return res;
 }
 #elif __APPLE__
-static wxString GetSystemLanguage()
-{
+static wxString GetSystemLanguage() {
 	CFLocaleRef locale = CFLocaleCopyCurrent();
 	CFStringRef localeName = (CFStringRef)CFLocaleGetValue(locale, kCFLocaleIdentifier);
 
@@ -317,277 +288,123 @@ static wxString GetSystemLanguage()
 
 }
 #else
-static wxString GetSystemLanguage()
-{
+static wxString GetSystemLanguage() {
 	return wxLocale::GetLanguageInfo(wxLocale::GetSystemLanguage())->CanonicalName;
 }
 #endif
 
-static wxString GetAegisubLanguage()
-{
+static wxString GetAegisubLanguage() {
 	return to_wx(OPT_GET("App/Language")->GetString());
 }
 
-template<class OutIter>
-static void split_str(wxString const& str, wxString const& sep, bool empty, OutIter out)
-{
-	wxStringTokenizer tk(str, sep, empty ? wxTOKEN_DEFAULT : wxTOKEN_RET_EMPTY_ALL);
-	while (tk.HasMoreTokens())
-	{
-		*out++ = tk.GetNextToken();
-	}
-}
-
-
-void AegisubVersionCheckerThread::DoCheck()
-{
-	std::set<wxString> accept_tags;
-#ifdef UPDATE_CHECKER_ACCEPT_TAGS
-	split_str(wxString(UPDATE_CHECKER_ACCEPT_TAGS, wxConvUTF8), " ", false,
-		inserter(accept_tags, accept_tags.end()));
-#endif
-
-#ifdef __APPLE__
-	wxString cmd = wxString::Format(
-		"curl --silent --fail 'http://%s/%s?rev=%d&rel=%d&os=%s&lang=%s&aegilang=%s'",
-		UPDATE_CHECKER_SERVER,
-		UPDATE_CHECKER_BASE_URL,
-		GetSVNRevision(),
-		GetIsOfficialRelease()?1:0,
-		GetOSShortName(),
-		GetSystemLanguage(),
-		GetAegisubLanguage());
-
-	// wxExecute only works on the main thread so use popen instead
-	char buf[1024];
-	FILE *pipe = popen(cmd.utf8_str(), "r");
-	if (!pipe)
-		throw VersionCheckError("Could not run curl");
-
-	wxString update_str;
-	while(fgets(buf, sizeof(buf), pipe))
-		update_str += buf;
-
-	int ret = pclose(pipe);
-	switch (ret)
-	{
-		case 0:
-			break;
-		case 6:
-		case 7:
-			throw VersionCheckError(from_wx(_("Could not connect to updates server.")));
-		case 22:
-			throw VersionCheckError(from_wx(_("Could not download from updates server.")));
-		default:
-			throw VersionCheckError(from_wx(wxString::Format("curl failed with error code %d", ret)));
-	}
-
-	agi::scoped_ptr<wxStringInputStream> stream(new wxStringInputStream(update_str));
-#else
-	wxString path = wxString::Format(
-		"%s?rev=%d&rel=%d&os=%s&lang=%s&aegilang=%s",
-		UPDATE_CHECKER_BASE_URL,
-		GetSVNRevision(),
-		GetIsOfficialRelease()?1:0,
-		GetOSShortName(),
-		GetSystemLanguage(),
-		GetAegisubLanguage());
-
-	wxHTTP http;
-	http.SetHeader("User-Agent", wxString("Aegisub ") + GetAegisubLongVersionString());
-	http.SetHeader("Connection", "Close");
-	http.SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK);
-
-	if (!http.Connect(UPDATE_CHECKER_SERVER))
+void DoCheck(bool interactive) {
+	boost::asio::ip::tcp::iostream stream;
+	stream.connect(UPDATE_CHECKER_SERVER, "http");
+	if (!stream)
 		throw VersionCheckError(from_wx(_("Could not connect to updates server.")));
 
-	agi::scoped_ptr<wxInputStream> stream(http.GetInputStream(path));
-	if (!stream) // check for null-pointer
+	stream << boost::format(
+		"GET %s?rev=%d&rel=%d&os=%s&lang=%s&aegilang=%s HTTP/1.0\r\n"
+		"User-Agent: Aegisub %s\r\n"
+		"Host: %s\r\n"
+		"Accept: */*\r\n"
+		"Connection: close\r\n\r\n")
+		% UPDATE_CHECKER_BASE_URL
+		% GetSVNRevision()
+		% (GetIsOfficialRelease() ? 1 : 0)
+		% GetOSShortName()
+		% GetSystemLanguage()
+		% GetAegisubLanguage()
+		% GetAegisubLongVersionString()
+		% UPDATE_CHECKER_SERVER
+		;
+
+	std::string http_version;
+	stream >> http_version;
+	int status_code;
+	stream >> status_code;
+	if (!stream || http_version.substr(0, 5) != "HTTP/")
 		throw VersionCheckError(from_wx(_("Could not download from updates server.")));
+	if (status_code != 200)
+		throw VersionCheckError(from_wx(wxString::Format(_("HTTP request failed, got HTTP response %d."), status_code)));
 
-	if (http.GetResponse() < 200 || http.GetResponse() >= 300) {
-		throw VersionCheckError(from_wx(wxString::Format(_("HTTP request failed, got HTTP response %d."), http.GetResponse())));
-	}
-#endif
-	wxTextInputStream text(*stream);
+	stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+
+	// Skip the headers since we don't care about them
+	for (auto const& header : agi::line_iterator<std::string>(stream))
+		if (header.empty()) break;
 
-	AegisubVersionCheckResultEvent result_event;
+	std::vector<AegisubUpdateDescription> results;
+	for (auto const& line : agi::line_iterator<std::string>(stream)) {
+		if (line.empty()) continue;
 
-	while (!stream->Eof() && stream->GetSize() > 0)
-	{
-		wxArrayString parsed;
-		split_str(text.ReadLine(), "|", true, std::back_inserter(parsed));
+		std::vector<std::string> parsed;
+		boost::split(parsed, line, boost::is_any_of("|"));
 		if (parsed.size() != 6) continue;
 
-		wxString line_type = parsed[0];
-		wxString line_revision = parsed[1];
-		wxString line_tags_str = parsed[2];
-		wxString line_url = inline_string_decode(parsed[3]);
-		wxString line_friendlyname = inline_string_decode(parsed[4]);
-		wxString line_description = inline_string_decode(parsed[5]);
+		// 0 and 2 being things that never got used
+		std::string revision = parsed[1];
+		std::string url = inline_string_decode(parsed[3]);
+		std::string friendlyname = inline_string_decode(parsed[4]);
+		std::string description = inline_string_decode(parsed[5]);
 
-		// stable runners don't want unstable, not interesting, skip
-		if ((line_type == "branch" || line_type == "dev") && GetIsOfficialRelease())
+		if (atoi(revision.c_str()) <= GetSVNRevision())
 			continue;
 
-		// check if the tags match
-		if (line_tags_str.empty() || line_tags_str == "all")
-		{
-			// looking good
-		}
-		else
-		{
-			std::set<wxString> tags;
-			split_str(line_tags_str, " ", false, inserter(tags, tags.end()));
-			if (!includes(accept_tags.begin(), accept_tags.end(), tags.begin(), tags.end()))
-				continue;
-		}
-
-		if (line_type == "upgrade" || line_type == "bugfix")
-		{
-			// de facto interesting
-		}
-		else
-		{
-			// maybe interesting, check revision
-
-			long new_revision = 0;
-			if (!line_revision.ToLong(&new_revision)) continue;
-			if (new_revision <= GetSVNRevision())
-			{
-				// too old, not interesting, skip
-				continue;
-			}
-		}
-
-		// it's interesting!
-		result_event.AddUpdate(line_url, line_friendlyname, line_description);
+		results.emplace_back(url, friendlyname, description);
 	}
 
-	if (result_event.GetUpdates().size() > 0 || interactive)
-	{
-		wxTheApp->AddPendingEvent(result_event);
+	if (!results.empty() || interactive) {
+		agi::dispatch::Main().Async([=]{
+			wxString text;
+			if (results.size() == 1)
+				text = _("An update to Aegisub was found.");
+			else if (results.size() > 1)
+				text = _("Several possible updates to Aegisub were found.");
+			else
+				text = _("There are no updates to Aegisub.");
+
+			new VersionCheckerResultDialog(text, results);
+		});
 	}
 }
 
-class VersionCheckerResultDialog : public wxDialog {
-	void OnCloseButton(wxCommandEvent &evt);
-	void OnRemindMeLater(wxCommandEvent &evt);
-	void OnClose(wxCloseEvent &evt);
-
-	wxCheckBox *automatic_check_checkbox;
-
-public:
-	VersionCheckerResultDialog(const wxString &main_text, const std::vector<AegisubUpdateDescription> &updates);
-
-	bool ShouldPreventAppExit() const { return false; }
-};
-
-VersionCheckerResultDialog::VersionCheckerResultDialog(const wxString &main_text, const std::vector<AegisubUpdateDescription> &updates)
-: wxDialog(0, -1, _("Version Checker"))
-{
-	const int controls_width = 500;
-
-	wxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
-
-	wxStaticText *text = new wxStaticText(this, -1, main_text);
-	text->Wrap(controls_width);
-	main_sizer->Add(text, 0, wxBOTTOM|wxEXPAND, 6);
-
-	std::vector<AegisubUpdateDescription>::const_iterator upd_iterator = updates.begin();
-	for (; upd_iterator != updates.end(); ++upd_iterator)
-	{
-		main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND|wxALL, 6);
-
-		text = new wxStaticText(this, -1, upd_iterator->friendly_name);
-		wxFont boldfont = text->GetFont();
-		boldfont.SetWeight(wxFONTWEIGHT_BOLD);
-		text->SetFont(boldfont);
-		main_sizer->Add(text, 0, wxEXPAND|wxBOTTOM, 6);
-
-		wxTextCtrl *descbox = new wxTextCtrl(this, -1, upd_iterator->description, wxDefaultPosition, wxSize(controls_width,60), wxTE_MULTILINE|wxTE_READONLY);
-		main_sizer->Add(descbox, 0, wxEXPAND|wxBOTTOM, 6);
-
-		main_sizer->Add(new wxHyperlinkCtrl(this, -1, upd_iterator->url, upd_iterator->url), 0, wxALIGN_LEFT|wxBOTTOM, 6);
-	}
-
-	automatic_check_checkbox = new wxCheckBox(this, -1, _("&Auto Check for Updates"));
-	automatic_check_checkbox->SetValue(OPT_GET("App/Auto/Check For Updates")->GetBool());
-
-	wxButton *remind_later_button = 0;
-	if (updates.size() > 0)
-		remind_later_button = new wxButton(this, wxID_NO, _("Remind me again in a &week"));
-
-	wxButton *close_button = new wxButton(this, wxID_OK, _("&Close"));
-	SetAffirmativeId(wxID_OK);
-	SetEscapeId(wxID_OK);
-
-	if (updates.size())
-		main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND|wxALL, 6);
-	main_sizer->Add(automatic_check_checkbox, 0, wxEXPAND|wxBOTTOM, 6);
-
-	wxStdDialogButtonSizer *button_sizer = new wxStdDialogButtonSizer();
-	button_sizer->AddButton(close_button);
-	if (remind_later_button)
-		button_sizer->AddButton(remind_later_button);
-	button_sizer->Realize();
-	main_sizer->Add(button_sizer, 0, wxEXPAND, 0);
-
-	wxSizer *outer_sizer = new wxBoxSizer(wxVERTICAL);
-	outer_sizer->Add(main_sizer, 0, wxALL|wxEXPAND, 12);
-
-	SetSizerAndFit(outer_sizer);
-	Centre();
-	Show();
-
-	Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&VersionCheckerResultDialog::Close, this, false), wxID_OK);
-	Bind(wxEVT_COMMAND_BUTTON_CLICKED, &VersionCheckerResultDialog::OnRemindMeLater, this, wxID_NO);
-	Bind(wxEVT_CLOSE_WINDOW, &VersionCheckerResultDialog::OnClose, this);
 }
-void VersionCheckerResultDialog::OnRemindMeLater(wxCommandEvent &)
-{
-	// In one week
-	time_t new_next_check_time = wxDateTime::Today().GetTicks() + 7*24*60*60;
-	OPT_SET("Version/Next Check")->SetInt((int)new_next_check_time);
 
-	Close();
-}
+void PerformVersionCheck(bool interactive) {
+	agi::dispatch::Background().Async([=]{
+		if (!interactive) {
+			// Automatic checking enabled?
+			if (!OPT_GET("App/Auto/Check For Updates")->GetBool())
+				return;
+
+			// Is it actually time for a check?
+			time_t next_check = OPT_GET("Version/Next Check")->GetInt();
+			if (next_check > time(nullptr))
+				return;
+		}
 
-void VersionCheckerResultDialog::OnClose(wxCloseEvent &)
-{
-	OPT_SET("App/Auto/Check For Updates")->SetBool(automatic_check_checkbox->GetValue());
-	Destroy();
-}
+		if (!VersionCheckLock.try_lock()) return;
 
-static void on_update_result(AegisubVersionCheckResultEvent &evt)
-{
-	wxString text = evt.GetMainText();
-	if (!text)
-	{
-		if (evt.GetUpdates().size() == 1)
-		{
-			text = _("An update to Aegisub was found.");
+		try {
+			DoCheck(interactive);
 		}
-		else if (evt.GetUpdates().size() > 1)
-		{
-			text = _("Several possible updates to Aegisub were found.");
+		catch (const agi::Exception &e) {
+			PostErrorEvent(interactive, wxString::Format(
+				_("There was an error checking for updates to Aegisub:\n%s\n\nIf other applications can access the Internet fine, this is probably a temporary server problem on our end."),
+				e.GetMessage()));
 		}
-		else
-		{
-			text = _("There are no updates to Aegisub.");
+		catch (...) {
+			PostErrorEvent(interactive, _("An unknown error occurred while checking for updates to Aegisub."));
 		}
-	}
 
-	new VersionCheckerResultDialog(text, evt.GetUpdates());
-}
-
-static void register_event_handler()
-{
-	static bool is_registered = false;
-	if (is_registered) return;
+		VersionCheckLock.unlock();
 
-	wxTheApp->Bind(AEGISUB_EVENT_VERSIONCHECK_RESULT, on_update_result);
-	is_registered = true;
+		agi::dispatch::Main().Async([]{
+			time_t new_next_check_time = time(nullptr) + 60*60; // in one hour
+			OPT_SET("Version/Next Check")->SetInt(new_next_check_time);
+		});
+	});
 }
 
 #endif
diff --git a/aegisub/src/dialog_version_check.h b/aegisub/src/dialog_version_check.h
index 6e3c5be7e1444a63b4eec23b897e9078f1c8a601..d556d753a739872afe89fd373c37b4464ca0f413 100644
--- a/aegisub/src/dialog_version_check.h
+++ b/aegisub/src/dialog_version_check.h
@@ -32,7 +32,7 @@
 /// @ingroup configuration_ui
 ///
 
-class wxMutex;
+#include <mutex>
 
 /// @brief Check whether a newer version is available and report to the user if there is
 /// @param interactive If true, always check and report all results, both success and failure.
@@ -40,10 +40,9 @@ class wxMutex;
 ///                    new version actually exists.
 void PerformVersionCheck(bool interactive);
 
-
 /// @brief Mutex that is taken while version checking is being performed.
 ///
 /// A new version check can't be performed while this mutex is locked, checking whether
 /// it is locked is a way to disable UI to invoke a version check while one is being
 /// performed.
-extern wxMutex VersionCheckLock;
+extern std::mutex VersionCheckLock;
diff --git a/aegisub/src/dialog_video_details.cpp b/aegisub/src/dialog_video_details.cpp
index 0f2043dfee40897f5d6185896b8fabfd955af7ca..7d16eec9a4afba3f2bc877c560c77c690dd8b1f3 100644
--- a/aegisub/src/dialog_video_details.cpp
+++ b/aegisub/src/dialog_video_details.cpp
@@ -34,17 +34,18 @@
 
 #include "config.h"
 
-#include <wx/sizer.h>
-#include <wx/stattext.h>
-#include <wx/textctrl.h>
-
 #include "dialog_video_details.h"
 
+#include "compat.h"
 #include "include/aegisub/context.h"
 #include "utils.h"
 #include "video_context.h"
 #include "video_provider_manager.h"
 
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+
 static void make_field(wxWindow *parent, wxSizer *sizer, wxString const& name, wxString const& value) {
 	sizer->Add(new wxStaticText(parent, -1, name), 0, wxALIGN_CENTRE_VERTICAL);
 	sizer->Add(new wxTextCtrl(parent, -1, value, wxDefaultPosition, wxSize(300,-1), wxTE_READONLY), 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
@@ -79,11 +80,11 @@ DialogVideoDetails::DialogVideoDetails(agi::Context *c)
 	double fps = c->videoController->FPS().FPS();
 
 	wxFlexGridSizer *fg = new wxFlexGridSizer(2, 5, 10);
-	make_field(this, fg, _("File name:"), c->videoController->GetVideoName());
+	make_field(this, fg, _("File name:"), c->videoController->GetVideoName().wstring());
 	make_field(this, fg, _("FPS:"), wxString::Format("%.3f", fps));
 	make_field(this, fg, _("Resolution:"), wxString::Format("%dx%d (%s)", width, height, pretty_ar(width, height)));
 	make_field(this, fg, _("Length:"), wxString::Format(_("%d frames (%s)"), framecount, pretty_time_stamp(framecount, fps)));
-	make_field(this, fg, _("Decoder:"), c->videoController->GetProvider()->GetDecoderName());
+	make_field(this, fg, _("Decoder:"), to_wx(c->videoController->GetProvider()->GetDecoderName()));
 
 	wxStaticBoxSizer *video_sizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Video"));
 	video_sizer->Add(fg);
diff --git a/aegisub/src/export_fixstyle.cpp b/aegisub/src/export_fixstyle.cpp
index f43a5003e6f5b5d9e40c552ca9cfff49f5e5096f..de613258feef54478e36582b7b17844aebc11b54 100644
--- a/aegisub/src/export_fixstyle.cpp
+++ b/aegisub/src/export_fixstyle.cpp
@@ -36,18 +36,19 @@
 
 #include "export_fixstyle.h"
 
-#include <algorithm>
-#include <boost/algorithm/string/case_conv.hpp>
-#include <functional>
-
 #include "ass_file.h"
 #include "ass_dialogue.h"
 #include "compat.h"
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <functional>
+#include <wx/intl.h>
+
 AssFixStylesFilter::AssFixStylesFilter()
-: AssExportFilter(_("Fix Styles"), _("Fixes styles by replacing any style that isn't available on file with Default."), -5000)
+: AssExportFilter(from_wx(_("Fix Styles")), from_wx(_("Fixes styles by replacing any style that isn't available on file with Default.")), -5000)
 {
 }
 
@@ -57,7 +58,7 @@ void AssFixStylesFilter::ProcessSubs(AssFile *subs, wxWindow *) {
 	sort(begin(styles), end(styles));
 
 	for (auto diag : subs->Line | agi::of_type<AssDialogue>()) {
-		if (!binary_search(begin(styles), end(styles), from_wx(diag->Style.get().Lower())))
+		if (!binary_search(begin(styles), end(styles), boost::to_lower_copy(diag->Style.get())))
 			diag->Style = "Default";
 	}
 }
diff --git a/aegisub/src/export_framerate.cpp b/aegisub/src/export_framerate.cpp
index f734ec2e078ab4550901f8e6348c04f0206d63ff..b64267e44896bc82ff49f7618fedee24b2a0addc 100644
--- a/aegisub/src/export_framerate.cpp
+++ b/aegisub/src/export_framerate.cpp
@@ -34,6 +34,17 @@
 
 #include "config.h"
 
+#include "export_framerate.h"
+
+#include "ass_dialogue.h"
+#include "ass_file.h"
+#include "compat.h"
+#include "include/aegisub/context.h"
+#include "utils.h"
+#include "video_context.h"
+
+#include <libaegisub/of_type_adaptor.h>
+
 #include <utility>
 
 #include <wx/button.h>
@@ -44,17 +55,10 @@
 #include <wx/stattext.h>
 #include <wx/textctrl.h>
 
-#include "ass_dialogue.h"
-#include "ass_file.h"
-#include "export_framerate.h"
-#include "include/aegisub/context.h"
-#include "utils.h"
-#include "video_context.h"
-
-#include <libaegisub/of_type_adaptor.h>
-
 AssTransformFramerateFilter::AssTransformFramerateFilter()
-: AssExportFilter(_("Transform Framerate"), _("Transform subtitle times, including those in override tags, from an input framerate to an output framerate.\n\nThis is useful for converting regular time subtitles to VFRaC time subtitles for hardsubbing.\nIt can also be used to convert subtitles to a different speed video, such as NTSC to PAL speedup."), 1000)
+: AssExportFilter(from_wx(_("Transform Framerate")),
+	from_wx(_("Transform subtitle times, including those in override tags, from an input framerate to an output framerate.\n\nThis is useful for converting regular time subtitles to VFRaC time subtitles for hardsubbing.\nIt can also be used to convert subtitles to a different speed video, such as NTSC to PAL speedup.")),
+	1000)
 , c(0)
 , line(0)
 , newStart(0)
@@ -81,7 +85,9 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
 	wxButton *FromVideo = new wxButton(base,-1,_("From &video"));
 	if (Input->IsLoaded()) {
 		initialInput = wxString::Format("%2.3f",Input->FPS());
-		FromVideo->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &AssTransformFramerateFilter::OnFpsFromVideo, this);
+		FromVideo->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=](wxCommandEvent&) {
+			InputFramerate->SetValue(wxString::Format("%g", c->videoController->FPS().FPS()));
+		});
 	}
 	else {
 		initialInput = "23.976";
@@ -133,10 +139,6 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent, a
 	return base;
 }
 
-void AssTransformFramerateFilter::OnFpsFromVideo(wxCommandEvent &) {
-	InputFramerate->SetValue(wxString::Format("%g", c->videoController->FPS().FPS()));
-}
-
 void AssTransformFramerateFilter::LoadSettings(bool is_default, agi::Context *c) {
 	this->c = c;
 
diff --git a/aegisub/src/export_framerate.h b/aegisub/src/export_framerate.h
index 380079c6a5ee193af15211a1960d78004450137c..c5717c7fc78a226181ddee6d1ec78e9542cebaf6 100644
--- a/aegisub/src/export_framerate.h
+++ b/aegisub/src/export_framerate.h
@@ -33,6 +33,7 @@
 ///
 
 #include "ass_export_filter.h"
+
 #include <libaegisub/vfr.h>
 
 class AssDialogue;
@@ -83,9 +84,6 @@ class AssTransformFramerateFilter : public AssExportFilter {
 	///   2. The relative distance between the beginning of the frame which time
 	///      is in and the beginning of the next frame
 	int ConvertTime(int time);
-
-	/// From Video click handler
-	void OnFpsFromVideo(wxCommandEvent &);
 public:
 	/// Constructor
 	AssTransformFramerateFilter();
diff --git a/aegisub/src/factory_manager.h b/aegisub/src/factory_manager.h
index 3b17771b4d129d50f12180cacf7476f9e4cdeb31..710a314f36e6d35ba447292272c44863e8988063 100644
--- a/aegisub/src/factory_manager.h
+++ b/aegisub/src/factory_manager.h
@@ -22,12 +22,10 @@
 #pragma once
 
 #include <algorithm>
-#include <cctype>
 #include <map>
+#include <string>
 #include <vector>
 
-#include <wx/string.h>
-
 template <class func>
 class FactoryBase {
 protected:
diff --git a/aegisub/src/ffmpegsource_common.cpp b/aegisub/src/ffmpegsource_common.cpp
index 106bb157a682669132f6cab6b9ec73725e063e57..8893aaaed7d1b685d8c0b3bf97b5966d3cee05a6 100644
--- a/aegisub/src/ffmpegsource_common.cpp
+++ b/aegisub/src/ffmpegsource_common.cpp
@@ -45,15 +45,20 @@
 #include "standard_paths.h"
 #include "utils.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
 
+#include <boost/algorithm/string/predicate.hpp>
 #include <boost/crc.hpp>
+#include <boost/filesystem.hpp>
 #include <inttypes.h>
 
-#include <wx/dir.h>
+#include <wx/config.h>
 #include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
 
-#ifdef WIN32
+#ifdef _WIN32
+#include <objbase.h>
+
 static void deinit_com(bool) {
 	CoUninitialize();
 }
@@ -64,7 +69,7 @@ static void deinit_com(bool) { }
 FFmpegSourceProvider::FFmpegSourceProvider()
 : COMInited(false, deinit_com)
 {
-#ifdef WIN32
+#ifdef _WIN32
 	HRESULT res = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
 	if (SUCCEEDED(res))
 		COMInited = true;
@@ -76,18 +81,6 @@ FFmpegSourceProvider::FFmpegSourceProvider()
 	FFMS_Init(0, 1);
 }
 
-/// @brief Callback function that updates the indexing progress dialog
-/// @param Current	The current file positition in bytes
-/// @param Total	The total file size in bytes
-/// @param Private	A pointer to the progress sink to update
-/// @return			Returns non-0 if indexing is cancelled, 0 otherwise.
-///
-static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
-	agi::ProgressSink *ps = static_cast<agi::ProgressSink*>(Private);
-	ps->SetProgress(Current, Total);
-	return ps->IsCancelled();
-}
-
 /// @brief Does indexing of a source file
 /// @param Indexer		A pointer to the indexer object representing the file to be indexed
 /// @param CacheName    The filename of the output index file
@@ -95,14 +88,14 @@ static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *
 /// @param IgnoreDecodeErrors	True if audio decoding errors will be tolerated, false otherwise
 /// @return				Returns the index object on success, nullptr otherwise
 ///
-FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, const wxString &CacheName, int Trackmask, FFMS_IndexErrorHandling IndexEH) {
+FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, agi::fs::path const& CacheName, int Trackmask, FFMS_IndexErrorHandling IndexEH) {
 	char FFMSErrMsg[1024];
 	FFMS_ErrorInfo ErrInfo;
 	ErrInfo.Buffer		= FFMSErrMsg;
 	ErrInfo.BufferSize	= sizeof(FFMSErrMsg);
 	ErrInfo.ErrorType	= FFMS_ERROR_SUCCESS;
 	ErrInfo.SubType		= FFMS_ERROR_SUCCESS;
-	wxString MsgString;
+	std::string MsgString;
 
 	// set up progress dialog callback
 	DialogProgress Progress(wxGetApp().frame, _("Indexing"), _("Reading timecodes and frame/sample data"));
@@ -110,22 +103,23 @@ FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, const wxStri
 	// index all audio tracks
 	FFMS_Index *Index;
 	Progress.Run([&](agi::ProgressSink *ps) {
-		Index = FFMS_DoIndexing(Indexer, Trackmask, FFMS_TRACKMASK_NONE, nullptr, nullptr, IndexEH, UpdateIndexingProgress, ps, &ErrInfo);
+		Index = FFMS_DoIndexing(Indexer, Trackmask, FFMS_TRACKMASK_NONE, nullptr, nullptr, IndexEH,
+			static_cast<TIndexCallback>([](int64_t Current, int64_t Total, void *Private) -> int {
+				agi::ProgressSink *ps = static_cast<agi::ProgressSink*>(Private);
+				ps->SetProgress(Current, Total);
+				return ps->IsCancelled();
+			}),
+			ps, &ErrInfo);
 	});
 
 	if (Index == nullptr) {
-		MsgString.Append("Failed to index: ").Append(wxString(ErrInfo.Buffer, wxConvUTF8));
+		MsgString += "Failed to index: ";
+		MsgString += ErrInfo.Buffer;
 		throw MsgString;
 	}
 
 	// write index to disk for later use
-	// ignore write errors for now
-	FFMS_WriteIndex(CacheName.utf8_str(), Index, &ErrInfo);
-	/*if (FFMS_WriteIndex(CacheName.char_str(), Index, FFMSErrMsg, MsgSize)) {
-		wxString temp(FFMSErrMsg, wxConvUTF8);
-		MsgString << "Failed to write index: " << temp;
-		throw MsgString;
-	} */
+	FFMS_WriteIndex(CacheName.string().c_str(), Index, &ErrInfo);
 
 	return Index;
 }
@@ -134,14 +128,14 @@ FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, const wxStri
 /// @param Indexer	The indexer object representing the source file
 /// @param Type		The track type to look for
 /// @return			Returns a std::map with the track numbers as keys and the codec names as values.
-std::map<int,wxString> FFmpegSourceProvider::GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type) {
-	std::map<int,wxString> TrackList;
+std::map<int, std::string> FFmpegSourceProvider::GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type) {
+	std::map<int,std::string> TrackList;
 	int NumTracks = FFMS_GetNumTracksI(Indexer);
 
 	for (int i=0; i<NumTracks; i++) {
 		if (FFMS_GetTrackTypeI(Indexer, i) == Type) {
-			wxString CodecName(FFMS_GetCodecNameI(Indexer, i), wxConvUTF8);
-			TrackList.insert(std::pair<int,wxString>(i, CodecName));
+			std::string CodecName(FFMS_GetCodecNameI(Indexer, i));
+			TrackList.insert(std::pair<int,std::string>(i, CodecName));
 		}
 	}
 	return TrackList;
@@ -151,12 +145,12 @@ std::map<int,wxString> FFmpegSourceProvider::GetTracksOfType(FFMS_Indexer *Index
 /// @param TrackList	A std::map with the track numbers as keys and codec names as values
 /// @param Type			The track type to ask about
 /// @return				Returns the track number chosen (an integer >= 0) on success, or a negative integer if the user cancelled.
-int FFmpegSourceProvider::AskForTrackSelection(const std::map<int,wxString> &TrackList, FFMS_TrackType Type) {
+int FFmpegSourceProvider::AskForTrackSelection(const std::map<int, std::string> &TrackList, FFMS_TrackType Type) {
 	std::vector<int> TrackNumbers;
 	wxArrayString Choices;
 
 	for (auto const& track : TrackList) {
-		Choices.Add(wxString::Format(_("Track %02d: %s"), track.first, track.second));
+		Choices.Add(wxString::Format(_("Track %02d: %s"), track.first, to_wx(track.second)));
 		TrackNumbers.push_back(track.first);
 	}
 
@@ -173,37 +167,36 @@ int FFmpegSourceProvider::AskForTrackSelection(const std::map<int,wxString> &Tra
 
 /// @brief Set ffms2 log level according to setting in config.dat
 void FFmpegSourceProvider::SetLogLevel() {
-	wxString LogLevel = to_wx(OPT_GET("Provider/FFmpegSource/Log Level")->GetString());
+	std::string LogLevel = OPT_GET("Provider/FFmpegSource/Log Level")->GetString();
 
-	if (!LogLevel.CmpNoCase("panic"))
+	if (boost::iequals(LogLevel, "panic"))
 		FFMS_SetLogLevel(FFMS_LOG_PANIC);
-	else if (!LogLevel.CmpNoCase("fatal"))
+	else if (boost::iequals(LogLevel, "fatal"))
 		FFMS_SetLogLevel(FFMS_LOG_FATAL);
-	else if (!LogLevel.CmpNoCase("error"))
+	else if (boost::iequals(LogLevel, "error"))
 		FFMS_SetLogLevel(FFMS_LOG_ERROR);
-	else if (!LogLevel.CmpNoCase("warning"))
+	else if (boost::iequals(LogLevel, "warning"))
 		FFMS_SetLogLevel(FFMS_LOG_WARNING);
-	else if (!LogLevel.CmpNoCase("info"))
+	else if (boost::iequals(LogLevel, "info"))
 		FFMS_SetLogLevel(FFMS_LOG_INFO);
-	else if (!LogLevel.CmpNoCase("verbose"))
+	else if (boost::iequals(LogLevel, "verbose"))
 		FFMS_SetLogLevel(FFMS_LOG_VERBOSE);
-	else if (!LogLevel.CmpNoCase("debug"))
+	else if (boost::iequals(LogLevel, "debug"))
 		FFMS_SetLogLevel(FFMS_LOG_DEBUG);
 	else
 		FFMS_SetLogLevel(FFMS_LOG_QUIET);
 }
 
-
 FFMS_IndexErrorHandling FFmpegSourceProvider::GetErrorHandlingMode() {
-	wxString Mode = to_wx(OPT_GET("Provider/Audio/FFmpegSource/Decode Error Handling")->GetString());
+	std::string Mode = OPT_GET("Provider/Audio/FFmpegSource/Decode Error Handling")->GetString();
 
-	if (!Mode.CmpNoCase("ignore"))
+	if (boost::iequals(Mode, "ignore"))
 		return FFMS_IEH_IGNORE;
-	else if (!Mode.CmpNoCase("clear"))
+	else if (boost::iequals(Mode, "clear"))
 		return FFMS_IEH_CLEAR_TRACK;
-	else if (!Mode.CmpNoCase("stop"))
+	else if (boost::iequals(Mode, "stop"))
 		return FFMS_IEH_STOP_TRACK;
-	else if (!Mode.CmpNoCase("abort"))
+	else if (boost::iequals(Mode, "abort"))
 		return FFMS_IEH_ABORT;
 	else
 		return FFMS_IEH_STOP_TRACK; // questionable default?
@@ -212,34 +205,21 @@ FFMS_IndexErrorHandling FFmpegSourceProvider::GetErrorHandlingMode() {
 /// @brief	Generates an unique name for the ffms2 index file and prepares the cache folder if it doesn't exist
 /// @param filename	The name of the source file
 /// @return			Returns the generated filename.
-wxString FFmpegSourceProvider::GetCacheFilename(const wxString& _filename) {
+agi::fs::path FFmpegSourceProvider::GetCacheFilename(agi::fs::path const& filename) {
 	// Get the size of the file to be hashed
-	wxFileOffset len = 0;
-	{
-		wxFile file(_filename,wxFile::read);
-		if (file.IsOpened()) len = file.Length();
-	}
-
-	wxFileName filename(_filename);
+	uintmax_t len = agi::fs::Size(filename);
 
 	// Get the hash of the filename
-	wxString toHash = filename.GetFullName();
 	boost::crc_32_type hash;
-	hash.process_bytes(toHash.wc_str(), toHash.size() * sizeof(wchar_t));
+	hash.process_bytes(filename.string().c_str(), filename.string().size());
 
 	// Generate the filename
-	wxString result = wxString::Format("?local/ffms2cache/%u_%" PRId64 "_%" PRId64 ".ffindex", hash.checksum(), len, (int64_t)filename.GetModificationTime().GetTicks());
-	result = StandardPaths::DecodePath(result);
+	auto result = StandardPaths::DecodePath("?local/ffms2cache/" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".ffindex");
 
 	// Ensure that folder exists
-	wxFileName fn(result);
-	wxString dir = fn.GetPath();
-	if (!wxFileName::DirExists(dir)) {
-		wxFileName::Mkdir(dir);
-	}
+	agi::fs::CreateDirectory(result.parent_path());
 
-	wxFileName dirfn(dir);
-	return dirfn.GetShortPath() + "/" + fn.GetFullName();
+	return result;
 }
 
 /// @brief		Starts the cache cleaner thread
diff --git a/aegisub/src/ffmpegsource_common.h b/aegisub/src/ffmpegsource_common.h
index ec037e6fab33510d77c64535238026420a93e5a9..dbb05314b19730a83a9dde99011c64e4295f736f 100644
--- a/aegisub/src/ffmpegsource_common.h
+++ b/aegisub/src/ffmpegsource_common.h
@@ -33,14 +33,11 @@
 ///
 
 #ifdef WITH_FFMS2
-
 #include <map>
 
-#include <wx/filename.h>
-#include <wx/thread.h>
-
 #include <ffms.h>
 
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/scoped_ptr.h>
 
 /// Index all tracks
@@ -72,10 +69,10 @@ public:
 
 	void CleanCache();
 
-	FFMS_Index *DoIndexing(FFMS_Indexer *Indexer, const wxString& Cachename, int Trackmask, FFMS_IndexErrorHandling IndexEH);
-	std::map<int,wxString> GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type);
-	int AskForTrackSelection(const std::map<int,wxString>& TrackList, FFMS_TrackType Type);
-	wxString GetCacheFilename(const wxString& filename);
+	FFMS_Index *DoIndexing(FFMS_Indexer *Indexer, agi::fs::path const& Cachename, int Trackmask, FFMS_IndexErrorHandling IndexEH);
+	std::map<int, std::string> GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type);
+	int AskForTrackSelection(const std::map<int, std::string>& TrackList, FFMS_TrackType Type);
+	agi::fs::path GetCacheFilename(agi::fs::path const& filename);
 	void SetLogLevel();
 	FFMS_IndexErrorHandling GetErrorHandlingMode();
 
diff --git a/aegisub/src/font_file_lister.cpp b/aegisub/src/font_file_lister.cpp
index 4e5e223774a2f6458d0af7dd74d0da7d9d432528..aacedc3bc018b1c6b8364b9139182ced061a8944 100644
--- a/aegisub/src/font_file_lister.cpp
+++ b/aegisub/src/font_file_lister.cpp
@@ -32,6 +32,7 @@
 #include <libaegisub/of_type_adaptor.h>
 
 #include <algorithm>
+#include <tuple>
 
 #include <wx/intl.h>
 
@@ -49,7 +50,7 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) {
 	if (line->Comment) return;
 
 	boost::ptr_vector<AssDialogueBlock> blocks(line->ParseTags());
-	StyleInfo style = styles[from_wx(line->Style)];
+	StyleInfo style = styles[line->Style];
 	StyleInfo initial = style;
 
 	bool overriden = false;
@@ -60,7 +61,7 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) {
 				std::string const& name = tag.Name;
 
 				if (name == "\\r") {
-					style = styles[tag.Params[0].Get(from_wx(line->Style))];
+					style = styles[tag.Params[0].Get(line->Style.get())];
 					overriden = false;
 				}
 				else if (name == "\\b") {
@@ -117,7 +118,7 @@ void FontCollector::ProcessChunk(std::pair<StyleInfo, UsageData> const& style) {
 	else {
 		for (size_t i = 0; i < res.paths.size(); ++i) {
 			if (results.insert(res.paths[i]).second)
-				status_callback(wxString::Format(_("Found '%s' at '%s'\n"), style.first.facename, res.paths[i]), 0);
+				status_callback(wxString::Format(_("Found '%s' at '%s'\n"), style.first.facename, res.paths[i].make_preferred().wstring()), 0);
 		}
 
 		if (res.missing.size()) {
@@ -147,7 +148,7 @@ void FontCollector::PrintUsage(UsageData const& data) {
 	status_callback("\n", 2);
 }
 
-std::vector<wxString> FontCollector::GetFontPaths(const AssFile *file) {
+std::vector<agi::fs::path> FontCollector::GetFontPaths(const AssFile *file) {
 	missing = 0;
 	missing_glyphs = 0;
 
@@ -169,7 +170,7 @@ std::vector<wxString> FontCollector::GetFontPaths(const AssFile *file) {
 	for_each(used_styles.begin(), used_styles.end(), bind(&FontCollector::ProcessChunk, this, _1));
 	status_callback(_("Done\n\n"), 0);
 
-	std::vector<wxString> paths;
+	std::vector<agi::fs::path> paths;
 	paths.reserve(results.size());
 	paths.insert(paths.end(), results.begin(), results.end());
 
@@ -184,15 +185,5 @@ std::vector<wxString> FontCollector::GetFontPaths(const AssFile *file) {
 }
 
 bool FontCollector::StyleInfo::operator<(StyleInfo const& rgt) const {
-#define CMP(field) \
-	if (field < rgt.field) return true; \
-	if (field > rgt.field) return false
-
-	CMP(facename);
-	CMP(bold);
-	CMP(italic);
-
-#undef CMP
-
-	return false;
+	return std::tie(facename, bold, italic) < std::tie(rgt.facename, rgt.bold, rgt.italic);
 }
diff --git a/aegisub/src/font_file_lister.h b/aegisub/src/font_file_lister.h
index 8156a9699c5416f79cf202935e617848c6c2441e..87efd4a5bcfd88dfec318d846d55b107c25028ed 100644
--- a/aegisub/src/font_file_lister.h
+++ b/aegisub/src/font_file_lister.h
@@ -21,10 +21,13 @@
 
 #pragma once
 
-#include <list>
-#include <map>
+#include <libaegisub/fs_fwd.h>
+
+#include <boost/filesystem/path.hpp>
 #include <functional>
+#include <map>
 #include <set>
+#include <string>
 #include <vector>
 
 #include <wx/string.h>
@@ -42,7 +45,7 @@ public:
 		/// Characters which could not be found in any font files
 		wxString missing;
 		/// Paths to the file(s) containing the requested font
-		std::vector<wxString> paths;
+		std::vector<agi::fs::path> paths;
 	};
 
 	/// @brief Get the path to the font with the given styles
@@ -82,7 +85,7 @@ class FontCollector {
 	/// Style name -> ASS style definition
 	std::map<std::string, StyleInfo> styles;
 	/// Paths to found required font files
-	std::set<wxString> results;
+	std::set<agi::fs::path> results;
 	/// Number of fonts which could not be found
 	int missing;
 	/// Number of fonts which were found, but did not contain all used glyphs
@@ -107,5 +110,5 @@ public:
 	/// @param file Lines in the subtitle file to check
 	/// @param status Callback function for messages
 	/// @return List of paths to fonts
-	std::vector<wxString> GetFontPaths(const AssFile *file);
+	std::vector<agi::fs::path> GetFontPaths(const AssFile *file);
 };
diff --git a/aegisub/src/font_file_lister_fontconfig.cpp b/aegisub/src/font_file_lister_fontconfig.cpp
index 4755c548095c4ce8824fe647e30c536f7650c402..ca4cd865cf40f9d4a275bd2e9f816bcf89fe8f87 100644
--- a/aegisub/src/font_file_lister_fontconfig.cpp
+++ b/aegisub/src/font_file_lister_fontconfig.cpp
@@ -26,8 +26,6 @@
 
 #include <fontconfig/fontconfig.h>
 
-#include "compat.h"
-
 #include <libaegisub/log.h>
 #include <libaegisub/util.h>
 
@@ -36,6 +34,7 @@
 #endif
 
 #include <boost/algorithm/string/case_conv.hpp>
+#include <boost/filesystem/path.hpp>
 
 #include <wx/intl.h>
 
@@ -139,7 +138,7 @@ FontFileLister::CollectionResult FontConfigFontFileLister::GetFontPaths(std::str
 		}
 	}
 
-	ret.paths.push_back((const char *)file);
+	ret.paths.emplace_back((const char *)file);
 	return ret;
 }
 #endif
diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp
index d5ee8fc7df3e3c08df4d4188a9c362c8f5db21d1..cfaf14c20870f7ec635260d0f348090c947b0e69 100644
--- a/aegisub/src/frame_main.cpp
+++ b/aegisub/src/frame_main.cpp
@@ -35,16 +35,10 @@
 
 #include "frame_main.h"
 
-#include <wx/clipbrd.h>
-#include <wx/dnd.h>
-#include <wx/filename.h>
-#include <wx/image.h>
-#include <wx/msgdlg.h>
-#include <wx/statline.h>
-#include <wx/sysopt.h>
-#include <wx/tokenzr.h>
-
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
+#include <libaegisub/path.h>
+#include <libaegisub/util.h>
 
 #include "include/aegisub/context.h"
 #include "include/aegisub/menu.h"
@@ -66,7 +60,6 @@
 #include "main.h"
 #include "options.h"
 #include "search_replace_engine.h"
-#include "standard_paths.h"
 #include "subs_edit_box.h"
 #include "subs_edit_ctrl.h"
 #include "subs_grid.h"
@@ -79,10 +72,18 @@
 #include "video_provider_manager.h"
 #include "video_slider.h"
 
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <wx/dnd.h>
+#include <wx/filename.h>
+#include <wx/image.h>
+#include <wx/msgdlg.h>
+#include <wx/statline.h>
+#include <wx/sysopt.h>
+
 enum {
 	ID_APP_TIMER_AUTOSAVE					= 12001,
-	ID_APP_TIMER_STATUSCLEAR				= 12002,
-	ID_SASH_MAIN_AUDIO						= 14001
+	ID_APP_TIMER_STATUSCLEAR				= 12002
 };
 
 #ifdef WITH_STARTUPLOG
@@ -95,7 +96,7 @@ static void autosave_timer_changed(wxTimer *timer);
 
 wxDEFINE_EVENT(FILE_LIST_DROPPED, wxThreadEvent);
 
-static void get_files_to_load(wxArrayString const& list, wxString &subs, wxString &audio, wxString &video) {
+static void get_files_to_load(wxArrayString const& list, std::string &subs, std::string &audio, std::string &video) {
 	// Keep these lists sorted
 
 	// Video formats
@@ -155,11 +156,11 @@ static void get_files_to_load(wxArrayString const& list, wxString &subs, wxStrin
 		wxString ext = file.GetExt().Lower();
 
 		if (subs.empty() && std::binary_search(subsList, subsList + countof(subsList), ext))
-			subs = file.GetFullPath();
+			subs = from_wx(file.GetFullPath());
 		if (video.empty() && std::binary_search(videoList, videoList + countof(videoList), ext))
-			video = file.GetFullPath();
+			video = from_wx(file.GetFullPath());
 		if (audio.empty() && std::binary_search(audioList, audioList + countof(audioList), ext))
-			audio = file.GetFullPath();
+			audio = from_wx(file.GetFullPath());
 	}
 }
 
@@ -169,10 +170,10 @@ class AegisubFileDropTarget : public wxFileDropTarget {
 public:
 	AegisubFileDropTarget(FrameMain *parent) : parent(parent) { }
 	bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) {
-		wxString subs, audio, video;
+		std::string subs, audio, video;
 		get_files_to_load(filenames, subs, audio, video);
 
-		if (!subs && !audio && !video)
+		if (subs.empty() && audio.empty() && video.empty())
 			return false;
 
 		wxThreadEvent *evt = new wxThreadEvent(FILE_LIST_DROPPED);
@@ -407,7 +408,7 @@ void FrameMain::InitContents() {
 	StartupLog("Leaving InitContents");
 }
 
-void FrameMain::LoadSubtitles(wxString const& filename, wxString const& charset) {
+void FrameMain::LoadSubtitles(agi::fs::path const& filename, std::string const& charset) {
 	if (context->ass->loaded) {
 		if (TryToCloseSubs() == wxCANCEL) return;
 	}
@@ -416,8 +417,8 @@ void FrameMain::LoadSubtitles(wxString const& filename, wxString const& charset)
 		// Make sure that file isn't actually a timecode file
 		try {
 			TextFileReader testSubs(filename, charset);
-			wxString cur = testSubs.ReadLineFromFile();
-			if (cur.Left(10) == "# timecode") {
+			std::string cur = testSubs.ReadLineFromFile();
+			if (boost::starts_with(cur, "# timecode")) {
 				context->videoController->LoadTimecodes(filename);
 				return;
 			}
@@ -429,29 +430,27 @@ void FrameMain::LoadSubtitles(wxString const& filename, wxString const& charset)
 
 		context->ass->Load(filename, charset);
 
-		wxFileName file(filename);
-		StandardPaths::SetPathValue("?script", file.GetPath());
-		config::mru->Add("Subtitle", from_wx(filename));
-		OPT_SET("Path/Last/Subtitles")->SetString(from_wx(file.GetPath()));
+		StandardPaths::SetPathValue("?script", filename);
+		config::mru->Add("Subtitle", filename);
+		OPT_SET("Path/Last/Subtitles")->SetString(filename.parent_path().string());
 
 		// Save backup of file
 		if (context->ass->CanSave() && OPT_GET("App/Auto/Backup")->GetBool()) {
-			if (file.FileExists()) {
-				wxString path = to_wx(OPT_GET("Path/Auto/Backup")->GetString());
-				if (path.empty()) path = file.GetPath();
-				wxFileName dstpath(StandardPaths::DecodePath(path + "/"));
-				if (!dstpath.DirExists())
-					wxMkdir(dstpath.GetPath());
-
-				dstpath.SetFullName(file.GetName() + ".ORIGINAL." + file.GetExt());
-
-				wxCopyFile(file.GetFullPath(), dstpath.GetFullPath(), true);
+			if (agi::fs::FileExists(filename)) {
+				auto path_str = OPT_GET("Path/Auto/Backup")->GetString();
+				agi::fs::path path;
+				if (path_str.empty())
+					path = filename.parent_path();
+				else
+					path = StandardPaths::DecodePath(path_str);
+				agi::fs::CreateDirectory(path);
+				agi::fs::Copy(filename, path/(filename.stem().string() + ".ORIGINAL" + filename.extension().string()));
 			}
 		}
 	}
-	catch (agi::FileNotFoundError const&) {
-		wxMessageBox(filename + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
-		config::mru->Remove("Subtitle", from_wx(filename));
+	catch (agi::fs::FileNotFound const&) {
+		wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
+		config::mru->Remove("Subtitle", filename);
 		return;
 	}
 	catch (agi::Exception const& err) {
@@ -561,7 +560,7 @@ void FrameMain::OnVideoOpen() {
 		catch (agi::UserCancelException const&) { }
 		// Opening a video with no audio data isn't an error, so just log
 		// and move on
-		catch (agi::FileNotAccessibleError const&) {
+		catch (agi::fs::FileNotFound const&) {
 			LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " found by video provider but not audio provider";
 		}
 		catch (agi::AudioDataNotFoundError const& e) {
@@ -591,7 +590,7 @@ void FrameMain::OnFilesDropped(wxThreadEvent &evt) {
 }
 
 bool FrameMain::LoadList(wxArrayString list) {
-	wxString audio, video, subs;
+	std::string audio, video, subs;
 	get_files_to_load(list, subs, audio, video);
 
 	blockVideoLoad = !video.empty();
@@ -660,19 +659,13 @@ void FrameMain::OnCloseWindow (wxCloseEvent &event) {
 }
 
 void FrameMain::OnAutoSave(wxTimerEvent &) try {
-	wxString fn = context->ass->AutoSave();
+	auto fn = context->ass->AutoSave();
 	if (!fn.empty())
-		StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn));
+		StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn.wstring()));
 }
 catch (const agi::Exception& err) {
 	StatusTimeout(to_wx("Exception when attempting to autosave file: " + err.GetMessage()));
 }
-catch (wxString err) {
-	StatusTimeout("Exception when attempting to autosave file: " + err);
-}
-catch (const char *err) {
-	StatusTimeout("Exception when attempting to autosave file: " + wxString(err));
-}
 catch (...) {
 	StatusTimeout("Unhandled exception when attempting to autosave file.");
 }
@@ -696,15 +689,15 @@ void FrameMain::OnSubtitlesOpen() {
 	///       prompting for each file loaded/unloaded
 
 	// Load stuff from the new script
-	wxString curSubsVideo = DecodeRelativePath(context->ass->GetScriptInfo("Video File"), context->ass->filename);
-	wxString curSubsVFR = DecodeRelativePath(context->ass->GetScriptInfo("VFR File"), context->ass->filename);
-	wxString curSubsKeyframes = DecodeRelativePath(context->ass->GetScriptInfo("Keyframes File"), context->ass->filename);
-	wxString curSubsAudio = DecodeRelativePath(context->ass->GetScriptInfo("Audio URI"), context->ass->filename);
+	auto video     = config::path->MakeAbsolute(context->ass->GetScriptInfo("Video File"), "?script");
+	auto vfr       = config::path->MakeAbsolute(context->ass->GetScriptInfo("VFR File"), "?script");
+	auto keyframes = config::path->MakeAbsolute(context->ass->GetScriptInfo("Keyframes File"), "?script");
+	auto audio     = config::path->MakeAbsolute(context->ass->GetScriptInfo("Audio URI"), "?script");
 
-	bool videoChanged = !blockVideoLoad && curSubsVideo != context->videoController->GetVideoName();
-	bool timecodesChanged = curSubsVFR != context->videoController->GetTimecodesName();
-	bool keyframesChanged = curSubsKeyframes != context->videoController->GetKeyFramesName();
-	bool audioChanged = !blockAudioLoad && curSubsAudio != context->audioController->GetAudioURL();
+	bool videoChanged     = !blockVideoLoad && video != context->videoController->GetVideoName();
+	bool timecodesChanged = vfr != context->videoController->GetTimecodesName();
+	bool keyframesChanged = keyframes != context->videoController->GetKeyFramesName();
+	bool audioChanged     = !blockAudioLoad && audio != context->audioController->GetAudioURL();
 
 	// Check if there is anything to change
 	int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
@@ -725,43 +718,42 @@ void FrameMain::OnSubtitlesOpen() {
 
 	// Video
 	if (videoChanged) {
-		wxString arString = context->ass->GetScriptInfo("Video Aspect Ratio");
-		context->videoController->SetVideo(curSubsVideo);
+		context->videoController->SetVideo(video);
 		if (context->videoController->IsLoaded()) {
 			context->videoController->JumpToFrame(context->ass->GetScriptInfoAsInt("Video Position"));
 
-			long videoAr = 0;
+			int videoAr = 0;
 			double videoArValue = 0.;
-			if (arString.StartsWith("c")) {
+			std::string arString = context->ass->GetScriptInfo("Video Aspect Ratio");
+			if (boost::starts_with(arString, "c")) {
 				videoAr = 4;
-				arString.Mid(1).ToDouble(&videoArValue);
+				agi::util::try_parse(arString.substr(1), &videoArValue);
 			}
 			else
-				arString.ToLong(&videoAr);
+				agi::util::try_parse(arString.substr(1), &videoAr);
 
 			context->videoController->SetAspectRatio(videoAr, videoArValue);
 
 			double videoZoom = 0.;
-			if (context->ass->GetScriptInfo("Video Zoom Percent").ToDouble(&videoZoom))
+			if (agi::util::try_parse(context->ass->GetScriptInfo("Video Zoom Percent"), &videoZoom))
 				context->videoDisplay->SetZoom(videoZoom);
 		}
 	}
 
-	context->videoController->LoadTimecodes(curSubsVFR);
-	context->videoController->LoadKeyframes(curSubsKeyframes);
+	context->videoController->LoadTimecodes(vfr);
+	context->videoController->LoadKeyframes(keyframes);
 
 	// Audio
 	if (audioChanged) {
 		blockAudioLoad = false;
 		try {
-			if (!curSubsAudio)
+			if (audio.empty())
 				context->audioController->CloseAudio();
 			else
-				context->audioController->OpenAudio(curSubsAudio);
+				context->audioController->OpenAudio(audio);
 		}
 		catch (agi::UserCancelException const&) { }
-		catch (agi::FileNotAccessibleError const& err) {
-			config::mru->Remove("Audio", from_wx(curSubsAudio));
+		catch (agi::fs::FileSystemError const& err) {
 			wxMessageBox(to_wx(err.GetMessage()), "Error opening audio", wxOK | wxICON_ERROR | wxCENTER, this);
 		}
 	}
@@ -789,5 +781,5 @@ wxString FrameMain::GetScriptFileName() const {
 #endif
 	}
 	else
-		return wxFileName(context->ass->filename).GetFullName();
+		return context->ass->filename.filename().wstring();
 }
diff --git a/aegisub/src/frame_main.h b/aegisub/src/frame_main.h
index 2ee78ab0f0d3d841d90973cb458c4bd0e3073890..5e2a7daa7d3f4b9ea7f0a02b7a0cfa90c3367673 100644
--- a/aegisub/src/frame_main.h
+++ b/aegisub/src/frame_main.h
@@ -32,6 +32,9 @@
 /// @ingroup main_ui
 ///
 
+#include <libaegisub/fs_fwd.h>
+
+#include <memory>
 #include <vector>
 
 #include <wx/combobox.h>
@@ -42,8 +45,6 @@
 #include <wx/sizer.h>
 #include <wx/timer.h>
 
-#include <libaegisub/scoped_ptr.h>
-
 class AegisubFileDropTarget;
 class AssFile;
 class AudioBox;
@@ -63,7 +64,7 @@ namespace agi { struct Context; class OptionValue; }
 class FrameMain: public wxFrame {
 	friend class AegisubFileDropTarget;
 
-	agi::scoped_ptr<agi::Context> context;
+	std::unique_ptr<agi::Context> context;
 
     // XXX: Make Freeze()/Thaw() noops on GTK, this seems to be buggy
 #ifdef __WXGTK__
@@ -137,7 +138,7 @@ public:
 	/// @param enableCancel Should the user be able to cancel the close?
 	int TryToCloseSubs(bool enableCancel=true);
 
-	void LoadSubtitles(wxString const& filename, wxString const& charset="");
+	void LoadSubtitles(agi::fs::path const& filename, std::string const& charset="");
 
 	DECLARE_EVENT_TABLE()
 };
diff --git a/aegisub/src/gl_text.cpp b/aegisub/src/gl_text.cpp
index 43bd695fe7184e8a5d8061f1b4dff4cd2e884715..2d883dc017a82c87e2b81bc98408441013558c01 100644
--- a/aegisub/src/gl_text.cpp
+++ b/aegisub/src/gl_text.cpp
@@ -36,14 +36,17 @@
 
 #include "gl_text.h"
 
+#include "compat.h"
+#include "utils.h"
+
+#include <libaegisub/color.h>
+
 #include <wx/bitmap.h>
 #include <wx/dcmemory.h>
 
 #include <algorithm>
 #include <boost/noncopyable.hpp>
 
-#include "utils.h"
-
 #ifdef HAVE_OPENGL_GL_H
 #include <OpenGL/gl.h>
 #else
@@ -231,7 +234,6 @@ OpenGLText::OpenGLText()
 , g(1.f)
 , b(1.f)
 , a(1.f)
-, lineHeight(0)
 , fontSize(0)
 , fontBold(false)
 , fontItalics(false)
@@ -241,7 +243,7 @@ OpenGLText::OpenGLText()
 OpenGLText::~OpenGLText() {
 }
 
-void OpenGLText::SetFont(wxString face, int size, bool bold, bool italics) {
+void OpenGLText::SetFont(std::string const& face, int size, bool bold, bool italics) {
 	// No change required
 	if (size == fontSize && face == fontFace && bold == fontBold && italics == fontItalics) return;
 
@@ -250,7 +252,7 @@ void OpenGLText::SetFont(wxString face, int size, bool bold, bool italics) {
 	fontSize = size;
 	fontBold = bold;
 	fontItalics = italics;
-	font.SetFaceName(fontFace);
+	font.SetFaceName(to_wx(fontFace));
 	font.SetPointSize(size);
 	font.SetWeight(bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL);
 
@@ -259,14 +261,14 @@ void OpenGLText::SetFont(wxString face, int size, bool bold, bool italics) {
 	glyphs.clear();
 }
 
-void OpenGLText::SetColour(wxColour col, float alpha) {
-	r = col.Red()   / 255.f;
-	g = col.Green() / 255.f;
-	b = col.Blue()  / 255.f;
-	a = alpha;
+void OpenGLText::SetColour(agi::Color col) {
+	r = col.r / 255.f;
+	g = col.g / 255.f;
+	b = col.b / 255.f;
+	a = col.a / 255.f;
 }
 
-void OpenGLText::Print(const wxString &text, int x, int y) {
+void OpenGLText::Print(const std::string &text, int x, int y) {
 	glEnable(GL_BLEND);
 	glEnable(GL_TEXTURE_2D);
 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -287,54 +289,22 @@ void OpenGLText::Print(const wxString &text, int x, int y) {
 	glDisable(GL_BLEND);
 }
 
-void OpenGLText::DrawString(const wxString &text, int x, int y) {
-	lineHeight = 0;
-	int dx=x, dy=y;
-
-	for (int curChar : text) {
-		// Handle carriage returns
-		if (curChar == '\n') {
-			dx = x;
-			dy += lineHeight;
-		}
-
-		// Handle normal glyphs
-		else {
-			OpenGLTextGlyph const& glyph = GetGlyph(curChar);
-			glyph.Draw(dx, dy);
-			dx += glyph.w;
-			if (glyph.h > lineHeight) lineHeight = glyph.h;
-		}
+void OpenGLText::DrawString(const std::string &text, int x, int y) {
+	for (char curChar : text) {
+		OpenGLTextGlyph const& glyph = GetGlyph(curChar);
+		glyph.Draw(x, y);
+		x += glyph.w;
 	}
 }
 
-void OpenGLText::GetExtent(wxString const& text, int &w, int &h) {
-	lineHeight = 0;
-	int dx=0, dy=0;
-	w = 0;
-	h = 0;
-
-	// Simulate drawing of string
-	for (int curChar : text) {
-		// Handle carriage returns
-		if (curChar == '\n') {
-			if (dx > w) w = dx;
-			dx = 0;
-			dy += lineHeight;
-			lineHeight = 0;
-		}
+void OpenGLText::GetExtent(std::string const& text, int &w, int &h) {
+	w = h = 0;
 
-		// Handle normal glyphs
-		else {
-			OpenGLTextGlyph const& glyph = GetGlyph(curChar);
-			dx += glyph.w;
-			if (glyph.h > lineHeight) lineHeight = glyph.h;
-		}
+	for (char curChar : text) {
+		OpenGLTextGlyph const& glyph = GetGlyph(curChar);
+		w += glyph.w;
+		h = std::max(h, glyph.h);
 	}
-
-	// Return results
-	if (dx > w) w = dx;
-	h = dy+lineHeight;
 }
 
 OpenGLTextGlyph const& OpenGLText::GetGlyph(int i) {
diff --git a/aegisub/src/gl_text.h b/aegisub/src/gl_text.h
index c1870ee401417ce4b478662d5d506b732b5cb617..dbb0a3991cea1d094806c079bdeab69250095436 100644
--- a/aegisub/src/gl_text.h
+++ b/aegisub/src/gl_text.h
@@ -35,9 +35,9 @@
 #include <boost/container/map.hpp>
 
 #include <memory>
+#include <string>
 #include <vector>
 
-#include <wx/colour.h>
 #include <wx/font.h>
 
 namespace {
@@ -45,16 +45,17 @@ struct OpenGLTextGlyph;
 class OpenGLTextTexture;
 }
 
-typedef boost::container::map<int,OpenGLTextGlyph> glyphMap;
+namespace agi { struct Color; }
+
+typedef boost::container::map<int, OpenGLTextGlyph> glyphMap;
 
 class OpenGLText {
 	float r,g,b,a;
 
-	int lineHeight;
 	int fontSize;
 	bool fontBold;
 	bool fontItalics;
-	wxString fontFace;
+	std::string fontFace;
 	wxFont font;
 
 	glyphMap glyphs;
@@ -71,7 +72,7 @@ class OpenGLText {
 	/// @brief Create a new glyph
 	OpenGLTextGlyph const& CreateGlyph(int chr);
 
-	void DrawString(const wxString &text,int x,int y);
+	void DrawString(const std::string &text,int x,int y);
 public:
 	/// @brief Get the currently active font
 	wxFont GetFont() const { return font; }
@@ -81,21 +82,20 @@ public:
 	/// @param size    Size in points of the desired font
 	/// @param bold    Should the font be bold?
 	/// @param italics Should the font be italic?
-	void SetFont(wxString face,int size,bool bold,bool italics);
+	void SetFont(std::string const& face, int size, bool bold, bool italics);
 	/// @brief Set the text color
 	/// @param col   Color
-	/// @param alpha Alpha value from 0.f-1.f
-	void SetColour(wxColour col,float alpha);
+	void SetColour(agi::Color col);
 	/// @brief Print a string on screen
 	/// @param text String to print
 	/// @param x    x coordinate
 	/// @param y    y coordinate
-	void Print(const wxString &text,int x,int y);
+	void Print(const std::string &text,int x,int y);
 	/// @brief Get the extents of a string printed with the current font in pixels
 	/// @param text String to get extends of
 	/// @param[out] w    Width
 	/// @param[out] h    Height
-	void GetExtent(const wxString &text,int &w,int &h);
+	void GetExtent(const std::string &text,int &w,int &h);
 
 	OpenGLText();
 	~OpenGLText();
diff --git a/aegisub/src/gl_wrap.cpp b/aegisub/src/gl_wrap.cpp
index 9df8ee7072ae668b158f7bb3637ab5c624f5667b..03949b4df94613609438f067f5037ec2ffd90fa1 100644
--- a/aegisub/src/gl_wrap.cpp
+++ b/aegisub/src/gl_wrap.cpp
@@ -24,7 +24,7 @@
 
 #include "gl_wrap.h"
 
-#include <wx/msgdlg.h>
+#include <wx/colour.h>
 
 #ifdef HAVE_OPENGL_GL_H
 #include <OpenGL/gl.h>
diff --git a/aegisub/src/help_button.cpp b/aegisub/src/help_button.cpp
index 2030e25945f7fb78ede809080a32098521fccdbd..1b277837e6b992b7ee1067ef5bc3b267bfa0c0d9 100644
--- a/aegisub/src/help_button.cpp
+++ b/aegisub/src/help_button.cpp
@@ -36,18 +36,16 @@
 
 #include "help_button.h"
 
-#include <algorithm>
+#include "standard_paths.h"
+
+#include <boost/filesystem/path.hpp>
 #include <functional>
 #include <map>
 
 #include <wx/filename.h>
-#include <wx/msgdlg.h>
 
 #include <libaegisub/exception.h>
 
-#include "standard_paths.h"
-#include "utils.h"
-
 static std::map<wxString,wxString> *pages = 0;
 
 static void init_static() {
@@ -94,7 +92,7 @@ void HelpButton::OpenPage(wxString const& pageID) {
 	wxString section;
 	page = page.BeforeFirst('#', &section);
 
-	wxFileName docFile(StandardPaths::DecodePath("?data/docs/"), page, "html", wxPATH_NATIVE);
+	wxFileName docFile(StandardPaths::DecodePath("?data/docs/").wstring(), page, "html", wxPATH_NATIVE);
 
 	wxString url;
 	// If we can read a file by the constructed name, assume we have a local copy of the manual
diff --git a/aegisub/src/hotkey.cpp b/aegisub/src/hotkey.cpp
index 6431985cc55133f0c10415a685b3e90317942393..9375c52b0678151f8b4db37f54ed79b189c832b4 100644
--- a/aegisub/src/hotkey.cpp
+++ b/aegisub/src/hotkey.cpp
@@ -24,7 +24,6 @@
 
 #include "libresrc/libresrc.h"
 #include "command/command.h"
-#include "compat.h"
 #include "options.h"
 #include "standard_paths.h"
 
@@ -124,7 +123,7 @@ namespace hotkey {
 agi::hotkey::Hotkey *inst = 0;
 void init() {
 	inst = new agi::hotkey::Hotkey(
-		from_wx(StandardPaths::DecodePath("?user/hotkey.json")),
+		StandardPaths::DecodePath("?user/hotkey.json"),
 		GET_DEFAULT_CONFIG(default_hotkey));
 
 	int last_version = OPT_GET("Version/Last Version")->GetInt();
diff --git a/aegisub/src/hotkey_data_view_model.cpp b/aegisub/src/hotkey_data_view_model.cpp
index f72e3b3424c52ee07a2f60c4c1672a2c3a745616..b0ae35c45aa8d0ca3c8addbb25e01b2d2be18c65 100644
--- a/aegisub/src/hotkey_data_view_model.cpp
+++ b/aegisub/src/hotkey_data_view_model.cpp
@@ -90,13 +90,13 @@ public:
 
 	void GetValue(wxVariant &variant, unsigned int col) const {
 		if (col == 0)
-			variant = combo.Str();
+			variant = to_wx(combo.Str());
 		else if (col == 1) {
 			wxBitmap icon_bmp(icon::get(combo.CmdName(), 16));
 			wxIcon icon;
 			if (icon_bmp.IsOk())
 				icon.CopyFromBitmap(icon_bmp);
-			variant << wxDataViewIconText(combo.CmdName(), icon);
+			variant << wxDataViewIconText(to_wx(combo.CmdName()), icon);
 		}
 		else if (col == 2) {
 			try {
@@ -117,7 +117,7 @@ public:
 			keys.reserve(toks.size());
 			transform(toks.begin(), toks.end(), back_inserter(keys), (std::string(*)(wxString const&))&from_wx);
 			combo = Combo(combo.Context(), combo.CmdName(), keys);
-			cmd_str = combo.Str();
+			cmd_str = to_wx(combo.Str());
 			return true;
 		}
 		else if (col == 1) {
@@ -226,7 +226,7 @@ public:
 			if (cat_it != cat_map.end())
 				cat = cat_it->second;
 			else {
-				categories.emplace_back(model, cat_name);
+				categories.emplace_back(model, to_wx(cat_name));
 				cat = cat_map[cat_name] = &categories.back();
 			}
 
diff --git a/aegisub/src/include/aegisub/audio_provider.h b/aegisub/src/include/aegisub/audio_provider.h
index a91ce4b7378fe37d2ee66a3148bb1eff5be3bbe1..855e2c30112d9429f9402ff6e9fc4aff2f0a8719 100644
--- a/aegisub/src/include/aegisub/audio_provider.h
+++ b/aegisub/src/include/aegisub/audio_provider.h
@@ -34,10 +34,12 @@
 
 #pragma once
 
-#include <wx/string.h>
+#include "factory_manager.h"
 
 #include <libaegisub/exception.h>
-#include "factory_manager.h"
+#include <libaegisub/fs_fwd.h>
+
+#include <boost/filesystem/path.hpp>
 
 class AudioProvider {
 protected:
@@ -48,7 +50,7 @@ protected:
 	int sample_rate;
 	int bytes_per_sample;
 	bool float_samples;
-	wxString filename;
+	agi::fs::path filename;
 
 	virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
 
@@ -60,27 +62,25 @@ public:
 	void GetAudio(void *buf, int64_t start, int64_t count) const;
 	void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
 
-	wxString GetFilename() const  { return filename; };
-	int64_t GetNumSamples() const { return num_samples; }
-	int GetSampleRate() const     { return sample_rate; }
-	int GetBytesPerSample() const { return bytes_per_sample; }
-	int GetChannels() const       { return channels; }
-	bool AreSamplesFloat() const  { return float_samples; }
-	virtual bool AreSamplesNativeEndian() const = 0;
-
-	virtual int64_t GetSamplesLoaded() const { return num_samples; }
+	agi::fs::path GetFilename()       const { return filename; }
+	int64_t       GetNumSamples()     const { return num_samples; }
+	int           GetSampleRate()     const { return sample_rate; }
+	int           GetBytesPerSample() const { return bytes_per_sample; }
+	int           GetChannels()       const { return channels; }
+	bool          AreSamplesFloat()   const { return float_samples; }
 
 	/// @brief Does this provider benefit from external caching?
 	virtual bool NeedsCache() const { return false; }
+	virtual bool AreSamplesNativeEndian() const = 0;
 };
 
-class AudioProviderFactory : public Factory1<AudioProvider, wxString> {
+class AudioProviderFactory : public Factory1<AudioProvider, agi::fs::path> {
 public:
 	static void RegisterProviders();
 
 	/// Get a provider for the file
 	/// @param filename URI to open
-	static AudioProvider *GetProvider(wxString const& filename);
+	static AudioProvider *GetProvider(agi::fs::path const& filename);
 };
 
 DEFINE_BASE_EXCEPTION_NOINNER(AudioProviderError, agi::Exception)
diff --git a/aegisub/src/include/aegisub/video_provider.h b/aegisub/src/include/aegisub/video_provider.h
index aabfbc8aa25cefe8bbabd50280d96f0d43b4cf5e..e6f2a2cdb391f4b241ec04fc2b7efa227da47025 100644
--- a/aegisub/src/include/aegisub/video_provider.h
+++ b/aegisub/src/include/aegisub/video_provider.h
@@ -37,7 +37,7 @@
 #include <libaegisub/exception.h>
 #include <libaegisub/vfr.h>
 
-#include <wx/string.h>
+#include <string>
 
 class AegiVideoFrame;
 
@@ -59,13 +59,13 @@ public:
 	/// Get the source colorspace of the video before it was converted to RGB
 	/// @return A string describing the source colorspace or "None" if it is
 	///         unknown or meaningless
-	virtual wxString GetColorSpace() const = 0;
+	virtual std::string GetColorSpace() const = 0;
 
 	/// @brief Use this to set any post-loading warnings, such as "being loaded with unreliable seeking"
-	virtual wxString GetWarning() const { return ""; }
+	virtual std::string GetWarning() const { return ""; }
 
 	/// @brief Name of decoder, e.g. "Avisynth/FFMpegSource"
-	virtual wxString GetDecoderName() const = 0;
+	virtual std::string GetDecoderName() const = 0;
 
 	/// @brief Does this provider want Aegisub to cache video frames?
 	/// @return Returns true if caching is desired, false otherwise.
diff --git a/aegisub/src/initial_line_state.h b/aegisub/src/initial_line_state.h
index 1230bab0a8f95668045e7c0d43c35337e9db1588..a92724b34772848bdc39331fafd829cd18c7d591 100644
--- a/aegisub/src/initial_line_state.h
+++ b/aegisub/src/initial_line_state.h
@@ -12,26 +12,24 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef AGI_PRE
-#include <wx/string.h>
-#endif
-
 #include <libaegisub/signal.h>
 
+#include <string>
+
 namespace agi { struct Context; }
 class AssDialogue;
 
 class InitialLineState {
 	agi::signal::Connection active_line_connection;
-	wxString initial_text;
+	std::string initial_text;
 	int line_id;
 
-	agi::signal::Signal<wxString const&> InitialStateChanged;
+	agi::signal::Signal<std::string const&> InitialStateChanged;
 	void OnActiveLineChanged(AssDialogue *new_line);
 
 public:
 	InitialLineState(agi::Context *c);
 
-	wxString GetInitialText() const { return initial_text; }
+	std::string const& GetInitialText() const { return initial_text; }
 	DEFINE_SIGNAL_ADDERS(InitialStateChanged, AddChangeListener);
 };
diff --git a/aegisub/src/main.cpp b/aegisub/src/main.cpp
index e45983b83dd8017811e1d220b757c8b667a22481..38a42d2e69c50e2affcf2f9bb0e57c63156f5171 100644
--- a/aegisub/src/main.cpp
+++ b/aegisub/src/main.cpp
@@ -34,24 +34,18 @@
 
 #include "config.h"
 
-#include "main.h"
-
-#include "include/aegisub/menu.h"
 #include "command/command.h"
 #include "command/icon.h"
-#include "include/aegisub/toolbar.h"
 #include "include/aegisub/hotkey.h"
 
 #include "ass_dialogue.h"
-#include "ass_export_filter.h"
 #include "ass_file.h"
-#include "audio_box.h"
 #include "auto4_base.h"
-#include "charset_conv.h"
 #include "compat.h"
 #include "export_fixstyle.h"
 #include "export_framerate.h"
 #include "frame_main.h"
+#include "main.h"
 #include "libresrc/libresrc.h"
 #include "options.h"
 #include "plugin_manager.h"
@@ -61,25 +55,27 @@
 #include "video_context.h"
 #include "utils.h"
 
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/hotkey.h>
 #include <libaegisub/io.h>
 #include <libaegisub/log.h>
-#include <libaegisub/hotkey.h>
-#include <libaegisub/scoped_ptr.h>
+#include <libaegisub/path.h>
+#include <libaegisub/util.h>
 
+#include <boost/filesystem/fstream.hpp>
+#include <boost/format.hpp>
 #include <sstream>
 
-#include <wx/clipbrd.h>
 #include <wx/config.h>
-#include <wx/datetime.h>
-#include <wx/filefn.h>
-#include <wx/filename.h>
 #include <wx/msgdlg.h>
-#include <wx/stdpaths.h>
+#include <wx/stackwalk.h>
 #include <wx/utils.h>
 
 namespace config {
-	agi::Options *opt = 0;
-	agi::MRUManager *mru = 0;
+	agi::Options *opt = nullptr;
+	agi::MRUManager *mru = nullptr;
+	agi::Path *path = nullptr;
 }
 
 IMPLEMENT_APP(AegisubApp)
@@ -133,6 +129,8 @@ AegisubApp::AegisubApp() {
 	wxSetEnv("UBUNTU_MENUPROXY", "0");
 }
 
+wxDEFINE_EVENT(EVT_CALL_THUNK, wxThreadEvent);
+
 /// @brief Gets called when application starts.
 /// @return bool
 bool AegisubApp::OnInit() {
@@ -143,13 +141,20 @@ bool AegisubApp::OnInit() {
 	SetAppName("aegisub");
 #endif
 
-	Bind(EVT_CALL_THUNK, [](wxThreadEvent &evt) {
+	// Pointless `this` capture required due to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51494
+	agi::dispatch::Init([this](agi::dispatch::Thunk f) {
+		wxThreadEvent *evt = new wxThreadEvent(EVT_CALL_THUNK);
+		evt->SetPayload(f);
+		wxTheApp->QueueEvent(evt);
+	});
+
+	wxTheApp->Bind(EVT_CALL_THUNK, [](wxThreadEvent &evt) {
 		evt.GetPayload<std::function<void()>>()();
 	});
 
-	// logging.
-	agi::log::log = new agi::log::LogSink;
+	config::path = new agi::Path;
 
+	agi::log::log = new agi::log::LogSink;
 #ifdef _DEBUG
 	agi::log::log->Subscribe(new agi::log::EmitSTDOUT());
 #endif
@@ -159,29 +164,29 @@ bool AegisubApp::OnInit() {
 #ifdef __WXMSW__
 	// Try loading configuration from the install dir if one exists there
 	try {
-		std::string conf_local(from_wx(StandardPaths::DecodePath("?data/config.json")));
-		agi::scoped_ptr<std::istream> localConfig(agi::io::Open(conf_local));
+		auto conf_local(StandardPaths::DecodePath("?data/config.json"));
+		std::unique_ptr<std::istream> localConfig(agi::io::Open(conf_local));
 		config::opt = new agi::Options(conf_local, GET_DEFAULT_CONFIG(default_config));
 
 		// Local config, make ?user mean ?data so all user settings are placed in install dir
 		StandardPaths::SetPathValue("?user", StandardPaths::DecodePath("?data"));
 		StandardPaths::SetPathValue("?local", StandardPaths::DecodePath("?data"));
-	} catch (agi::FileNotAccessibleError const&) {
+	} catch (agi::fs::FileSystemError const&) {
 		// File doesn't exist or we can't read it
 		// Might be worth displaying an error in the second case
 	}
 #endif
 
 	StartupLog("Create log writer");
-	wxString path_log = StandardPaths::DecodePath("?user/log/");
-	wxFileName::Mkdir(path_log, 0777, wxPATH_MKDIR_FULL);
-	agi::log::log->Subscribe(new agi::log::JsonEmitter(from_wx(path_log), agi::log::log));
+	auto path_log = StandardPaths::DecodePath("?user/log/");
+	agi::fs::CreateDirectory(path_log);
+	agi::log::log->Subscribe(new agi::log::JsonEmitter(path_log, agi::log::log));
 	CleanCache(path_log, "*.json", 10, 100);
 
 	StartupLog("Load user configuration");
 	try {
 		if (!config::opt)
-			config::opt = new agi::Options(from_wx(StandardPaths::DecodePath("?user/config.json")), GET_DEFAULT_CONFIG(default_config));
+			config::opt = new agi::Options(StandardPaths::DecodePath("?user/config.json"), GET_DEFAULT_CONFIG(default_config));
 		std::istringstream stream(GET_DEFAULT_CONFIG(default_config_platform));
 		config::opt->ConfigNext(stream);
 	} catch (agi::Exception& e) {
@@ -205,7 +210,7 @@ bool AegisubApp::OnInit() {
 	icon::icon_init();
 
 	StartupLog("Load MRU");
-	config::mru = new agi::MRUManager(from_wx(StandardPaths::DecodePath("?user/mru.json")), GET_DEFAULT_CONFIG(default_mru), config::opt);
+	config::mru = new agi::MRUManager(StandardPaths::DecodePath("?user/mru.json"), GET_DEFAULT_CONFIG(default_mru), config::opt);
 
 	SetThreadName(-1, "AegiMain");
 
@@ -246,7 +251,7 @@ bool AegisubApp::OnInit() {
 
 		// Load Automation scripts
 		StartupLog("Load global Automation scripts");
-		global_scripts = new Automation4::AutoloadScriptManager(to_wx(OPT_GET("Path/Automation/Autoload")->GetString()));
+		global_scripts = new Automation4::AutoloadScriptManager(OPT_GET("Path/Automation/Autoload")->GetString());
 
 		// Load export filters
 		StartupLog("Register export filters");
@@ -286,8 +291,7 @@ bool AegisubApp::OnInit() {
 #endif
 
 	StartupLog("Clean old autosave files");
-	wxString autosave_path = StandardPaths::DecodePath(to_wx(OPT_GET("Path/Auto/Save")->GetString()));
-	CleanCache(autosave_path, "*.AUTOSAVE.ass", 100, 1000);
+	CleanCache(StandardPaths::DecodePath(OPT_GET("Path/Auto/Save")->GetString()), "*.AUTOSAVE.ass", 100, 1000);
 
 	StartupLog("Initialization complete");
 	return true;
@@ -298,7 +302,6 @@ int AegisubApp::OnExit() {
 		delete frame;
 
 	SubtitleFormat::DestroyFormats();
-	VideoContext::OnExit();
 	delete plugins;
 	delete config::opt;
 	delete config::mru;
@@ -317,50 +320,44 @@ int AegisubApp::OnExit() {
 
 #if wxUSE_STACKWALKER == 1
 class StackWalker: public wxStackWalker {
-	wxFile *crash_text;	// FP to the crash text file.
+	boost::filesystem::ofstream fp;
 
 public:
-	StackWalker(wxString cause);
+	StackWalker(std::string const& cause);
 	~StackWalker();
-	void OnStackFrame(const wxStackFrame& frame);
+	void OnStackFrame(wxStackFrame const& frame);
 };
 
 /// @brief Called at the start of walking the stack.
 /// @param cause cause of the crash.
-///
-StackWalker::StackWalker(wxString cause) {
-	crash_text = new wxFile(StandardPaths::DecodePath("?user/crashlog.txt"), wxFile::write_append);
-
-	if (crash_text->IsOpened()) {
-		wxDateTime time = wxDateTime::Now();
-
-		crash_text->Write(wxString::Format("--- %s ------------------\n", time.FormatISOCombined()));
-		crash_text->Write(wxString::Format("VER - %s\n", GetAegisubLongVersionString()));
-		crash_text->Write(wxString::Format("FTL - Beginning stack dump for \"%s\":\n", cause));
-	}
+StackWalker::StackWalker(std::string const& cause)
+: fp(StandardPaths::DecodePath("?user/crashlog.txt"), std::ios::app)
+{
+	if (!fp.good()) return;
+
+	fp << agi::util::strftime("--- %y-%m-%d %H:%M:%S ------------------\n");
+	fp << boost::format("VER - %s\n") % GetAegisubLongVersionString();
+	fp << boost::format("FTL - Beginning stack dump for \"%s\": \n") % cause;
 }
 
 /// @brief Callback to format a single frame
 /// @param frame frame to parse.
-///
-void StackWalker::OnStackFrame(const wxStackFrame &frame) {
-	if (crash_text->IsOpened()) {
-		wxString dst = wxString::Format("%03u - %p: ", (unsigned)frame.GetLevel(),frame.GetAddress()) + frame.GetName();
-		if (frame.HasSourceLocation())
-			dst = wxString::Format("%s on %s:%u", dst, frame.GetFileName(), (unsigned)frame.GetLine());
+void StackWalker::OnStackFrame(wxStackFrame const& frame) {
+	if (!fp.good()) return;
 
-		crash_text->Write(wxString::Format("%s\n", dst));
-	}
+	fp << boost::format("%03u - %p: %s") % frame.GetLevel() % frame.GetAddress();
+	if (frame.HasSourceLocation())
+		fp << boost::format(" on %s:%u") % frame.GetFileName(), frame.GetLine();
+
+	fp << "\n";
 }
 
 /// @brief Called at the end of walking the stack.
 StackWalker::~StackWalker() {
-	if (crash_text->IsOpened()) {
-		crash_text->Write("End of stack dump.\n");
-		crash_text->Write("----------------------------------------\n\n");
+	if (!fp.good()) return;
 
-		crash_text->Close();
-	}
+	fp << "End of stack dump.\n";
+	fp << "----------------------------------------\n\n";
 }
 #endif
 
@@ -370,17 +367,12 @@ const static wxString exception_message = _("Oops, Aegisub has crashed!\n\nAn at
 static void UnhandledExeception(bool stackWalk) {
 #if (!defined(_DEBUG) || defined(WITH_EXCEPTIONS)) && (wxUSE_ON_FATAL_EXCEPTION+0)
 	if (AssFile::top) {
-		// Current filename if any.
-		wxFileName file(AssFile::top->filename);
-		if (!file.HasName()) file.SetName("untitled");
+		auto path = StandardPaths::DecodePath("?user/recovered");
+		agi::fs::CreateDirectory(path);
 
-		// Set path and create if it doesn't exist.
-		file.SetPath(StandardPaths::DecodePath("?user/recovered"));
-		if (!file.DirExists()) file.Mkdir();
-
-		// Save file
-		wxString filename = wxString::Format("%s/%s.%s.ass", file.GetPath(), file.GetName(), wxDateTime::Now().Format("%Y-%m-%d-%H-%M-%S"));
-		AssFile::top->Save(filename,false,false);
+		auto filename = AssFile::top->filename.empty() ? "untitled" : AssFile::top->filename.stem().string();
+		path /= str(boost::format("%s.%s.ass") % filename % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
+		AssFile::top->Save(path, false, false);
 
 #if wxUSE_STACKWALKER == 1
 		if (stackWalk) {
@@ -390,7 +382,7 @@ static void UnhandledExeception(bool stackWalk) {
 #endif
 
 		// Inform user of crash.
-		wxMessageBox(wxString::Format(exception_message, filename), _("Program error"), wxOK | wxICON_ERROR | wxCENTER, nullptr);
+		wxMessageBox(wxString::Format(exception_message, path.wstring()), _("Program error"), wxOK | wxICON_ERROR | wxCENTER, nullptr);
 	}
 	else if (LastStartupState) {
 #if wxUSE_STACKWALKER == 1
@@ -404,12 +396,10 @@ static void UnhandledExeception(bool stackWalk) {
 #endif
 }
 
-/// @brief Called during an unhandled exception
 void AegisubApp::OnUnhandledException() {
 	UnhandledExeception(false);
 }
 
-/// @brief Called during a fatal exception.
 void AegisubApp::OnFatalException() {
 	UnhandledExeception(true);
 }
@@ -423,10 +413,10 @@ void AegisubApp::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEven
 		SHOW_EXCEPTION(to_wx(e.GetChainedMessage()));
 	}
 	catch (const std::exception &e) {
-		SHOW_EXCEPTION(wxString(e.what(), wxConvUTF8));
+		SHOW_EXCEPTION(to_wx(e.what()));
 	}
 	catch (const char *e) {
-		SHOW_EXCEPTION(wxString(e, wxConvUTF8));
+		SHOW_EXCEPTION(to_wx(e));
 	}
 	catch (const wxString &e) {
 		SHOW_EXCEPTION(e);
@@ -435,33 +425,26 @@ void AegisubApp::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEven
 }
 
 int AegisubApp::OnRun() {
-	wxString error;
+	std::string error;
 
-	// Run program
 	try {
 		if (m_exitOnFrameDelete == Later) m_exitOnFrameDelete = Yes;
 		return MainLoop();
 	}
-
-	// Catch errors
-	catch (const wxString &err) { error = err; }
+	catch (const wxString &err) { error = from_wx(err); }
 	catch (const char *err) { error = err; }
-	catch (const std::exception &e) { error = wxString("std::exception: ") + wxString(e.what(),wxConvUTF8); }
-	catch (const agi::Exception &e) { error = "agi::exception: " + to_wx(e.GetChainedMessage()); }
+	catch (const std::exception &e) { error = std::string("std::exception: ") + e.what(); }
+	catch (const agi::Exception &e) { error = "agi::exception: " + e.GetChainedMessage(); }
 	catch (...) { error = "Program terminated in error."; }
 
 	// Report errors
-	if (!error.IsEmpty()) {
-		std::ofstream file;
-		file.open(wxString(StandardPaths::DecodePath("?user/crashlog.txt")).mb_str(),std::ios::out | std::ios::app);
+	if (!error.empty()) {
+		boost::filesystem::ofstream file(StandardPaths::DecodePath("?user/crashlog.txt"), std::ios::app);
 		if (file.is_open()) {
-			wxDateTime time = wxDateTime::Now();
-			wxString timeStr = "---" + time.FormatISODate() + " " + time.FormatISOTime() + "------------------";
-			file << std::endl << timeStr.mb_str(csConvLocal);
-			file << "\nVER - " << GetAegisubLongVersionString();
-			file << "\nEXC - Aegisub has crashed with unhandled exception \"" << error.mb_str(csConvLocal) <<"\".\n";
-			file << wxString('-', timeStr.size());
-			file << "\n";
+			file << agi::util::strftime("--- %y-%m-%d %H:%M:%S ------------------\n");
+			file << boost::format("VER - %s\n") % GetAegisubLongVersionString();
+			file << boost::format("EXC - Aegisub has crashed with unhandled exception \"%s\".\n") % error;
+			file << "----------------------------------------\n\n";
 			file.close();
 		}
 
@@ -474,7 +457,7 @@ int AegisubApp::OnRun() {
 
 void AegisubApp::MacOpenFile(const wxString &filename) {
 	if (frame != nullptr && !filename.empty()) {
-		frame->LoadSubtitles(filename);
+		frame->LoadSubtitles(from_wx(filename));
 		wxFileName filepath(filename);
 		OPT_SET("Path/Last/Subtitles")->SetString(from_wx(filepath.GetPath()));
 	}
diff --git a/aegisub/src/main.h b/aegisub/src/main.h
index 20c137804b689ba5e4a238cb740240ce59014057..03661e549ec053a204bd480b51fd1f5b1a43fd14 100644
--- a/aegisub/src/main.h
+++ b/aegisub/src/main.h
@@ -33,8 +33,6 @@
 ///
 
 #include <wx/app.h>
-#include <wx/file.h>
-#include <wx/stackwalk.h>
 
 #include "aegisublocale.h"
 
diff --git a/aegisub/src/menu.cpp b/aegisub/src/menu.cpp
index f968b13b9c8aca81d867f519d8e1666090b7629a..407db65a33c431ba65be1374772a2dc823942b3e 100644
--- a/aegisub/src/menu.cpp
+++ b/aegisub/src/menu.cpp
@@ -37,11 +37,9 @@
 
 #include <algorithm>
 #include <deque>
-#include <sstream>
 #include <vector>
 
 #include <wx/app.h>
-#include <wx/filename.h>
 #include <wx/frame.h>
 #include <wx/menu.h>
 #include <wx/menuitem.h>
@@ -98,9 +96,12 @@ public:
 
 		int i = 0;
 		for (auto it = mru->begin(); it != mru->end(); ++it, ++i) {
+			wxString name = it->wstring();
+			if (!name.StartsWith("?"))
+				name = it->filename().wstring();
 			items[i]->SetItemLabel(wxString::Format("%s%d %s",
 				i <= 9 ? "&" : "", i + 1,
-				wxFileName(to_wx(*it)).GetFullName()));
+				name));
 			items[i]->Enable(true);
 		}
 	}
@@ -165,7 +166,7 @@ class CommandManager {
 			text = c->StrMenu(context);
 		else
 			text = item.second->GetItemLabel().BeforeFirst('\t');
-		item.second->SetItemLabel(text + "\t" + hotkey::get_hotkey_str_first("Default", c->name()));
+		item.second->SetItemLabel(text + to_wx("\t" + hotkey::get_hotkey_str_first("Default", c->name())));
 	}
 
 public:
@@ -184,7 +185,7 @@ public:
 			wxITEM_NORMAL;
 
 		wxString menu_text = text.empty() ? co->StrMenu(context) : _(to_wx(text));
-		menu_text += "\t" + hotkey::get_hotkey_str_first("Default", co->name());
+		menu_text += to_wx("\t" + hotkey::get_hotkey_str_first("Default", co->name()));
 
 		wxMenuItem *item = new wxMenuItem(parent, MENU_ID_BASE + items.size(), menu_text, co->StrHelp(), kind);
 #ifndef __WXMAC__
@@ -292,7 +293,7 @@ menu_map const& get_menus_root() {
 	if (!root.empty()) return root;
 
 	try {
-		root = agi::json_util::file(StandardPaths::DecodePath("?user/menu.json").utf8_str().data(), GET_DEFAULT_CONFIG(default_menu));
+		root = agi::json_util::file(StandardPaths::DecodePath("?user/menu.json"), GET_DEFAULT_CONFIG(default_menu));
 		return root;
 	}
 	catch (json::Reader::ParseException const& e) {
diff --git a/aegisub/src/mkv_wrap.cpp b/aegisub/src/mkv_wrap.cpp
index 978284d1291df1e738025254ddb8b41e56eb6b94..93d39e35a9903ffad38144e9866913b947915b55 100644
--- a/aegisub/src/mkv_wrap.cpp
+++ b/aegisub/src/mkv_wrap.cpp
@@ -34,16 +34,6 @@
 
 #include "config.h"
 
-#include <algorithm>
-#include <cerrno>
-#include <cstdint>
-#include <cstdio>
-#include <iterator>
-
-#include <wx/filename.h>
-#include <wx/tokenzr.h>
-#include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
-
 #include "mkv_wrap.h"
 
 #include "ass_file.h"
@@ -53,9 +43,26 @@
 #include "dialog_progress.h"
 #include "MatroskaParser.h"
 
+#include <libaegisub/fs.h>
+
+#include <algorithm>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/range/irange.hpp>
+#include <boost/tokenizer.hpp>
+#include <cerrno>
+#include <cstdint>
+#include <cstdio>
+#include <iterator>
+
+#include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
+
 class MkvStdIO : public InputStream {
 public:
-	MkvStdIO(wxString filename);
+	MkvStdIO(agi::fs::path const& filename);
 	~MkvStdIO() { if (fp) fclose(fp); }
 
 	FILE *fp;
@@ -73,32 +80,20 @@ public:
 #endif
 
 static void read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *input, bool srt, double totalTime, AssParser *parser) {
-	std::map<int, wxString> subList;
-	char *readBuf = 0;
-	size_t readBufSize = 0;
+	std::map<int, std::string> subList;
+	std::string readBuf;
 
 	// Load blocks
 	ulonglong startTime, endTime, filePos;
 	unsigned int rt, frameSize, frameFlags;
 
-	while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) {
-		if (ps->IsCancelled()) {
-			delete[] readBuf;
-			return;
-		}
-
-		// Read to temp
-		if (frameSize > readBufSize) {
-			delete[] readBuf;
-			readBufSize = frameSize * 2;
-			readBuf = new char[readBufSize];
-		}
-		else if (frameSize == 0)
-			continue;
+	while (mkv_ReadFrame(file, 0, &rt, &startTime, &endTime, &filePos, &frameSize, &frameFlags) == 0) {
+		if (ps->IsCancelled()) return;
+		if (frameSize == 0) continue;
 
+		readBuf.resize(frameSize);
 		std_fseek(input->fp, filePos, SEEK_SET);
-		fread(readBuf, 1, frameSize, input->fp);
-		wxString blockString(readBuf, wxConvUTF8, frameSize);
+		fread(&readBuf[0], 1, frameSize, input->fp);
 
 		// Get start and end times
 		longlong timecodeScaleLow = 1000000;
@@ -107,35 +102,35 @@ static void read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *
 
 		// Process SSA/ASS
 		if (!srt) {
-			long order = 0, layer = 0;
-			wxString afterOrder, afterLayer;
-			blockString.BeforeFirst(',', &afterOrder).ToLong(&order);
-			afterOrder.BeforeFirst(',', &afterLayer).ToLong(&layer);
-
-			subList[order] = wxString::Format("Dialogue: %d,%s,%s,%s", (int)layer, subStart.GetAssFormated(), subEnd.GetAssFormated(), afterLayer);
+			std::vector<boost::iterator_range<std::string::iterator>> chunks;
+			boost::split(chunks, readBuf, boost::is_any_of(","));
+
+			subList[boost::lexical_cast<int>(chunks[0])] =
+				str(boost::format("Dialogue: %d,%s,%s,%s")
+					% boost::lexical_cast<int>(chunks[1])
+					% subStart.GetAssFormated()
+					% subEnd.GetAssFormated()
+					% boost::make_iterator_range(begin(chunks[2]), readBuf.end()));
 		}
 		// Process SRT
 		else {
-			blockString = wxString::Format("Dialogue: 0,%s,%s,Default,,0,0,0,,%s", subStart.GetAssFormated(), subEnd.GetAssFormated(), blockString);
-			blockString.Replace("\r\n","\\N");
-			blockString.Replace("\r","\\N");
-			blockString.Replace("\n","\\N");
+			readBuf = str(boost::format("Dialogue: 0,%s,%s,Default,,0,0,0,,%s") % subStart.GetAssFormated() % subEnd.GetAssFormated() % readBuf);
+			boost::replace_all(readBuf, "\r\n", "\\N");
+			boost::replace_all(readBuf, "\r", "\\N");
+			boost::replace_all(readBuf, "\n", "\\N");
 
-			subList[subList.size()] = blockString;
+			subList[subList.size()] = readBuf;
 		}
 
 		ps->SetProgress(startTime / timecodeScaleLow, totalTime);
 	}
 
-	delete[] readBuf;
-
 	// Insert into file
-	for (auto order_value_pair : subList) {
+	for (auto order_value_pair : subList)
 		parser->AddLine(order_value_pair.second);
-	}
 }
 
-void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
+void MatroskaWrapper::GetSubtitles(agi::fs::path const& filename, AssFile *target) {
 	MkvStdIO input(filename);
 	char err[2048];
 	MatroskaFile *file = mkv_Open(&input, err, sizeof(err));
@@ -144,26 +139,20 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
 	try {
 		// Get info
 		unsigned tracks = mkv_GetNumTracks(file);
-		TrackInfo *trackInfo;
 		std::vector<unsigned> tracksFound;
-		wxArrayString tracksNames;
+		std::vector<std::string> tracksNames;
 		unsigned trackToRead;
 
 		// Find tracks
 		for (unsigned track = 0; track < tracks; track++) {
-			trackInfo = mkv_GetTrackInfo(file,track);
-
-			// Subtitle track
-			if (trackInfo->Type == 0x11) {
-				wxString CodecID = wxString::FromUTF8(trackInfo->CodecID);
-				wxString TrackName = wxString::FromUTF8(trackInfo->Name);
-				wxString TrackLanguage = wxString::FromUTF8(trackInfo->Language);
+			TrackInfo *trackInfo = mkv_GetTrackInfo(file, track);
+			if (trackInfo->Type != 0x11) continue;
 
-				// Known subtitle format
-				if (CodecID == "S_TEXT/SSA" || CodecID == "S_TEXT/ASS" || CodecID == "S_TEXT/UTF8") {
-					tracksFound.push_back(track);
-					tracksNames.Add(wxString::Format("%d (%s %s): %s", track, CodecID, TrackLanguage, TrackName));
-				}
+			// Known subtitle format
+			std::string CodecID(trackInfo->CodecID);
+			if (CodecID == "S_TEXT/SSA" || CodecID == "S_TEXT/ASS" || CodecID == "S_TEXT/UTF8") {
+				tracksFound.push_back(track);
+				tracksNames.emplace_back(str(boost::format("%d (%s %s): %s") % track % CodecID % trackInfo->Language % trackInfo->Name));
 			}
 		}
 
@@ -172,12 +161,11 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
 			throw MatroskaException("File has no recognised subtitle tracks.");
 
 		// Only one track found
-		if (tracksFound.size() == 1) {
+		if (tracksFound.size() == 1)
 			trackToRead = tracksFound[0];
-		}
 		// Pick a track
 		else {
-			int choice = wxGetSingleChoiceIndex(_("Choose which track to read:"), _("Multiple subtitle tracks found"), tracksNames);
+			int choice = wxGetSingleChoiceIndex(_("Choose which track to read:"), _("Multiple subtitle tracks found"), to_wx(tracksNames));
 			if (choice == -1)
 				throw agi::UserCancelException("canceled");
 
@@ -186,8 +174,8 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
 
 		// Picked track
 		mkv_SetTrackMask(file, ~(1 << trackToRead));
-		trackInfo = mkv_GetTrackInfo(file,trackToRead);
-		wxString CodecID = wxString::FromUTF8(trackInfo->CodecID);
+		TrackInfo *trackInfo = mkv_GetTrackInfo(file, trackToRead);
+		std::string CodecID(trackInfo->CodecID);
 		bool srt = CodecID == "S_TEXT/UTF8";
 		bool ssa = CodecID == "S_TEXT/SSA";
 
@@ -196,13 +184,12 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
 		// Read private data if it's ASS/SSA
 		if (!srt) {
 			// Read raw data
-			trackInfo = mkv_GetTrackInfo(file,trackToRead);
-			wxString privString((const char *)trackInfo->CodecPrivate, wxConvUTF8, trackInfo->CodecPrivateSize);
+			std::string priv((const char *)trackInfo->CodecPrivate, trackInfo->CodecPrivateSize);
 
 			// Load into file
-			wxStringTokenizer token(privString, "\r\n", wxTOKEN_STRTOK);
-			while (token.HasMoreTokens())
-				parser.AddLine(token.GetNextToken());
+			boost::char_separator<char> sep("\r\n");
+			for (auto const& cur : boost::tokenizer<boost::char_separator<char>>(priv, sep))
+				parser.AddLine(cur);
 		}
 		// Load default if it's SRT
 		else {
@@ -225,20 +212,20 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
 	}
 }
 
-bool MatroskaWrapper::HasSubtitles(wxString const& filename) {
+bool MatroskaWrapper::HasSubtitles(agi::fs::path const& filename) {
 	char err[2048];
 	try {
 		MkvStdIO input(filename);
-		MatroskaFile* file = mkv_Open(&input, err, sizeof(err));
+		auto file = mkv_Open(&input, err, sizeof(err));
 		if (!file) return false;
 
 		// Find tracks
-		int tracks = mkv_GetNumTracks(file);
-		for (int track = 0; track < tracks; track++) {
-			TrackInfo *trackInfo = mkv_GetTrackInfo(file, track);
+		auto tracks = mkv_GetNumTracks(file);
+		for (auto track : boost::irange(0u, tracks)) {
+			auto trackInfo = mkv_GetTrackInfo(file, track);
 
 			if (trackInfo->Type == 0x11) {
-				wxString CodecID = wxString::FromUTF8(trackInfo->CodecID);
+				std::string CodecID(trackInfo->CodecID);
 				if (CodecID == "S_TEXT/SSA" || CodecID == "S_TEXT/ASS" || CodecID == "S_TEXT/UTF8") {
 					mkv_Close(file);
 					return true;
@@ -256,102 +243,70 @@ bool MatroskaWrapper::HasSubtitles(wxString const& filename) {
 }
 
 int StdIoRead(InputStream *_st, ulonglong pos, void *buffer, int count) {
-  MkvStdIO *st = (MkvStdIO *) _st;
-  size_t  rd;
-  if (std_fseek(st->fp, pos, SEEK_SET)) {
-    st->error = errno;
-    return -1;
-  }
-  rd = fread(buffer, 1, count, st->fp);
-  if (rd == 0) {
-    if (feof(st->fp))
-      return 0;
-    st->error = errno;
-    return -1;
-  }
-  return rd;
+	auto *st = static_cast<MkvStdIO*>(_st);
+	if (std_fseek(st->fp, pos, SEEK_SET)) {
+		st->error = errno;
+		return -1;
+	}
+
+	auto rd = fread(buffer, 1, count, st->fp);
+	if (rd == 0) {
+		if (feof(st->fp))
+			return 0;
+		st->error = errno;
+		return -1;
+	}
+	return rd;
 }
 
 /// @brief scan for a signature sig(big-endian) starting at file position pos
 /// @return position of the first byte of signature or -1 if error/not found
-longlong StdIoScan(InputStream *_st, ulonglong start, unsigned signature) {
-  MkvStdIO *st = (MkvStdIO *) _st;
-  int	      c;
-  unsigned    cmp = 0;
-  FILE	      *fp = st->fp;
-
-  if (std_fseek(fp, start, SEEK_SET))
-    return -1;
-
-  while ((c = getc(fp)) != EOF) {
-    cmp = ((cmp << 8) | c) & 0xffffffff;
-    if (cmp == signature)
-      return std_ftell(fp) - 4;
-  }
-
-  return -1;
-}
-
-/// @brief This is used to limit readahead.
-unsigned StdIoGetCacheSize(InputStream *st) {
-  return CACHESIZE;
-}
-
-/// @brief Get last error message
-const char *StdIoGetLastError(InputStream *st) {
-  return strerror(((MkvStdIO *)st)->error);
-}
-
-/// @brief Memory allocation, this is done via stdlib
-void *StdIoMalloc(InputStream *, size_t size) {
-  return malloc(size);
-}
-
-void *StdIoRealloc(InputStream *, void *mem, size_t size) {
-  return realloc(mem, size);
-}
-
-void StdIoFree(InputStream *, void *mem) {
-  free(mem);
-}
+longlong StdIoScan(InputStream *st, ulonglong start, unsigned signature) {
+	FILE *fp = static_cast<MkvStdIO*>(st)->fp;
+
+	if (std_fseek(fp, start, SEEK_SET))
+		return -1;
+
+	int c;
+	unsigned cmp = 0;
+	while ((c = getc(fp)) != EOF) {
+		cmp = ((cmp << 8) | c) & 0xffffffff;
+		if (cmp == signature)
+			return std_ftell(fp) - 4;
+	}
 
-int StdIoProgress(InputStream *, ulonglong cur, ulonglong max) {
-  return 1;
+	return -1;
 }
 
-longlong StdIoGetFileSize(InputStream *_st) {
-	MkvStdIO *st = (MkvStdIO *) _st;
-	longlong epos = 0;
-	longlong cpos = std_ftell(st->fp);
-	std_fseek(st->fp, 0, SEEK_END);
-	epos = std_ftell(st->fp);
-	std_fseek(st->fp, cpos, SEEK_SET);
+longlong StdIoGetFileSize(InputStream *st) {
+	auto fp = static_cast<MkvStdIO*>(st)->fp;
+	auto cpos = std_ftell(fp);
+	std_fseek(fp, 0, SEEK_END);
+	auto epos = std_ftell(fp);
+	std_fseek(fp, cpos, SEEK_SET);
 	return epos;
 }
 
-MkvStdIO::MkvStdIO(wxString filename)
+MkvStdIO::MkvStdIO(agi::fs::path const& filename)
 : error(0)
 {
 	read = StdIoRead;
 	scan = StdIoScan;
-	getcachesize = StdIoGetCacheSize;
-	geterror = StdIoGetLastError;
-	memalloc = StdIoMalloc;
-	memrealloc = StdIoRealloc;
-	memfree = StdIoFree;
-	progress = StdIoProgress;
+	getcachesize = [](InputStream *) -> unsigned int { return CACHESIZE; };
+	geterror = [](InputStream *st) -> const char * { return strerror(((MkvStdIO *)st)->error); };
+	memalloc = [](InputStream *, size_t size) { return malloc(size); };
+	memrealloc = [](InputStream *, void *mem, size_t size) { return realloc(mem, size); };
+	memfree = [](InputStream *, void *mem) { free(mem); };
+	progress = [](InputStream *, ulonglong, ulonglong) { return 1; };
 	getfilesize = StdIoGetFileSize;
 
-	wxFileName fname(filename);
 #ifdef __VISUALC__
-	fp = _wfopen(fname.GetFullPath().wc_str(), L"rb");
+	fp = _wfopen(filename.c_str(), L"rb");
 #else
-	fp = fopen(fname.GetFullPath().utf8_str(), "rb");
+	fp = fopen(filename.c_str(), "rb");
 #endif
-	if (fp) {
-		setvbuf(fp, nullptr, _IOFBF, CACHESIZE);
-	}
-	else {
-		throw agi::FileNotFoundError(from_wx(filename));
-	}
+	if (!fp)
+		throw agi::fs::FileNotFound(filename);
+
+	setvbuf(fp, nullptr, _IOFBF, CACHESIZE);
 }
diff --git a/aegisub/src/mkv_wrap.h b/aegisub/src/mkv_wrap.h
index 83c13f8bc98f955cf04f5e8dce0912fe15ddc565..e7a3406f13430ccc2cde4c49bff7b21d5a29c404 100644
--- a/aegisub/src/mkv_wrap.h
+++ b/aegisub/src/mkv_wrap.h
@@ -1,29 +1,16 @@
-// Copyright (c) 2006, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -32,10 +19,8 @@
 /// @ingroup video_input
 ///
 
-#include <map>
-#include <wx/string.h>
-
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
 
 DEFINE_SIMPLE_EXCEPTION_NOINNER(MatroskaException, agi::Exception, "matroksa_wrapper/generic")
 
@@ -44,7 +29,7 @@ class AssFile;
 class MatroskaWrapper {
 public:
 	/// Check if the file is a matroska file with at least one subtitle track
-	static bool HasSubtitles(wxString const& filename);
+	static bool HasSubtitles(agi::fs::path const& filename);
 	/// Load subtitles from a matroska file
-	static void GetSubtitles(wxString const& filename, AssFile *target);
+	static void GetSubtitles(agi::fs::path const& filename, AssFile *target);
 };
diff --git a/aegisub/src/options.h b/aegisub/src/options.h
index 6c3261fc5b88884551ca7b6a9312a6d829ba9433..bded9e4153caf01acaf460d34dbfcf5c5c365d7f 100644
--- a/aegisub/src/options.h
+++ b/aegisub/src/options.h
@@ -18,10 +18,15 @@
 #include <libaegisub/option.h>
 #include <libaegisub/option_value.h>
 
+namespace agi {
+	class Path;
+}
+
 /// For holding all configuration-related objects and values.
 namespace config {
 	extern agi::Options *opt;    ///< Options
 	extern agi::MRUManager *mru; ///< Most Recently Used
+	extern agi::Path *path;
 }
 
 /// Macro to get OptionValue object
diff --git a/aegisub/src/preferences.cpp b/aegisub/src/preferences.cpp
index c6c3eed93cf397d433e268d18ea58e539cc4e743..1727c3ed915cedcd25d192d3ffdbb96d8bc29db2 100644
--- a/aegisub/src/preferences.cpp
+++ b/aegisub/src/preferences.cpp
@@ -30,7 +30,6 @@
 #include <wx/sizer.h>
 #include <wx/spinctrl.h>
 #include <wx/stattext.h>
-#include <wx/stdpaths.h>
 #include <wx/treebook.h>
 #include <wx/treebook.h>
 
diff --git a/aegisub/src/preferences_base.cpp b/aegisub/src/preferences_base.cpp
index f85569894a89b1d83efd578e9cc3526c2673ce15..f51c5034e1815ca9c4bc26e1c1c887f0d77570c9 100644
--- a/aegisub/src/preferences_base.cpp
+++ b/aegisub/src/preferences_base.cpp
@@ -28,7 +28,6 @@
 #include <wx/sizer.h>
 #include <wx/spinctrl.h>
 #include <wx/stattext.h>
-#include <wx/stdpaths.h>
 #include <wx/treebook.h>
 #include <wx/treebook.h>
 
@@ -64,8 +63,7 @@ OPTION_UPDATER(BoolUpdater, wxCommandEvent, OptionValueBool, !!evt.GetInt());
 OPTION_UPDATER(ColourUpdater, wxThreadEvent, OptionValueColor, evt.GetPayload<agi::Color>());
 
 static void browse_button(wxTextCtrl *ctrl) {
-	wxString def = StandardPaths::DecodePath(ctrl->GetValue());
-	wxDirDialog dlg(0, _("Please choose the folder:"), def);
+	wxDirDialog dlg(0, _("Please choose the folder:"), StandardPaths::DecodePath(from_wx(ctrl->GetValue())).wstring());
 	if (dlg.ShowModal() == wxID_OK) {
 		wxString dir = dlg.GetPath();
 		if (!dir.empty())
diff --git a/aegisub/src/preferences_base.h b/aegisub/src/preferences_base.h
index dce5b48b4ed59230ab6a5a22adaa792123ee693d..810afffffcefe8bd4cd49717151465bcdeffe5dd 100644
--- a/aegisub/src/preferences_base.h
+++ b/aegisub/src/preferences_base.h
@@ -17,7 +17,15 @@
 /// @see preferences_base.cpp
 /// @ingroup configuration_ui
 
+#include <wx/panel.h>
+#include <wx/scrolwin.h>
+
 class Preferences;
+class wxControl;
+class wxFlexGridSizer;
+class wxSizer;
+class wxString;
+class wxTreebook;
 
 class OptionPage : public wxScrolled<wxPanel> {
 	template<class T>
@@ -32,7 +40,6 @@ protected:
 	wxSizer *sizer;
 	Preferences *parent;
 
-
 	wxFlexGridSizer* PageSizer(wxString name);
 
 	void CellSkip(wxFlexGridSizer *flex);
diff --git a/aegisub/src/scintilla_text_selection_controller.cpp b/aegisub/src/scintilla_text_selection_controller.cpp
index b5c679503661c316403a63c8dfe4dd6e50e1edfc..642b05bdac8df3fa922b00177b04a13f7f9ac83a 100644
--- a/aegisub/src/scintilla_text_selection_controller.cpp
+++ b/aegisub/src/scintilla_text_selection_controller.cpp
@@ -26,21 +26,21 @@ ScintillaTextSelectionController::ScintillaTextSelectionController(ScintillaText
 }
 
 void ScintillaTextSelectionController::SetInsertionPoint(int position) {
-	ctrl->SetInsertionPoint(ctrl->GetUnicodePosition(position));
+	ctrl->SetInsertionPoint(position);
 }
 
 int ScintillaTextSelectionController::GetInsertionPoint() const {
-	return ctrl->GetReverseUnicodePosition(ctrl->GetInsertionPoint());
+	return ctrl->GetInsertionPoint();
 }
 
 void ScintillaTextSelectionController::SetSelection(int start, int end) {
-	ctrl->SetSelectionU(start, end);
+	ctrl->SetSelection(start, end);
 }
 
 int ScintillaTextSelectionController::GetSelectionStart() const {
-	return ctrl->GetReverseUnicodePosition(ctrl->GetSelectionStart());
+	return ctrl->GetSelectionStart();
 }
 
 int ScintillaTextSelectionController::GetSelectionEnd() const {
-	return ctrl->GetReverseUnicodePosition(ctrl->GetSelectionEnd());
+	return ctrl->GetSelectionEnd();
 }
diff --git a/aegisub/src/search_replace_engine.cpp b/aegisub/src/search_replace_engine.cpp
index ccb999ee4beabca13bb8a8cde008205c27758414..6fb45d60acedfa92ad7ae8ecc650bf7ea4667651 100644
--- a/aegisub/src/search_replace_engine.cpp
+++ b/aegisub/src/search_replace_engine.cpp
@@ -26,14 +26,13 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/algorithm/string/case_conv.hpp>
+
 #include <wx/msgdlg.h>
-#include <wx/regex.h>
 
 static const size_t bad_pos = -1;
 
 namespace {
-DEFINE_SIMPLE_EXCEPTION(BadRegex, agi::InvalidInputException, "bad_regex")
-
 auto get_dialogue_field(SearchReplaceSettings::Field field) -> decltype(&AssDialogue::Text) {
 	switch (field) {
 		case SearchReplaceSettings::Field::TEXT: return &AssDialogue::Text;
@@ -47,28 +46,28 @@ auto get_dialogue_field(SearchReplaceSettings::Field field) -> decltype(&AssDial
 typedef std::function<MatchState (const AssDialogue*, size_t)> matcher;
 
 class noop_accessor {
-	boost::flyweight<wxString> AssDialogue::*field;
+	boost::flyweight<std::string> AssDialogue::*field;
 	size_t start;
 
 public:
 	noop_accessor(SearchReplaceSettings::Field f) : field(get_dialogue_field(f)), start(0) { }
 
-	wxString get(const AssDialogue *d, size_t s) {
+	std::string get(const AssDialogue *d, size_t s) {
 		start = s;
 		return (d->*field).get().substr(s);
 	}
 
-	MatchState make_match_state(size_t s, size_t e, wxRegEx *r = nullptr) {
+	MatchState make_match_state(size_t s, size_t e, boost::regex *r = nullptr) {
 		return MatchState(s + start, e + start, r);
 	}
 };
 
 class skip_tags_accessor {
-	boost::flyweight<wxString> AssDialogue::*field;
+	boost::flyweight<std::string> AssDialogue::*field;
 	std::vector<std::pair<size_t, size_t>> blocks;
 	size_t start;
 
-	void parse_str(wxString const& str) {
+	void parse_str(std::string const& str) {
 		blocks.clear();
 
 		size_t ovr_start = bad_pos;
@@ -87,11 +86,11 @@ class skip_tags_accessor {
 public:
 	skip_tags_accessor(SearchReplaceSettings::Field f) : field(get_dialogue_field(f)), start(0) { }
 
-	wxString get(const AssDialogue *d, size_t s) {
+	std::string get(const AssDialogue *d, size_t s) {
 		auto const& str = (d->*field).get();
 		parse_str(str);
 
-		wxString out;
+		std::string out;
 
 		size_t last = s;
 		for (auto const& block : blocks) {
@@ -108,7 +107,7 @@ public:
 		return out;
 	}
 
-	MatchState make_match_state(size_t s, size_t e, wxRegEx *r = nullptr) {
+	MatchState make_match_state(size_t s, size_t e, boost::regex *r = nullptr) {
 		s += start;
 		e += start;
 
@@ -140,31 +139,28 @@ public:
 };
 
 template<typename Accessor>
-matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Accessor&& a) {
+matcher get_matcher(SearchReplaceSettings const& settings, Accessor&& a) {
 	if (settings.use_regex) {
-		int flags = wxRE_ADVANCED;
+		int flags = boost::regex::perl;
 		if (!settings.match_case)
-			flags |= wxRE_ICASE;
+			flags |= boost::regex::icase;
 
-		regex->Compile(settings.find, flags);
-		if (!regex->IsValid())
-			throw BadRegex("Invalid syntax in regular expression", nullptr);
+		boost::regex regex(settings.find, flags);
 
 		return [=](const AssDialogue *diag, size_t start) mutable -> MatchState {
-			if (!regex->Matches(a.get(diag, start)))
+			boost::smatch result;
+			auto const& str = a.get(diag, start);
+			if (!regex_search(str, result, regex, start > 0 ? boost::match_not_bol : boost::match_default))
 				return MatchState();
-
-			size_t match_start, match_len;
-			regex->GetMatch(&match_start, &match_len, 0);
-			return a.make_match_state(match_start, match_start + match_len, regex);
+			return a.make_match_state(result.position(), result.position() + result.length(), &regex);
 		};
 	}
 
 	bool full_match_only = settings.exact_match;
 	bool match_case = settings.match_case;
-	wxString look_for = settings.find;
+	std::string look_for = settings.find;
 	if (!settings.match_case)
-		look_for.MakeLower();
+		boost::to_lower(look_for);
 
 	return [=](const AssDialogue *diag, size_t start) mutable -> MatchState {
 		auto str = a.get(diag, start);
@@ -172,10 +168,10 @@ matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Acces
 			return MatchState();
 
 		if (!match_case)
-			str.MakeLower();
+			boost::to_lower(str);
 
 		size_t pos = str.find(look_for);
-		if (pos == wxString::npos)
+		if (pos == std::string::npos)
 			return MatchState();
 
 		return a.make_match_state(pos, pos + look_for.size());
@@ -192,10 +188,10 @@ Iterator circular_next(Iterator it, Container& c) {
 
 }
 
-std::function<MatchState (const AssDialogue*, size_t)> SearchReplaceEngine::GetMatcher(SearchReplaceSettings const& settings, wxRegEx *regex) {
+std::function<MatchState (const AssDialogue*, size_t)> SearchReplaceEngine::GetMatcher(SearchReplaceSettings const& settings) {
 	if (settings.skip_tags)
-		return get_matcher(settings, regex, skip_tags_accessor(settings.field));
-	return get_matcher(settings, regex, noop_accessor(settings.field));
+		return get_matcher(settings, skip_tags_accessor(settings.field));
+	return get_matcher(settings, noop_accessor(settings.field));
 }
 
 SearchReplaceEngine::SearchReplaceEngine(agi::Context *c)
@@ -208,11 +204,10 @@ void SearchReplaceEngine::Replace(AssDialogue *diag, MatchState &ms) {
 	auto& diag_field = diag->*get_dialogue_field(settings.field);
 	auto text = diag_field.get();
 
-	wxString replacement = settings.replace_with;
+	std::string replacement = settings.replace_with;
 	if (ms.re) {
-		wxString to_replace = text.substr(ms.start, ms.end - ms.start);
-		ms.re->ReplaceFirst(&to_replace, settings.replace_with);
-		replacement = to_replace;
+		auto to_replace = text.substr(ms.start, ms.end - ms.start);
+		replacement = regex_replace(to_replace, *ms.re, replacement, boost::format_first_only);
 	}
 
 	diag_field = text.substr(0, ms.start) + replacement + text.substr(ms.end);
@@ -223,8 +218,7 @@ bool SearchReplaceEngine::FindReplace(bool replace) {
 	if (!initialized)
 		return false;
 
-	wxRegEx r;
-	auto matches = GetMatcher(settings, &r);
+	auto matches = GetMatcher(settings);
 
 	AssDialogue *line = context->selectionController->GetActiveLine();
 	auto it = context->ass->Line.iterator_to(*line);
@@ -302,8 +296,7 @@ bool SearchReplaceEngine::ReplaceAll() {
 
 	size_t count = 0;
 
-	wxRegEx r;
-	auto matches = GetMatcher(settings, &r);
+	auto matches = GetMatcher(settings);
 
 	SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
 	bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
@@ -315,12 +308,9 @@ bool SearchReplaceEngine::ReplaceAll() {
 		if (settings.use_regex) {
 			if (MatchState ms = matches(diag, 0)) {
 				auto& diag_field = diag->*get_dialogue_field(settings.field);
-				auto text = diag_field.get();
-				// A zero length match (such as '$') will always be replaced
-				// maxMatches times, which is almost certainly not what the user
-				// wanted, so limit it to one replacement in that situation
-				count += ms.re->Replace(&text, settings.replace_with, ms.start == ms.end);
-				diag_field = text;
+				std::string const& text = diag_field.get();
+				count += distance(boost::sregex_iterator(begin(text), end(text), *ms.re), boost::sregex_iterator());
+				diag_field = regex_replace(text, *ms.re, settings.replace_with);
 			}
 			continue;
 		}
diff --git a/aegisub/src/search_replace_engine.h b/aegisub/src/search_replace_engine.h
index 95c9ad1c344c822ce2a5051d7df240bdb120ac3f..5ea41d12dcca175a7fdfd0cd479b888b4e29dfd0 100644
--- a/aegisub/src/search_replace_engine.h
+++ b/aegisub/src/search_replace_engine.h
@@ -15,18 +15,18 @@
 // Aegisub Project http://www.aegisub.org/
 
 #include <functional>
-#include <wx/string.h>
+#include <boost/regex.hpp>
+#include <string>
 
 namespace agi { struct Context; }
 class AssDialogue;
-class wxRegEx;
 
 struct MatchState {
-	wxRegEx *re;
+	boost::regex *re;
 	size_t start, end;
 
 	MatchState() : re(nullptr), start(0), end(-1) { }
-	MatchState(size_t s, size_t e, wxRegEx *re) : re(re), start(s), end(e) { }
+	MatchState(size_t s, size_t e, boost::regex *re) : re(re), start(s), end(e) { }
 	operator bool() const { return end != (size_t)-1; }
 };
 
@@ -43,8 +43,8 @@ struct SearchReplaceSettings {
 		SELECTED
 	};
 
-	wxString find;
-	wxString replace_with;
+	std::string find;
+	std::string replace_with;
 
 	Field field;
 	Limit limit_to;
@@ -71,7 +71,7 @@ public:
 
 	void Configure(SearchReplaceSettings const& new_settings);
 
-	static std::function<MatchState (const AssDialogue*, size_t)> GetMatcher(SearchReplaceSettings const& settings, wxRegEx *regex);
+	static std::function<MatchState (const AssDialogue*, size_t)> GetMatcher(SearchReplaceSettings const& settings);
 
 	SearchReplaceEngine(agi::Context *c);
 };
diff --git a/aegisub/src/spellchecker.cpp b/aegisub/src/spellchecker.cpp
index 01073f7308b3199e2989aff58069e047780acbc7..f070fee4a3962479ba52acd51ff3ec4f26e54eb7 100644
--- a/aegisub/src/spellchecker.cpp
+++ b/aegisub/src/spellchecker.cpp
@@ -34,27 +34,21 @@
 
 #include "config.h"
 
-#ifdef WITH_HUNSPELL
+#include "include/aegisub/spellchecker.h"
 #include "spellchecker_hunspell.h"
-#endif
 
-#include "compat.h"
-#include "include/aegisub/spellchecker.h"
 #include "options.h"
 
 agi::SpellChecker *SpellCheckerFactory::GetSpellChecker() {
 	std::vector<std::string> list = GetClasses(OPT_GET("Tool/Spell Checker/Backend")->GetString());
 	if (list.empty()) return nullptr;
 
-	// Get provider
-	wxString error;
+	std::string error;
 	for (auto const& name : list) {
 		try {
 			agi::SpellChecker *checker = Create(name);
 			if (checker) return checker;
 		}
-		catch (wxString const& err) { error += name + " factory: " + err + "\n"; }
-		catch (const char *err) { error += name + " factory: " + wxString(err) + "\n"; }
 		catch (...) { error += name + " factory: Unknown error\n"; }
 	}
 
diff --git a/aegisub/src/spellchecker_hunspell.cpp b/aegisub/src/spellchecker_hunspell.cpp
index fd71c0359e6d51a8aa879e5bd71b4b659fcd6037..7062fae8f3ec46bfa680cc2d3574443f33729e4b 100644
--- a/aegisub/src/spellchecker_hunspell.cpp
+++ b/aegisub/src/spellchecker_hunspell.cpp
@@ -21,26 +21,20 @@
 #include "config.h"
 
 #ifdef WITH_HUNSPELL
-
 #include "spellchecker_hunspell.h"
 
-#include <algorithm>
-#include <iterator>
-#include <set>
-
-#include <wx/dir.h>
-#include <wx/filename.h>
-
-#include <hunspell/hunspell.hxx>
+#include "options.h"
+#include "standard_paths.h"
 
+#include <libaegisub/charset_conv.h>
+#include <libaegisub/fs.h>
 #include <libaegisub/io.h>
 #include <libaegisub/line_iterator.h>
 #include <libaegisub/log.h>
 
-#include "charset_conv.h"
-#include "compat.h"
-#include "options.h"
-#include "standard_paths.h"
+#include <hunspell/hunspell.hxx>
+
+#include <boost/format.hpp>
 
 HunspellSpellChecker::HunspellSpellChecker()
 : lang_listener(OPT_SUB("Tool/Spell Checker/Language", &HunspellSpellChecker::OnLanguageChanged, this))
@@ -97,26 +91,24 @@ void HunspellSpellChecker::ReadUserDictionary() {
 
 	// Read the old contents of the user's dictionary
 	try {
-		agi::scoped_ptr<std::istream> stream(agi::io::Open(from_wx(userDicPath)));
+		std::unique_ptr<std::istream> stream(agi::io::Open(userDicPath));
 		copy_if(
 			++agi::line_iterator<std::string>(*stream), agi::line_iterator<std::string>(),
 			inserter(customWords, customWords.end()),
 			[](std::string const& str) { return !str.empty(); });
 	}
-	catch (agi::FileNotFoundError const&) {
+	catch (agi::fs::FileNotFound const&) {
 		// Not an error; user dictionary just doesn't exist
 	}
 }
 
 void HunspellSpellChecker::WriteUserDictionary() {
 	// Ensure that the path exists
-	wxFileName fn(userDicPath);
-	if (!fn.DirExists())
-		wxFileName::Mkdir(fn.GetPath());
+	agi::fs::CreateDirectory(userDicPath.parent_path());
 
 	// Write the new dictionary
 	{
-		agi::io::Save writer(from_wx(userDicPath));
+		agi::io::Save writer(userDicPath);
 		writer.Get() << customWords.size() << "\n";
 		copy(customWords.begin(), customWords.end(), std::ostream_iterator<std::string>(writer.Get(), "\n"));
 	}
@@ -165,23 +157,21 @@ std::vector<std::string> HunspellSpellChecker::GetSuggestions(std::string const&
 std::vector<std::string> HunspellSpellChecker::GetLanguageList() {
 	if (!languages.empty()) return languages;
 
-	wxArrayString dic, aff;
+	std::vector<std::string> dic, aff;
 
 	// Get list of dictionaries
-	wxString path = StandardPaths::DecodePath("?data/dictionaries/");
-	if (wxFileName::DirExists(path)) {
-		wxDir::GetAllFiles(path, &dic, "*.dic", wxDIR_FILES);
-		wxDir::GetAllFiles(path, &aff, "*.aff", wxDIR_FILES);
-	}
-	path = StandardPaths::DecodePath(to_wx(OPT_GET("Path/Dictionary")->GetString()) + "/");
-	if (wxFileName::DirExists(path)) {
-		wxDir::GetAllFiles(path, &dic, "*.dic", wxDIR_FILES);
-		wxDir::GetAllFiles(path, &aff, "*.aff", wxDIR_FILES);
-	}
-	if (aff.empty()) return std::vector<std::string>();
+	auto path = StandardPaths::DecodePath("?data/dictionaries/");
+	agi::fs::DirectoryIterator(path, "*.dic").GetAll(dic);
+	agi::fs::DirectoryIterator(path, "*.aff").GetAll(aff);
+
+	path = StandardPaths::DecodePath(OPT_GET("Path/Dictionary")->GetString());
+	agi::fs::DirectoryIterator(path, "*.dic").GetAll(dic);
+	agi::fs::DirectoryIterator(path, "*.aff").GetAll(aff);
+
+	if (dic.empty() || aff.empty()) return languages;
 
-	dic.Sort();
-	aff.Sort();
+	sort(begin(dic), end(dic));
+	sort(begin(aff), end(aff));
 
 	// Drop extensions
 	for (size_t i = 0; i < dic.size(); ++i) dic[i].resize(dic[i].size() - 4);
@@ -189,15 +179,14 @@ std::vector<std::string> HunspellSpellChecker::GetLanguageList() {
 
 	// Verify that each aff has a dic
 	for (size_t i = 0, j = 0; i < dic.size() && j < aff.size(); ) {
-		int cmp = dic[i].Cmp(aff[j]);
+		int cmp = dic[i].compare(aff[j]);
 		if (cmp < 0) ++i;
 		else if (cmp > 0) ++j;
 		else {
 			// Don't insert a language twice if it's in both the user dir and
 			// the app's dir
-			std::string name = from_wx(wxFileName(aff[j]).GetName());
-			if (languages.empty() || name != languages.back())
-				languages.push_back(name);
+			if (languages.empty() || aff[j] != languages.back())
+				languages.push_back(aff[j]);
 			++i;
 			++j;
 		}
@@ -208,31 +197,31 @@ std::vector<std::string> HunspellSpellChecker::GetLanguageList() {
 void HunspellSpellChecker::OnLanguageChanged() {
 	hunspell.reset();
 
-	std::string language = OPT_GET("Tool/Spell Checker/Language")->GetString();
+	auto language = OPT_GET("Tool/Spell Checker/Language")->GetString();
 	if (language.empty()) return;
 
-	wxString custDicRoot = StandardPaths::DecodePath(to_wx(OPT_GET("Path/Dictionary")->GetString()));
-	wxString dataDicRoot = StandardPaths::DecodePath("?data/dictionaries");
+	auto custDicRoot = StandardPaths::DecodePath(OPT_GET("Path/Dictionary")->GetString());
+	auto dataDicRoot = StandardPaths::DecodePath("?data/dictionaries");
 
 	// If the user has a dic/aff pair in their dictionary path for this language
 	// use that; otherwise use the one from Aegisub's install dir, adding words
 	// from the dic in the user's dictionary path if it exists
-	wxString affPath = wxString::Format("%s/%s.aff", custDicRoot, language);
-	wxString dicPath = wxString::Format("%s/%s.dic", custDicRoot, language);
-	userDicPath = wxString::Format("%s/user_%s.dic", StandardPaths::DecodePath("?user/dictionaries"), language);
-	if (!wxFileExists(affPath) || !wxFileExists(dicPath)) {
-		affPath = wxString::Format("%s/%s.aff", dataDicRoot, language);
-		dicPath = wxString::Format("%s/%s.dic", dataDicRoot, language);
+	auto affPath = custDicRoot/(language + ".aff");
+	auto dicPath = custDicRoot/(language + ".dic");
+	userDicPath = StandardPaths::DecodePath("?user/dictionaries")/str(boost::format("user_%s.dic") % language);
+	if (!agi::fs::FileExists(affPath) || !agi::fs::FileExists(dicPath)) {
+		affPath = dataDicRoot/(language + ".aff");
+		dicPath = dataDicRoot/(language + ".dic");
 	}
 
 	LOG_I("dictionary/file") << dicPath;
 
-	if (!wxFileExists(affPath) || !wxFileExists(dicPath)) {
+	if (!agi::fs::FileExists(affPath) || !agi::fs::FileExists(dicPath)) {
 		LOG_D("dictionary/file") << "Dictionary not found";
 		return;
 	}
 
-	hunspell.reset(new Hunspell(affPath.mb_str(csConvLocal), dicPath.mb_str(csConvLocal)));
+	hunspell.reset(new Hunspell(agi::fs::ShortName(affPath).c_str(), agi::fs::ShortName(dicPath).c_str()));
 	if (!hunspell) return;
 
 	conv.reset(new agi::charset::IconvWrapper("utf-8", hunspell->get_dic_encoding()));
diff --git a/aegisub/src/spellchecker_hunspell.h b/aegisub/src/spellchecker_hunspell.h
index 1a96b0266e4567cbac8ed6dc709bd65c9c42e07d..2f64e6fcd9c44d9102704e7077dd7b263ecd0a59 100644
--- a/aegisub/src/spellchecker_hunspell.h
+++ b/aegisub/src/spellchecker_hunspell.h
@@ -20,33 +20,32 @@
 ///
 
 #ifdef WITH_HUNSPELL
-
 #include <libaegisub/spellchecker.h>
 
-#include <libaegisub/scoped_ptr.h>
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/signal.h>
 
+#include <boost/filesystem/path.hpp>
+#include <memory>
 #include <set>
 
-#include <wx/string.h>
-
 namespace agi { namespace charset { class IconvWrapper; } }
 class Hunspell;
 
 /// @brief Hunspell-based spell checker implementation
 class HunspellSpellChecker : public agi::SpellChecker {
 	/// Hunspell instance
-	agi::scoped_ptr<Hunspell> hunspell;
+	std::unique_ptr<Hunspell> hunspell;
 
 	/// Conversions between the dictionary charset and utf-8
-	agi::scoped_ptr<agi::charset::IconvWrapper> conv;
-	agi::scoped_ptr<agi::charset::IconvWrapper> rconv;
+	std::unique_ptr<agi::charset::IconvWrapper> conv;
+	std::unique_ptr<agi::charset::IconvWrapper> rconv;
 
 	/// Languages which we have dictionaries for
 	std::vector<std::string> languages;
 
 	/// Path to user-local dictionary.
-	wxString userDicPath;
+	agi::fs::path userDicPath;
 
 	/// Words in the custom user dictionary
 	std::set<std::string> customWords;
diff --git a/aegisub/src/spline.cpp b/aegisub/src/spline.cpp
index 57dc120ea71f0046ce12adbc346503ad5872a531..b2a35bcaec04c4dc96ef9ab8648b8b1fc72db6e1 100644
--- a/aegisub/src/spline.cpp
+++ b/aegisub/src/spline.cpp
@@ -34,15 +34,16 @@
 
 #include "config.h"
 
-#include <limits>
-
-#include <wx/tokenzr.h>
-
 #include "spline.h"
 
 #include "utils.h"
 #include "visual_tool.h"
 
+#include <libaegisub/util.h>
+
+#include <boost/tokenizer.hpp>
+#include <limits>
+
 Spline::Spline(const VisualToolBase &tl)
 : coord_translator(tl)
 , scale(1)
@@ -63,8 +64,8 @@ void Spline::SetScale(int new_scale) {
 	scale = 1 << (raw_scale - 1);
 }
 
-wxString Spline::EncodeToAss() const {
-	wxString result;
+std::string Spline::EncodeToAss() const {
+	std::string result;
 	result.reserve(size() * 10);
 	char last = 0;
 
@@ -103,7 +104,7 @@ wxString Spline::EncodeToAss() const {
 	return result;
 }
 
-void Spline::DecodeFromAss(wxString str) {
+void Spline::DecodeFromAss(std::string const& str) {
 	// Clear current
 	clear();
 	std::vector<float> stack;
@@ -113,11 +114,10 @@ void Spline::DecodeFromAss(wxString str) {
 	Vector2D pt(0, 0);
 
 	// Tokenize the string
-	wxStringTokenizer tkn(str, " ");
-	while (tkn.HasMoreTokens()) {
-		wxString token = tkn.GetNextToken();
+	boost::char_separator<char> sep("|");
+	for (auto const& token : boost::tokenizer<boost::char_separator<char>>(str, sep)) {
 		double n;
-		if (token.ToCDouble(&n)) {
+		if (agi::util::try_parse(token, &n)) {
 			stack.push_back(n);
 
 			// Move
@@ -149,7 +149,6 @@ void Spline::DecodeFromAss(wxString str) {
 				stack.clear();
 			}
 		}
-
 		// Got something else
 		else if (token.size() == 1) {
 			command = token[0];
diff --git a/aegisub/src/spline.h b/aegisub/src/spline.h
index d6ac8e17a93b9e67e1e31b2fdbc43cae86788f53..6024782daa72e58d44729bbe7dc4feb17e7eca9d 100644
--- a/aegisub/src/spline.h
+++ b/aegisub/src/spline.h
@@ -33,9 +33,7 @@
 ///
 
 #include <list>
-#include <vector>
-
-#include <wx/gdicmn.h>
+#include <string>
 
 #include "spline_curve.h"
 
@@ -57,10 +55,10 @@ public:
 	Spline(const VisualToolBase &scale);
 
 	/// Encode to an ASS vector drawing
-	wxString EncodeToAss() const;
+	std::string EncodeToAss() const;
 
 	/// Decode an ASS vector drawing
-	void DecodeFromAss(wxString str);
+	void DecodeFromAss(std::string const& str);
 
 	/// Set the scale
 	/// @param new_scale Power-of-two to scale coordinates by
diff --git a/aegisub/src/standard_paths.cpp b/aegisub/src/standard_paths.cpp
index 9be84b38098d5edee6d9e7e51da618376951bf10..d34fc89906801dd2531563fd819d4cd8dc87b075 100644
--- a/aegisub/src/standard_paths.cpp
+++ b/aegisub/src/standard_paths.cpp
@@ -1,29 +1,16 @@
-// Copyright (c) 2007, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -36,60 +23,14 @@
 
 #include "standard_paths.h"
 
-#include <wx/filename.h>
-#include <wx/stdpaths.h>
-
-StandardPaths &StandardPaths::GetInstance() {
-	static StandardPaths instance;
-	return instance;
-}
-
-StandardPaths::StandardPaths() {
-   wxStandardPathsBase &paths = wxStandardPaths::Get();
-
-#if defined(__UNIX__) && !defined(__APPLE__)
-   // Relocation support, this is required to set the prefix to all
-   // wx StandardPaths.
-   static_cast<wxStandardPaths&>(paths).SetInstallPrefix(wxT(INSTALL_PREFIX));
-#endif
-
-	DoSetPathValue("?data", paths.GetDataDir());
-	DoSetPathValue("?user", paths.GetUserDataDir());
-	DoSetPathValue("?local", paths.GetUserLocalDataDir());
-	DoSetPathValue("?temp", paths.GetTempDir());
-
-	// Create paths if they don't exist
-	if (!wxDirExists(paths.GetUserDataDir()))
-		wxMkDir(paths.GetUserDataDir(), 0777);
-	if (!wxDirExists(paths.GetUserLocalDataDir()))
-		wxMkDir(paths.GetUserLocalDataDir(), 0777);
-}
-
-wxString StandardPaths::DoDecodePath(wxString path) {
-	if (!path || path[0] != '?')
-		return path;
+#include "options.h"
 
-	// Split ?part from rest
-	path.Replace("\\","/");
-	int pos = path.Find("/");
-	wxString path1,path2;
-	if (pos == wxNOT_FOUND) path1 = path;
-	else {
-		path1 = path.Left(pos);
-		path2 = path.Mid(pos+1);
-	}
+#include <libaegisub/path.h>
 
-	// Replace ?part if valid
-	auto iter = paths.find(path1);
-	if (iter == paths.end()) return path;
-	wxString final = iter->second + "/" + path2;
-	final.Replace("//","/");
-#ifdef WIN32
-	final.Replace("/","\\");
-#endif
-	return final;
+agi::fs::path StandardPaths::DecodePath(std::string const& path) {
+	return config::path->Decode(path);
 }
 
-void StandardPaths::DoSetPathValue(const wxString &path, const wxString &value) {
-	paths[path] = value;
+void StandardPaths::SetPathValue(std::string const& path, agi::fs::path const& value) {
+	config::path->SetToken(path, value);
 }
diff --git a/aegisub/src/standard_paths.h b/aegisub/src/standard_paths.h
index 52ddb09815e691b7f04ed4fd023e0e1f1400e1f1..2f8f2c93d80691e918c025c09c9718adc64a488b 100644
--- a/aegisub/src/standard_paths.h
+++ b/aegisub/src/standard_paths.h
@@ -1,29 +1,16 @@
-// Copyright (c) 2007, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -32,22 +19,12 @@
 /// @ingroup utility
 ///
 
-#include <map>
-#include <wx/string.h>
-
-class StandardPaths {
-	static StandardPaths &GetInstance();
-
-	std::map<wxString, wxString> paths;
-
-	StandardPaths();
-	StandardPaths(StandardPaths const&);
-	StandardPaths& operator=(StandardPaths const&);
+#include <libaegisub/fs_fwd.h>
 
-	wxString DoDecodePath(wxString path);
-	void DoSetPathValue(const wxString &path, const wxString &value);
+#include <string>
 
+class StandardPaths {
 public:
-	static wxString DecodePath(const wxString &path) { return GetInstance().DoDecodePath(path); }
-	static void SetPathValue(const wxString &path, const wxString &value) { GetInstance().DoSetPathValue(path,value); }
+	static agi::fs::path DecodePath(std::string const& path);
+	static void SetPathValue(std::string const& path, agi::fs::path const& value);
 };
diff --git a/aegisub/src/string_codec.cpp b/aegisub/src/string_codec.cpp
index f53920635ef9fe14fa99d671f6c4a02bfb751f25..7aeb585efc1eca4540c3a5fbf3425912640434b5 100644
--- a/aegisub/src/string_codec.cpp
+++ b/aegisub/src/string_codec.cpp
@@ -39,41 +39,30 @@
 
 #include "string_codec.h"
 
-wxString inline_string_encode(const wxString &input)
-{
-	const size_t inlen = input.length();
-	wxString output("");
-	output.Alloc(inlen);
-	for (size_t i = 0; i < inlen; i++) {
-		wxChar c = input[i];
-		if (c <= 0x1F || c == 0x23 || c == 0x2C || c == 0x3A || c == 0x7C) {
-			output << wxString::Format("#%02X", c);
-		} else {
-			output << c;
-		}
+#include <boost/format.hpp>
+
+std::string inline_string_encode(const std::string &input) {
+	std::string output;
+	output.reserve(input.size());
+	auto format = boost::format("#%02X");
+	for (char c : input) {
+		if (c <= 0x1F || c == 0x23 || c == 0x2C || c == 0x3A || c == 0x7C)
+			output += str(format % c);
+		else
+			output += c;
 	}
 	return output;
 }
 
-wxString inline_string_decode(const wxString &input)
-{
-	const size_t inlen = input.length();
-	wxString output("");
-	output.Alloc(inlen);
-	size_t i = 0;
-	while (i < inlen) {
-		if (input[i] == '#') {
-			// check if there's actually enough extra characters at the end of the string
-			if (inlen - i < 3)
-				break;
-			wxString charcode = input.Mid(i+1, 2);
-			long c;
-			if (charcode.ToLong(&c, 16)) {
-				output << (wchar_t)c;
-			}
-			i += 3;
-		} else {
-			output << input[i++];
+std::string inline_string_decode(const std::string &input) {
+	std::string output;
+	output.reserve(input.size());
+	for (size_t i = 0; i < input.size() - 2; ++i) {
+		if (input[i] != '#')
+			output += input[i];
+		else {
+			output += (char)strtol(input.substr(i + 1, 2).c_str(), nullptr, 16);
+			i += 2;
 		}
 	}
 	return output;
diff --git a/aegisub/src/string_codec.h b/aegisub/src/string_codec.h
index 498d314b6e0f09ac03264dbb254b244a73bd6150..f638a580d7fc624efa7d6dd4db494dea184fede1 100644
--- a/aegisub/src/string_codec.h
+++ b/aegisub/src/string_codec.h
@@ -52,7 +52,7 @@
 ///
 /// The encoded string should be usable in any kind of field in an ASS file.
 
-#include <wx/string.h>
+#include <string>
 
-wxString inline_string_encode(const wxString &input);
-wxString inline_string_decode(const wxString &input);
+std::string inline_string_encode(const std::string &input);
+std::string inline_string_decode(const std::string &input);
diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp
index 4cea88c7d00f2505be0aee6f0c7cab4291f66c46..e5e250e3d7aa5a4cb6243400284dde33de682aab 100644
--- a/aegisub/src/subs_edit_box.cpp
+++ b/aegisub/src/subs_edit_box.cpp
@@ -34,19 +34,6 @@
 
 #include "config.h"
 
-#include <functional>
-#include <unordered_set>
-
-#include <wx/bmpbuttn.h>
-#include <wx/button.h>
-#include <wx/checkbox.h>
-#include <wx/combobox.h>
-#include <wx/hashset.h>
-#include <wx/radiobut.h>
-#include <wx/settings.h>
-#include <wx/sizer.h>
-#include <wx/spinctrl.h>
-
 #include "subs_edit_box.h"
 
 #include "ass_dialogue.h"
@@ -70,6 +57,19 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/lexical_cast.hpp>
+#include <functional>
+#include <unordered_set>
+
+#include <wx/bmpbuttn.h>
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/hashset.h>
+#include <wx/radiobut.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/spinctrl.h>
+
 namespace {
 
 /// Work around wxGTK's fondness for generating events from ChangeValue
@@ -92,7 +92,7 @@ void time_edit_char_hook(wxKeyEvent &event) {
 
 SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
 : wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxRAISED_BORDER, "SubsEditBox")
-, line(0)
+, line(nullptr)
 , button_bar_split(true)
 , controls_enabled(true)
 , c(context)
@@ -232,9 +232,9 @@ wxTextCtrl *SubsEditBox::MakeMarginCtrl(wxString const& tooltip, int margin, wxS
 	middle_left_sizer->Add(ctrl, wxSizerFlags().Center());
 
 	Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&) {
-		wxString value = ctrl->GetValue();
-		SetSelectedRows([&](AssDialogue *d) { d->SetMarginString(value, margin); }, commit_msg, AssFile::COMMIT_DIAG_META);
-		if (line) change_value(ctrl, line->GetMarginString(margin));
+		int value = mid(0, boost::lexical_cast<int>(from_wx(ctrl->GetValue())), 9999);
+		SetSelectedRows([&](AssDialogue *d) { d->Margin[margin] = value; }, commit_msg, AssFile::COMMIT_DIAG_META);
+		if (line) change_value(ctrl, to_wx(line->GetMarginString(margin)));
 	}, ctrl->GetId());
 
 	return ctrl;
@@ -305,7 +305,7 @@ void SubsEditBox::OnCommit(int type) {
 		return;
 	}
 	else if (type & AssFile::COMMIT_STYLES)
-		style_box->Select(style_box->FindString(line->Style));
+		style_box->Select(style_box->FindString(to_wx(line->Style)));
 
 	if (!(type ^ AssFile::COMMIT_ORDER)) return;
 
@@ -319,38 +319,39 @@ void SubsEditBox::OnCommit(int type) {
 	}
 
 	if (type & AssFile::COMMIT_DIAG_TEXT) {
-		edit_ctrl->SetTextTo(line->Text);
+		edit_ctrl->SetTextTo(to_wx(line->Text));
 		UpdateCharacterCount(line->Text);
 	}
 
 	if (type & AssFile::COMMIT_DIAG_META) {
 		layer->SetValue(line->Layer);
 		for (size_t i = 0; i < margin.size(); ++i)
-			change_value(margin[i], line->GetMarginString(i));
+			change_value(margin[i], to_wx(line->GetMarginString(i)));
 		comment_box->SetValue(line->Comment);
-		style_box->Select(style_box->FindString(line->Style));
+		style_box->Select(style_box->FindString(to_wx(line->Style)));
 
 		PopulateList(effect_box, &AssDialogue::Effect);
-		effect_box->ChangeValue(line->Effect);
-		effect_box->SetStringSelection(line->Effect);
+		effect_box->ChangeValue(to_wx(line->Effect));
+		effect_box->SetStringSelection(to_wx(line->Effect));
 
 		PopulateList(actor_box, &AssDialogue::Actor);
-		actor_box->ChangeValue(line->Actor);
-		actor_box->SetStringSelection(line->Actor);
-		edit_ctrl->SetTextTo(line->Text);
+		actor_box->ChangeValue(to_wx(line->Actor));
+		actor_box->SetStringSelection(to_wx(line->Actor));
+		edit_ctrl->SetTextTo(to_wx(line->Text));
 	}
 }
 
-void SubsEditBox::PopulateList(wxComboBox *combo, boost::flyweight<wxString> AssDialogue::*field) {
+void SubsEditBox::PopulateList(wxComboBox *combo, boost::flyweight<std::string> AssDialogue::*field) {
 	wxEventBlocker blocker(this);
 
-	std::unordered_set<wxString, wxStringHash, wxStringEqual> values;
+	std::unordered_set<std::string> values;
 	for (auto diag : c->ass->Line | agi::of_type<AssDialogue>())
 		values.insert(diag->*field);
 	values.erase("");
 	wxArrayString arrstr;
 	arrstr.reserve(values.size());
-	copy(values.begin(), values.end(), std::back_inserter(arrstr));
+	transform(values.begin(), values.end(), std::back_inserter(arrstr),
+		(wxString (*)(std::string const&))to_wx);
 
 	combo->Freeze();
 	long pos = combo->GetInsertionPoint();
@@ -384,9 +385,9 @@ void SubsEditBox::OnSelectedSetChanged(const SubtitleSelection &, const Subtitle
 	initial_times.clear();
 }
 
-void SubsEditBox::OnLineInitialTextChanged(wxString const& new_text) {
+void SubsEditBox::OnLineInitialTextChanged(std::string const& new_text) {
 	if (split_box->IsChecked())
-		secondary_editor->SetValue(new_text);
+		secondary_editor->SetValue(to_wx(new_text));
 }
 
 void SubsEditBox::UpdateFrameTiming(agi::vfr::Framerate const& fps) {
@@ -408,7 +409,7 @@ void SubsEditBox::OnKeyDown(wxKeyEvent &event) {
 }
 
 void SubsEditBox::OnChange(wxStyledTextEvent &event) {
-	if (line && edit_ctrl->GetText() != line->Text) {
+	if (line && edit_ctrl->GetTextRaw().data() != line->Text.get()) {
 		if (event.GetModificationType() & wxSTC_STARTACTION)
 			commit_id = -1;
 		CommitText(_("modify text"));
@@ -434,8 +435,15 @@ void SubsEditBox::SetSelectedRows(T AssDialogue::*field, T value, wxString const
 	SetSelectedRows([&](AssDialogue *d) { d->*field = value; }, desc, type, amend);
 }
 
+template<class T>
+void SubsEditBox::SetSelectedRows(T AssDialogue::*field, wxString const& value, wxString const& desc, int type, bool amend) {
+	boost::flyweight<std::string> conv_value(from_wx(value));
+	SetSelectedRows([&](AssDialogue *d) { d->*field = conv_value; }, desc, type, amend);
+}
+
 void SubsEditBox::CommitText(wxString const& desc) {
-	SetSelectedRows(&AssDialogue::Text, boost::flyweight<wxString>(edit_ctrl->GetText()), desc, AssFile::COMMIT_DIAG_TEXT, true);
+	auto data = edit_ctrl->GetTextRaw();
+	SetSelectedRows(&AssDialogue::Text, boost::flyweight<std::string>(data.data(), data.length()), desc, AssFile::COMMIT_DIAG_TEXT, true);
 }
 
 void SubsEditBox::CommitTimes(TimeField field) {
@@ -534,16 +542,16 @@ void SubsEditBox::OnSplit(wxCommandEvent&) {
 	Thaw();
 
 	if (split_box->IsChecked())
-		secondary_editor->SetValue(c->initialLineState->GetInitialText());
+		secondary_editor->SetValue(to_wx(c->initialLineState->GetInitialText()));
 }
 
 void SubsEditBox::OnStyleChange(wxCommandEvent &) {
-	SetSelectedRows(&AssDialogue::Style,  boost::flyweight<wxString>(style_box->GetValue()), _("style change"), AssFile::COMMIT_DIAG_META);
+	SetSelectedRows(&AssDialogue::Style, style_box->GetValue(), _("style change"), AssFile::COMMIT_DIAG_META);
 }
 
 void SubsEditBox::OnActorChange(wxCommandEvent &evt) {
 	bool amend = evt.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED;
-	SetSelectedRows(&AssDialogue::Actor, boost::flyweight<wxString>(actor_box->GetValue()), _("actor change"), AssFile::COMMIT_DIAG_META, amend);
+	SetSelectedRows(&AssDialogue::Actor, actor_box->GetValue(), _("actor change"), AssFile::COMMIT_DIAG_META, amend);
 	PopulateList(actor_box, &AssDialogue::Actor);
 }
 
@@ -553,7 +561,7 @@ void SubsEditBox::OnLayerEnter(wxCommandEvent &) {
 
 void SubsEditBox::OnEffectChange(wxCommandEvent &evt) {
 	bool amend = evt.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED;
-	SetSelectedRows(&AssDialogue::Effect, boost::flyweight<wxString>(effect_box->GetValue()), _("effect change"), AssFile::COMMIT_DIAG_META, amend);
+	SetSelectedRows(&AssDialogue::Effect, effect_box->GetValue(), _("effect change"), AssFile::COMMIT_DIAG_META, amend);
 	PopulateList(effect_box, &AssDialogue::Effect);
 }
 
@@ -566,7 +574,7 @@ void SubsEditBox::CallCommand(const char *cmd_name) {
 	edit_ctrl->SetFocus();
 }
 
-void SubsEditBox::UpdateCharacterCount(wxString const& text) {
+void SubsEditBox::UpdateCharacterCount(std::string const& text) {
 	size_t length = MaxLineLength(text);
 	char_count->SetValue(wxString::Format("%lu", (unsigned long)length));
 	size_t limit = (size_t)OPT_GET("Subtitle/Character Limit")->GetInt();
diff --git a/aegisub/src/subs_edit_box.h b/aegisub/src/subs_edit_box.h
index f6dc587158b0e888d9d37c9272da85217d0aa5cb..998cc6331e091afb720c3e5ad05572fa1702ed21 100644
--- a/aegisub/src/subs_edit_box.h
+++ b/aegisub/src/subs_edit_box.h
@@ -38,6 +38,7 @@
 #include <boost/flyweight/flyweight_fwd.hpp>
 #include <vector>
 
+#include <wx/combobox.h>
 #include <wx/panel.h>
 #include <wx/timer.h>
 
@@ -55,7 +56,6 @@ class TextSelectionController;
 class TimeEdit;
 class wxButton;
 class wxCheckBox;
-class wxComboBox;
 class wxRadioButton;
 class wxSizer;
 class wxSpinCtrl;
@@ -149,7 +149,7 @@ class SubsEditBox : public wxPanel {
 
 	void OnActiveLineChanged(AssDialogue *new_line);
 	void OnSelectedSetChanged(const SubtitleSelection &, const SubtitleSelection &);
-	void OnLineInitialTextChanged(wxString const& new_text);
+	void OnLineInitialTextChanged(std::string const& new_text);
 
 	void OnFrameTimeRadio(wxCommandEvent &event);
 	void OnStyleChange(wxCommandEvent &event);
@@ -179,18 +179,21 @@ class SubsEditBox : public wxPanel {
 	template<class T>
 	void SetSelectedRows(T AssDialogue::*field, T value, wxString const& desc, int type, bool amend = false);
 
+	template<class T>
+	void SetSelectedRows(T AssDialogue::*field, wxString const& value, wxString const& desc, int type, bool amend = false);
+
 	/// @brief Reload the current line from the file
 	/// @param type AssFile::CommitType
 	void OnCommit(int type);
 
 	/// Regenerate a dropdown list with the unique values of a dialogue field
-	void PopulateList(wxComboBox *combo, boost::flyweight<wxString> AssDialogue::*field);
+	void PopulateList(wxComboBox *combo, boost::flyweight<std::string> AssDialogue::*field);
 
 	/// @brief Enable or disable frame timing mode
 	void UpdateFrameTiming(agi::vfr::Framerate const& fps);
 
 	/// Update the character count box for the given text
-	void UpdateCharacterCount(wxString const& text);
+	void UpdateCharacterCount(std::string const& text);
 
 	/// Call a command the restore focus to the edit box
 	void CallCommand(const char *cmd_name);
diff --git a/aegisub/src/subs_edit_ctrl.cpp b/aegisub/src/subs_edit_ctrl.cpp
index 3ec4c5544eff28a9b2d4e8a9d5c590955b750152..7ad11e153d50034c7d105a0108c069be59ccc932 100644
--- a/aegisub/src/subs_edit_ctrl.cpp
+++ b/aegisub/src/subs_edit_ctrl.cpp
@@ -34,13 +34,6 @@
 
 #include "config.h"
 
-#include <functional>
-
-#include <wx/clipbrd.h>
-#include <wx/intl.h>
-#include <wx/menu.h>
-#include <wx/settings.h>
-
 #include "subs_edit_ctrl.h"
 
 #include "ass_dialogue.h"
@@ -58,6 +51,15 @@
 #include <libaegisub/calltip_provider.h>
 #include <libaegisub/spellchecker.h>
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <functional>
+
+#include <wx/clipbrd.h>
+#include <wx/intl.h>
+#include <wx/menu.h>
+#include <wx/settings.h>
+
 /// Event ids
 enum {
 	EDIT_MENU_SPLIT_PRESERVE = 1400,
@@ -226,7 +228,7 @@ void SubsTextEditCtrl::SetStyles() {
 
 void SubsTextEditCtrl::UpdateStyle() {
 	AssDialogue *diag = context ? context->selectionController->GetActiveLine() : 0;
-	bool template_line = diag && diag->Comment && diag->Effect.get().Lower().StartsWith("template");
+	bool template_line = diag && diag->Comment && boost::istarts_with(diag->Effect.get(), "template");
 
 	tokenized_line = agi::ass::TokenizeDialogueBody(line_text, template_line);
 	agi::ass::SplitWords(line_text, tokenized_line);
@@ -291,19 +293,19 @@ void SubsTextEditCtrl::SetTextTo(wxString const& text) {
 }
 
 void SubsTextEditCtrl::Paste() {
-	wxString data = GetClipboard();
+	std::string data = GetClipboard();
 
-	data.Replace("\r\n", "\\N");
-	data.Replace("\n", "\\N");
-	data.Replace("\r", "\\N");
+	boost::replace_all(data, "\r\n", "\\N");
+	boost::replace_all(data, "\n", "\\N");
+	boost::replace_all(data, "\r", "\\N");
 
-	int from = GetReverseUnicodePosition(GetSelectionStart());
-	int to = GetReverseUnicodePosition(GetSelectionEnd());
+	wxCharBuffer old = GetTextRaw();
+	data.insert(0, old.data(), GetSelectionStart());
+	int sel_start = data.size();
+	data.append(old.data() + GetSelectionEnd());
 
-	wxString old = GetText();
-	SetText(old.Left(from) + data + old.Mid(to));
+	SetTextRaw(data.c_str());
 
-	int sel_start = GetUnicodePosition(from + data.size());
 	SetSelectionStart(sel_start);
 	SetSelectionEnd(sel_start);
 }
diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp
index f5b8725436db7105802f80e0ddef444de3b4bcf7..f4fef7f33ff770da17815e4071acf5db3fe43cd7 100644
--- a/aegisub/src/subs_grid.cpp
+++ b/aegisub/src/subs_grid.cpp
@@ -34,34 +34,31 @@
 
 #include "config.h"
 
-#include <algorithm>
-#include <utility>
-
-#include <wx/clipbrd.h>
-#include <wx/filename.h>
-#include <wx/regex.h>
-#include <wx/tokenzr.h>
-
 #include "subs_grid.h"
 
-#include "include/aegisub/context.h"
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
+#include "include/aegisub/context.h"
 #include "options.h"
 #include "utils.h"
-#include "video_context.h"
+
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/regex.hpp>
+#include <utility>
 
 SubtitlesGrid::SubtitlesGrid(wxWindow *parent, agi::Context *context)
 : BaseGrid(parent, context, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
 {
 }
 
-static void trim_text(wxString *text) {
-	static wxRegEx start("^( |\t|\\\\[nNh])+");
-	static wxRegEx end("( |\t|\\\\[nNh])+$");
-	start.ReplaceFirst(text, "");
-	end.ReplaceFirst(text, "");
+static std::string trim_text(std::string text) {
+	boost::regex start("^( |\t|\\\\[nNh])+");
+	boost::regex end("( |\t|\\\\[nNh])+$");
+
+	regex_replace(text, start, "", boost::format_first_only);
+	regex_replace(text, end, "", boost::format_first_only);
+	return text;
 }
 
 static void expand_times(AssDialogue *src, AssDialogue *dst) {
@@ -69,11 +66,9 @@ static void expand_times(AssDialogue *src, AssDialogue *dst) {
 	dst->End = std::max(dst->End, src->End);
 }
 
-static bool check_lines(AssDialogue *d1, AssDialogue *d2, bool (wxString::*pred)(wxString const&, wxString *) const) {
-	wxString rest;
-	if ((d1->Text.get().*pred)(d2->Text.get(), &rest)) {
-		trim_text(&rest);
-		d1->Text = rest;
+static bool check_lines(AssDialogue *d1, AssDialogue *d2, bool (*pred)(std::string const&, std::string const&)) {
+	if (pred(d1->Text.get(), d2->Text.get())) {
+		d1->Text = trim_text(d1->Text.get().substr(d2->Text.get().size()));
 		expand_times(d1, d2);
 		return true;
 	}
@@ -81,14 +76,13 @@ static bool check_lines(AssDialogue *d1, AssDialogue *d2, bool (wxString::*pred)
 }
 
 static bool check_start(AssDialogue *d1, AssDialogue *d2) {
-	return check_lines(d1, d2, &wxString::StartsWith);
+	return check_lines(d1, d2, &boost::starts_with<std::string, std::string>);
 }
 
 static bool check_end(AssDialogue *d1, AssDialogue *d2) {
-	return check_lines(d1, d2, &wxString::EndsWith);
+	return check_lines(d1, d2, &boost::ends_with<std::string, std::string>);
 }
 
-/// @brief Recombine
 void SubtitlesGrid::RecombineLines() {
 	Selection selectedSet = GetSelectedSet();
 	if (selectedSet.size() < 2) return;
@@ -97,11 +91,8 @@ void SubtitlesGrid::RecombineLines() {
 
 	std::vector<AssDialogue*> sel(selectedSet.begin(), selectedSet.end());
 	sort(sel.begin(), sel.end(), &AssFile::CompStart);
-	for (auto &diag : sel) {
-		wxString text = diag->Text;
-		trim_text(&text);
-		diag->Text = text;
-	}
+	for (auto &diag : sel)
+		diag->Text = trim_text(diag->Text);
 
 	auto end = sel.end() - 1;
 	for (auto cur = sel.begin(); cur != end; ++cur) {
diff --git a/aegisub/src/subs_grid.h b/aegisub/src/subs_grid.h
index c800fe5380aff8d559c30594662d3533ac20b86c..93f7b344923d5ef371cd9d504cee9cbbffa2ba91 100644
--- a/aegisub/src/subs_grid.h
+++ b/aegisub/src/subs_grid.h
@@ -34,8 +34,6 @@
 
 #include "base_grid.h"
 
-#include <vector>
-
 class SubtitlesGrid: public BaseGrid {
 public:
 	SubtitlesGrid(wxWindow *parent, agi::Context *context);
diff --git a/aegisub/src/subs_preview.cpp b/aegisub/src/subs_preview.cpp
index 0f766585d05cb0dca3f1d7787abdbf30380c7f25..f0e6a364a4a6c4a44aa34b0bdc8a8f67837d0578 100644
--- a/aegisub/src/subs_preview.cpp
+++ b/aegisub/src/subs_preview.cpp
@@ -36,7 +36,6 @@
 #include "config.h"
 
 #include <wx/dcclient.h>
-#include <wx/dcmemory.h>
 #include <wx/msgdlg.h>
 
 #include "ass_dialogue.h"
@@ -82,8 +81,8 @@ void SubtitlesPreview::SetStyle(AssStyle const& newStyle) {
 	UpdateBitmap();
 }
 
-void SubtitlesPreview::SetText(wxString text) {
-	wxString newText = "{\\q2}" + text;
+void SubtitlesPreview::SetText(std::string const& text) {
+	std::string newText = "{\\q2}" + text;
 	if (newText != line->Text) {
 		line->Text = newText;
 		UpdateBitmap();
@@ -141,8 +140,8 @@ void SubtitlesPreview::OnSize(wxSizeEvent &evt) {
 			"No subtitles provider", wxOK | wxICON_ERROR | wxCENTER);
 	}
 
-	subFile->SetScriptInfo("PlayResX", wxString::Format("%d", w));
-	subFile->SetScriptInfo("PlayResY", wxString::Format("%d", h));
+	subFile->SetScriptInfo("PlayResX", std::to_string(w));
+	subFile->SetScriptInfo("PlayResY", std::to_string(h));
 
 	UpdateBitmap();
 }
diff --git a/aegisub/src/subs_preview.h b/aegisub/src/subs_preview.h
index b3c7a1be8d3b2bf1129d1e439dc2ea4616f9ee80..16e1e52dd7146b392ec2c913cc3f72751eda627b 100644
--- a/aegisub/src/subs_preview.h
+++ b/aegisub/src/subs_preview.h
@@ -70,7 +70,7 @@ public:
 	/// Set the style to use
 	void SetStyle(AssStyle const& style);
 	/// Set the text to display
-	void SetText(wxString text);
+	void SetText(std::string const& text);
 	/// Set the background color
 	void SetColour(agi::Color col);
 
diff --git a/aegisub/src/subtitle_format.cpp b/aegisub/src/subtitle_format.cpp
index 64821c48f6f982e51ae42f8739426d7156b98e39..66583d7fb4437f6da3b03929ba34ea1aa408c48f 100644
--- a/aegisub/src/subtitle_format.cpp
+++ b/aegisub/src/subtitle_format.cpp
@@ -43,6 +43,7 @@
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
+#include "compat.h"
 #include "subtitle_format_ass.h"
 #include "subtitle_format_ebu3264.h"
 #include "subtitle_format_encore.h"
@@ -55,11 +56,17 @@
 #include "utils.h"
 #include "video_context.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
 using namespace std::placeholders;
 
-SubtitleFormat::SubtitleFormat(wxString const& name)
+SubtitleFormat::SubtitleFormat(std::string const& name)
 : name(name)
 {
 	formats.push_back(this);
@@ -69,12 +76,16 @@ SubtitleFormat::~SubtitleFormat() {
 	formats.erase(remove(begin(formats), end(formats), this));
 }
 
-bool SubtitleFormat::CanReadFile(wxString const& filename) const {
-	return GetReadWildcards().Index(filename.AfterLast('.'), false) != wxNOT_FOUND;
+bool SubtitleFormat::CanReadFile(agi::fs::path const& filename) const {
+	auto wildcards = GetReadWildcards();
+	return any_of(begin(wildcards), end(wildcards),
+		[&](std::string const& ext) { return agi::fs::HasExtension(filename, ext); });
 }
 
-bool SubtitleFormat::CanWriteFile(wxString const& filename) const {
-	return GetWriteWildcards().Index(filename.AfterLast('.'), false) != wxNOT_FOUND;
+bool SubtitleFormat::CanWriteFile(agi::fs::path const& filename) const {
+	auto wildcards = GetWriteWildcards();
+	return any_of(begin(wildcards), end(wildcards),
+		[&](std::string const& ext) { return agi::fs::HasExtension(filename, ext); });
 }
 
 bool SubtitleFormat::CanSave(const AssFile *subs) const {
@@ -168,14 +179,16 @@ void SubtitleFormat::StripTags(AssFile &file) {
 		current->StripTags();
 }
 
-void SubtitleFormat::ConvertNewlines(AssFile &file, wxString const& newline, bool mergeLineBreaks) {
+void SubtitleFormat::ConvertNewlines(AssFile &file, std::string const& newline, bool mergeLineBreaks) {
 	for (auto current : file.Line | agi::of_type<AssDialogue>()) {
-		wxString repl(current->Text);
-		repl.Replace("\\h", " ");
-		repl.Replace("\\n", newline);
-		repl.Replace("\\N", newline);
+		std::string repl = current->Text;
+		boost::replace_all(repl, "\\h", " ");
+		boost::ireplace_all(repl, "\\n", newline);
 		if (mergeLineBreaks) {
-			while (repl.Replace(newline+newline, newline));
+			std::string dbl(newline + newline);
+			size_t pos = 0;
+			while ((pos = repl.find(dbl, pos)) != std::string::npos)
+				boost::replace_all(repl, dbl, newline);
 		}
 		current->Text = repl;
 	}
@@ -184,7 +197,7 @@ void SubtitleFormat::ConvertNewlines(AssFile &file, wxString const& newline, boo
 void SubtitleFormat::StripComments(AssFile &file) {
 	file.Line.remove_and_dispose_if([](AssEntry const& e) {
 		const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&e);
-		return diag && (diag->Comment || !diag->Text.get());
+		return diag && (diag->Comment || diag->Text.get().empty());
 	}, [](AssEntry *e) { delete e; });
 }
 
@@ -242,7 +255,7 @@ void SubtitleFormat::RecombineOverlaps(AssFile &file) {
 			newdlg->Start = curdlg->Start;
 			newdlg->End = (prevdlg->End < curdlg->End) ? prevdlg->End : curdlg->End;
 			// Put an ASS format hard linewrap between lines
-			newdlg->Text = curdlg->Text + "\\N" + prevdlg->Text;
+			newdlg->Text = curdlg->Text.get() + "\\N" + prevdlg->Text.get();
 
 			file.Line.insert(find_if(next, file.Line.end(), std::bind(dialog_start_lt, _1, newdlg)), *newdlg);
 		}
@@ -322,32 +335,30 @@ SubtitleFormat *find_or_throw(Cont &container, Pred pred) {
 	return *it;
 }
 
-const SubtitleFormat *SubtitleFormat::GetReader(wxString const& filename) {
+const SubtitleFormat *SubtitleFormat::GetReader(agi::fs::path const& filename) {
 	LoadFormats();
 	return find_or_throw(formats, std::bind(&SubtitleFormat::CanReadFile, _1, filename));
 }
 
-const SubtitleFormat *SubtitleFormat::GetWriter(wxString const& filename) {
+const SubtitleFormat *SubtitleFormat::GetWriter(agi::fs::path const& filename) {
 	LoadFormats();
 	return find_or_throw(formats, std::bind(&SubtitleFormat::CanWriteFile, _1, filename));
 }
 
-wxString SubtitleFormat::GetWildcards(int mode) {
+std::string SubtitleFormat::GetWildcards(int mode) {
 	LoadFormats();
 
-	wxArrayString all;
-	wxString final;
+	std::vector<std::string> all;
+	std::string final;
 
 	for (auto format : formats) {
-		wxArrayString cur = mode == 0 ? format->GetReadWildcards() : format->GetWriteWildcards();
+		std::vector<std::string> cur = mode == 0 ? format->GetReadWildcards() : format->GetWriteWildcards();
 		if (cur.empty()) continue;
 
-		for_each(cur.begin(), cur.end(), std::bind(&wxString::Prepend, _1, "*."));
-		copy(cur.begin(), cur.end(), std::back_inserter(all));
-		final += "|" + format->GetName() + " (" + wxJoin(cur, ',') + ")|" + wxJoin(cur, ';');
+		for_each(cur.begin(), cur.end(), [](std::string &str) { str.insert(0, "*."); });
+		all.insert(all.end(), begin(cur), end(cur));
+		final += "|" + format->GetName() + " (" + boost::join(cur, ",") + ")|" + boost::join(cur, ";");
 	}
 
-	final.Prepend(_("All Supported Formats") + " (" + wxJoin(all, ',') + ")|" + wxJoin(all, ';'));
-
-	return final;
+	return from_wx(_("All Supported Formats")) + " (" + boost::join(all, ",") + ")|" + boost::join(all, ";") + final;
 }
diff --git a/aegisub/src/subtitle_format.h b/aegisub/src/subtitle_format.h
index 62784b5c92fb80e7bb90e3f2c6616d50faa94995..b330d7413b765a1e60034d868e5d8d250bd4b391 100644
--- a/aegisub/src/subtitle_format.h
+++ b/aegisub/src/subtitle_format.h
@@ -34,24 +34,23 @@
 
 #pragma once
 
-#include <vector>
-
-#include <wx/arrstr.h>
-#include <wx/string.h>
-
 #include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
+#include <string>
+#include <vector>
 
 class AssEntry;
 class AssFile;
 namespace agi { namespace vfr { class Framerate; } }
 
 class SubtitleFormat {
-	wxString name;
+	std::string name;
 
 	/// Get this format's wildcards for a load dialog
-	virtual wxArrayString GetReadWildcards() const { return wxArrayString(); }
+	virtual std::vector<std::string> GetReadWildcards() const { return std::vector<std::string>(); }
 	/// Get this format's wildcards for a save dialog
-	virtual wxArrayString GetWriteWildcards() const { return wxArrayString(); }
+	virtual std::vector<std::string> GetWriteWildcards() const { return std::vector<std::string>(); }
 
 	/// List of loaded subtitle formats
 	static std::vector<SubtitleFormat*> formats;
@@ -62,7 +61,7 @@ public:
 	/// Convert newlines to the specified character(s)
 	/// @param lineEnd newline character(s)
 	/// @param mergeLineBreaks Should multiple consecutive line breaks be merged into one?
-	static void ConvertNewlines(AssFile &file, wxString const& newline, bool mergeLineBreaks = true);
+	static void ConvertNewlines(AssFile &file, std::string const& newline, bool mergeLineBreaks = true);
 	/// Remove All commented and empty lines
 	static void StripComments(AssFile &file);
 	/// Remove everything but the dialogue lines
@@ -82,25 +81,25 @@ public:
 	/// Constructor
 	/// @param Subtitle format name
 	/// @note Automatically registers the format
-	SubtitleFormat(wxString const& name);
+	SubtitleFormat(std::string const& name);
 	/// Destructor
 	/// @note Automatically unregisters the format
 	virtual ~SubtitleFormat();
 
 	/// Get this format's name
-	wxString GetName() const { return name; }
+	std::string const& GetName() const { return name; }
 
 	/// @brief Check if the given file can be read by this format
 	///
 	/// Default implementation simply checks if the file's extension is in the
 	/// format's wildcard list
-	virtual bool CanReadFile(wxString const& filename) const;
+	virtual bool CanReadFile(agi::fs::path const& filename) const;
 
 	/// @brief Check if the given file can be written by this format
 	///
 	/// Default implementation simply checks if the file's extension is in the
 	/// format's wildcard list
-	virtual bool CanWriteFile(wxString const& filename) const;
+	virtual bool CanWriteFile(agi::fs::path const& filename) const;
 
 	/// @brief Check if the given subtitles can be losslessly written by this format
 	///
@@ -112,22 +111,22 @@ public:
 	/// @param[out] target Destination to read lines into
 	/// @param filename File to load
 	/// @param forceEncoding Encoding to use or empty string for default
-	virtual void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding="") const { }
+	virtual void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding="") const { }
 
 	/// Save a subtitle file
 	/// @param src Data to write
 	/// @param filename File to write to
 	/// @param forceEncoding Encoding to use or empty string for default
-	virtual void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding="") const { }
+	virtual void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding="") const { }
 
 	/// Get the wildcards for a save or load dialog
 	/// @param mode 0: load 1: save
-	static wxString GetWildcards(int mode);
+	static std::string GetWildcards(int mode);
 
 	/// Get a subtitle format that can read the given file or nullptr if none can
-	static const SubtitleFormat *GetReader(wxString const& filename);
+	static const SubtitleFormat *GetReader(agi::fs::path const& filename);
 	/// Get a subtitle format that can write the given file or nullptr if none can
-	static const SubtitleFormat *GetWriter(wxString const& filename);
+	static const SubtitleFormat *GetWriter(agi::fs::path const& filename);
 	/// Initialize subtitle formats
 	static void LoadFormats();
 	/// Deinitialize subtitle formats
diff --git a/aegisub/src/subtitle_format_ass.cpp b/aegisub/src/subtitle_format_ass.cpp
index bbec0936e99f7325faed75dd917c9bfa524e71ca..194d3e00ee66dd21057c5955e35d759aef1af57e 100644
--- a/aegisub/src/subtitle_format_ass.cpp
+++ b/aegisub/src/subtitle_format_ass.cpp
@@ -38,11 +38,12 @@
 
 #include "ass_file.h"
 #include "ass_parser.h"
-#include "compat.h"
 #include "text_file_reader.h"
 #include "text_file_writer.h"
 #include "version.h"
 
+#include <libaegisub/fs.h>
+
 DEFINE_SIMPLE_EXCEPTION(AssParseError, SubtitleFormatParseError, "subtitle_io/parse/ass")
 
 AssSubtitleFormat::AssSubtitleFormat()
@@ -50,35 +51,32 @@ AssSubtitleFormat::AssSubtitleFormat()
 {
 }
 
-wxArrayString AssSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("ass");
-	formats.Add("ssa");
+std::vector<std::string> AssSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("ass");
+	formats.push_back("ssa");
 	return formats;
 }
 
-wxArrayString AssSubtitleFormat::GetWriteWildcards() const {
-	wxArrayString formats;
-	formats.Add("ass");
-	formats.Add("ssa");
+std::vector<std::string> AssSubtitleFormat::GetWriteWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("ass");
+	formats.push_back("ssa");
 	return formats;
 }
 
-void AssSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
-	target->Clear();
-
+void AssSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
 	TextFileReader file(filename, encoding);
-	int version = filename.Right(4).Lower() != ".ssa";
+	int version = !agi::fs::HasExtension(filename, "ssa");
 	AssParser parser(target, version);
 
 	while (file.HasMoreLines()) {
-		wxString line = file.ReadLineFromFile();
+		std::string line = file.ReadLineFromFile();
 		try {
 			parser.AddLine(line);
 		}
 		catch (const char *err) {
-			target->Clear();
-			throw AssParseError("Error processing line: " + from_wx(line) + ": " + err, 0);
+			throw AssParseError("Error processing line: " + line + ": " + err, 0);
 		}
 	}
 }
@@ -89,7 +87,7 @@ void AssSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt
 #define LINEBREAK "\n"
 #endif
 
-static inline wxString format(AssEntryGroup group, bool ssa) {
+static inline std::string format(AssEntryGroup group, bool ssa) {
 	if (group == ENTRY_DIALOGUE) {
 		if (ssa)
 			return "Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" LINEBREAK;
@@ -104,17 +102,17 @@ static inline wxString format(AssEntryGroup group, bool ssa) {
 			return "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding" LINEBREAK;
 	}
 
-	return wxS("");
+	return "";
 }
 
-void AssSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void AssSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	TextFileWriter file(filename, encoding);
 
 	file.WriteLineToFile("[Script Info]");
-	file.WriteLineToFile(wxString("; Script generated by Aegisub ") + GetAegisubLongVersionString());
+	file.WriteLineToFile(std::string("; Script generated by Aegisub ") + GetAegisubLongVersionString());
 	file.WriteLineToFile("; http://www.aegisub.org/");
 
-	bool ssa = filename.Right(4).Lower() == ".ssa";
+	bool ssa = agi::fs::HasExtension(filename, "ssa");
 	AssEntryGroup group = ENTRY_INFO;
 
 	for (auto const& line : src->Line) {
diff --git a/aegisub/src/subtitle_format_ass.h b/aegisub/src/subtitle_format_ass.h
index f51954917ed269daaeaa721d25944399bdabe596..b3fbda8b9661741c59765970b54ee03345bbd5f1 100644
--- a/aegisub/src/subtitle_format_ass.h
+++ b/aegisub/src/subtitle_format_ass.h
@@ -38,12 +38,12 @@ class AssSubtitleFormat : public SubtitleFormat {
 public:
 	AssSubtitleFormat();
 
-	wxArrayString GetReadWildcards() const override;
-	wxArrayString GetWriteWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
+	std::vector<std::string> GetWriteWildcards() const override;
 
 	// Naturally the ASS subtitle format can save all Ass files
 	bool CanSave(const AssFile*) const override { return true; }
 
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_ebu3264.cpp b/aegisub/src/subtitle_format_ebu3264.cpp
index babac3f5d2e49d871ada1bfd8721d850a0bcf298..908ce4f517f4326ff8e62cbabc1dd55cf3fd8285 100644
--- a/aegisub/src/subtitle_format_ebu3264.cpp
+++ b/aegisub/src/subtitle_format_ebu3264.cpp
@@ -25,8 +25,6 @@
 
 #include "subtitle_format_ebu3264.h"
 
-#include <wx/regex.h>
-
 #include <libaegisub/charset_conv.h>
 #include <libaegisub/exception.h>
 #include <libaegisub/io.h>
@@ -43,6 +41,8 @@
 #include "options.h"
 #include "text_file_writer.h"
 
+#include <boost/algorithm/string/replace.hpp>
+
 namespace
 {
 #pragma pack(push, 1)
@@ -101,11 +101,11 @@ namespace
 	/// A block of text with basic formatting information
 	struct EbuFormattedText
 	{
-		wxString text;   ///< Text in this block
-		bool underline;  ///< Is this block underlined?
-		bool italic;     ///< Is this block italic?
-		bool word_start; ///< Is it safe to line-wrap between this block and the previous one?
-		EbuFormattedText(wxString const& t, bool u = false, bool i = false, bool ws = true) : text(t), underline(u), italic(i), word_start(ws) { }
+		std::string text; ///< Text in this block
+		bool underline;   ///< Is this block underlined?
+		bool italic;      ///< Is this block italic?
+		bool word_start;  ///< Is it safe to line-wrap between this block and the previous one?
+		EbuFormattedText(std::string const& t, bool u = false, bool i = false, bool ws = true) : text(t), underline(u), italic(i), word_start(ws) { }
 	};
 	typedef std::vector<EbuFormattedText> EbuTextRow;
 
@@ -262,9 +262,6 @@ namespace
 
 		void SetTextFromAss(AssDialogue *line, bool style_underline, bool style_italic, int align, int wrap_mode)
 		{
-			// Helper for finding special characters
-			wxRegEx special_char_search("\\\\[nN]| ", wxRE_ADVANCED);
-
 			boost::ptr_vector<AssDialogueBlock> blocks(line->ParseTags());
 
 			text_rows.clear();
@@ -285,42 +282,42 @@ namespace
 					case BLOCK_PLAIN:
 					// find special characters and convert them
 					{
-						wxString text = to_wx(b.GetText());
+						std::string text = b.GetText();
 
-						text.Replace("\\t", " ");
+						boost::replace_all(text, "\\t", " ");
 
-						while (special_char_search.Matches(text))
+						size_t start = 0;
+						for (size_t i = 0; i < text.size(); ++i)
 						{
-							size_t start, len;
-							special_char_search.GetMatch(&start, &len);
+							if (text[i] != ' ' && (i + 1 >= text.size() || text[i] != '\\' || (text[i + 1] != 'N' && text[i + 1] != 'n')))
+								continue;
 
 							// add first part of text to current part
-							cur_row->back().text.append(text.Left(start));
+							cur_row->back().text.append(text.substr(start, i));
 
 							// process special character
-							wxString substr = text.Mid(start, len);
-							if (substr == "\\N" || (wrap_mode == 1 && substr == "\\n"))
+							if (text[i] == '\\' && (text[i + 1] == 'N' || wrap_mode == 1))
 							{
 								// create a new row with current style
 								text_rows.emplace_back();
 								cur_row = &text_rows.back();
 								cur_row->emplace_back("", underline, italic, true);
 							}
-							else // if (substr == " " || substr == "\\h" || substr == "\\n")
+							else // if (substr == " " || substr == "\\n")
 							{
 								cur_row->back().text.append(" ");
 								cur_row->emplace_back("", underline, italic, true);
 							}
 
-							text = text.Mid(start+len);
+							start = i + (text[i] == '\\');
 						}
 
 						// add the remaining text
-						cur_row->back().text.append(text);
+						cur_row->back().text.append(text.substr(start));
 
 						// convert \h to regular spaces
 						// done after parsing so that words aren't split on \h
-						cur_row->back().text.Replace("\\h", " ");
+						boost::replace_all(cur_row->back().text, "\\h", " ");
 					}
 					break;
 
@@ -334,7 +331,7 @@ namespace
 						// apply any changes
 						if (underline != cur_row->back().underline || italic != cur_row->back().italic)
 						{
-							if (!cur_row->back().text)
+							if (cur_row->back().text.empty())
 							{
 								// current part is empty, we can safely change formatting on it
 								cur_row->back().underline = underline;
@@ -343,7 +340,7 @@ namespace
 							else
 							{
 								// create a new empty part with new style
-								cur_row->push_back(EbuFormattedText("", underline, italic, false));
+								cur_row->emplace_back("", underline, italic, false);
 							}
 						}
 					}
@@ -396,7 +393,7 @@ namespace
 				imline.time_out -= 1;
 
 			// convert alignment from style
-			AssStyle *style = copy.GetStyle(from_wx(line->Style));
+			AssStyle *style = copy.GetStyle(line->Style);
 			if (!style)
 				style = &default_style;
 
@@ -429,20 +426,6 @@ namespace
 		return subs_list;
 	}
 
-	inline size_t buffer_size(wxString const& str)
-	{
-#if wxUSE_UNICODE_UTF8
-		return str.utf8_length();
-#else
-		return str.length() * sizeof(wxStringCharType);
-#endif
-	}
-
-	inline const char *wx_str(wxString const& str)
-	{
-		return reinterpret_cast<const char *>(str.wx_str());
-	}
-
 	std::string convert_subtitle_line(EbuSubtitle const& sub, agi::charset::IconvWrapper *encoder, bool enable_formatting)
 	{
 		std::string fullstring;
@@ -468,7 +451,7 @@ namespace
 				}
 
 				// convert text to specified encoding
-				fullstring += encoder->Convert(std::string(wx_str(block.text), buffer_size(block.text)));
+				fullstring += encoder->Convert(block.text);
 			}
 		}
 		return fullstring;
@@ -564,11 +547,11 @@ namespace
 
 	BlockGSI create_header(AssFile const& copy, EbuExportSettings const& export_settings)
 	{
-		wxString scriptinfo_title = copy.GetScriptInfo("Title");
-		wxString scriptinfo_translation = copy.GetScriptInfo("Original Translation");
-		wxString scriptinfo_editing = copy.GetScriptInfo("Original Editing");
+		std::string scriptinfo_title = copy.GetScriptInfo("Title");
+		std::string scriptinfo_translation = copy.GetScriptInfo("Original Translation");
+		std::string scriptinfo_editing = copy.GetScriptInfo("Original Editing");
 
-		agi::charset::IconvWrapper gsi_encoder(wxSTRING_ENCODING, "CP850");
+		agi::charset::IconvWrapper gsi_encoder("UTF-8", "CP850");
 
 		BlockGSI gsi;
 		memset(&gsi, 0x20, sizeof(gsi)); // fill with spaces
@@ -595,8 +578,8 @@ namespace
 		if (export_settings.text_encoding == EbuExportSettings::utf8)
 			memcpy(gsi.cct, "U8", 2);
 		memcpy(gsi.lc, "00", 2);
-		gsi_encoder.Convert(wx_str(scriptinfo_title), buffer_size(scriptinfo_title), gsi.opt, 32);
-		gsi_encoder.Convert(wx_str(scriptinfo_translation), buffer_size(scriptinfo_translation), gsi.tn, 32);
+		gsi_encoder.Convert(scriptinfo_title.c_str(), scriptinfo_title.size(), gsi.opt, 32);
+		gsi_encoder.Convert(scriptinfo_translation.c_str(), scriptinfo_translation.size(), gsi.tn, 32);
 		{
 			char buf[20];
 			time_t now;
@@ -618,7 +601,7 @@ namespace
 		gsi.tnd = '1';
 		gsi.dsn = '1';
 		memcpy(gsi.co, "NTZ", 3); // neutral zone!
-		gsi_encoder.Convert(wx_str(scriptinfo_editing), buffer_size(scriptinfo_editing), gsi.en, 32);
+		gsi_encoder.Convert(scriptinfo_editing.c_str(), scriptinfo_editing.size(), gsi.en, 32);
 		if (export_settings.text_encoding == EbuExportSettings::utf8)
 			strncpy(gsi.uda, "This file was exported by Aegisub using non-standard UTF-8 encoding for the subtitle blocks. The TTI.TF field contains UTF-8-encoded text interspersed with the standard formatting codes, which are not encoded. GSI.CCT is set to 'U8' to signify this.", sizeof(gsi.uda));
 
@@ -648,14 +631,14 @@ Ebu3264SubtitleFormat::Ebu3264SubtitleFormat()
 {
 }
 
-wxArrayString Ebu3264SubtitleFormat::GetWriteWildcards() const
+std::vector<std::string> Ebu3264SubtitleFormat::GetWriteWildcards() const
 {
-	wxArrayString formats;
-	formats.Add("stl");
+	std::vector<std::string> formats;
+	formats.push_back("stl");
 	return formats;
 }
 
-void Ebu3264SubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const
+void Ebu3264SubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const&) const
 {
 	// collect data from user
 	EbuExportSettings export_settings = get_export_config(0);
@@ -671,7 +654,7 @@ void Ebu3264SubtitleFormat::WriteFile(const AssFile *src, wxString const& filena
 	snprintf(gsi.tns, 5, "%5u", (unsigned int)subs_list.size());
 
 	// write file
-	agi::io::Save f(from_wx(filename), true);
+	agi::io::Save f(filename, true);
 	f.Get().write((const char *)&gsi, sizeof(gsi));
 	for (auto const& block : tti)
 		f.Get().write((const char *)&block, sizeof(block));
diff --git a/aegisub/src/subtitle_format_ebu3264.h b/aegisub/src/subtitle_format_ebu3264.h
index 04179f9aa499c5a44a759900a004d1107e23d0e8..72b480e35f8ac78df410c4ffb03472df4ef32eb1 100644
--- a/aegisub/src/subtitle_format_ebu3264.h
+++ b/aegisub/src/subtitle_format_ebu3264.h
@@ -27,8 +27,8 @@
 class Ebu3264SubtitleFormat : public SubtitleFormat {
 public:
 	Ebu3264SubtitleFormat();
-	wxArrayString GetWriteWildcards() const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	std::vector<std::string> GetWriteWildcards() const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 
 	DEFINE_SIMPLE_EXCEPTION(ConversionFailed, agi::InvalidInputException, "subtitle_io/ebu3264/conversion_error")
 };
diff --git a/aegisub/src/subtitle_format_encore.cpp b/aegisub/src/subtitle_format_encore.cpp
index 2312d765f8d2fdeec2a1f884b328b80ac803a5c5..13796a355d6bd1474fb3671a6f4f39913f2d350b 100644
--- a/aegisub/src/subtitle_format_encore.cpp
+++ b/aegisub/src/subtitle_format_encore.cpp
@@ -42,22 +42,25 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+
 EncoreSubtitleFormat::EncoreSubtitleFormat()
 : SubtitleFormat("Adobe Encore")
 {
 }
 
-wxArrayString EncoreSubtitleFormat::GetWriteWildcards() const {
-	wxArrayString formats;
-	formats.Add("encore.txt");
+std::vector<std::string> EncoreSubtitleFormat::GetWriteWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("encore.txt");
 	return formats;
 }
 
-bool EncoreSubtitleFormat::CanWriteFile(wxString const& filename) const {
-	return filename.EndsWith(".encore.txt");
+bool EncoreSubtitleFormat::CanWriteFile(agi::fs::path const& filename) const {
+	return boost::iends_with(filename.string(), ".encore.txt");
 }
 
-void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const&) const {
+void EncoreSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const&) const {
 	agi::vfr::Framerate fps = AskForFPS(false, true);
 	if (!fps.IsLoaded()) return;
 
@@ -70,14 +73,13 @@ void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filenam
 	StripTags(copy);
 	ConvertNewlines(copy, "\r\n");
 
-	// Encode wants ; for NTSC and : for PAL
+	// Encore wants ; for NTSC and : for PAL
 	// The manual suggests no other frame rates are supported
-	char sep = fps.NeedsDropFrames() ? ';' : ':';
-	SmpteFormatter ft(fps, sep);
+	SmpteFormatter ft(fps, fps.NeedsDropFrames() ? ";" : ":");
 
 	// Write lines
 	int i = 0;
 	TextFileWriter file(filename, "UTF-8");
 	for (auto current : copy.Line | agi::of_type<AssDialogue>())
-		file.WriteLineToFile(wxString::Format("%i %s %s %s", ++i, ft.ToSMPTE(current->Start), ft.ToSMPTE(current->End), current->Text.get()));
+		file.WriteLineToFile(str(boost::format("%i %s %s %s") % ++i % ft.ToSMPTE(current->Start) % ft.ToSMPTE(current->End) % current->Text));
 }
diff --git a/aegisub/src/subtitle_format_encore.h b/aegisub/src/subtitle_format_encore.h
index 73ce73063739d011cddc4bd5e36fbeef88bf91f7..109e8ea734538f366ec0b7697e05e2768601c6af 100644
--- a/aegisub/src/subtitle_format_encore.h
+++ b/aegisub/src/subtitle_format_encore.h
@@ -37,7 +37,7 @@
 class EncoreSubtitleFormat : public SubtitleFormat {
 public:
 	EncoreSubtitleFormat();
-	wxArrayString GetWriteWildcards() const override;
-	bool CanWriteFile(wxString const& filename) const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const&) const override;
+	std::vector<std::string> GetWriteWildcards() const override;
+	bool CanWriteFile(agi::fs::path const& filename) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const&) const override;
 };
diff --git a/aegisub/src/subtitle_format_microdvd.cpp b/aegisub/src/subtitle_format_microdvd.cpp
index 0468de18a95c59af8fb07d0235af3eb7a3ceb481..bf2756bfe9e86ae51cad715815c66823f9c3c3fc 100644
--- a/aegisub/src/subtitle_format_microdvd.cpp
+++ b/aegisub/src/subtitle_format_microdvd.cpp
@@ -36,8 +36,6 @@
 
 #include "subtitle_format_microdvd.h"
 
-#include <wx/regex.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_time.h"
@@ -45,40 +43,46 @@
 #include "text_file_writer.h"
 #include "video_context.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/of_type_adaptor.h>
+#include <libaegisub/util.h>
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp>
 
 MicroDVDSubtitleFormat::MicroDVDSubtitleFormat()
 : SubtitleFormat("MicroDVD")
 {
 }
 
-wxArrayString MicroDVDSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("sub");
+std::vector<std::string> MicroDVDSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("sub");
 	return formats;
 }
 
-wxArrayString MicroDVDSubtitleFormat::GetWriteWildcards() const {
+std::vector<std::string> MicroDVDSubtitleFormat::GetWriteWildcards() const {
 	return GetReadWildcards();
 }
 
-bool MicroDVDSubtitleFormat::CanReadFile(wxString const& filename) const {
+static const boost::regex line_regex("^[\\{\\[]([0-9]+)[\\}\\]][\\{\\[]([0-9]+)[\\}\\]](.*)$");
+
+bool MicroDVDSubtitleFormat::CanReadFile(agi::fs::path const& filename) const {
 	// Return false immediately if extension is wrong
-	if (filename.Right(4).Lower() != ".sub") return false;
+	if (!agi::fs::HasExtension(filename, "sub")) return false;
 
 	// Since there is an infinity of .sub formats, load first line and check if it's valid
 	TextFileReader file(filename);
-	if (file.HasMoreLines()) {
-		wxRegEx exp("^[\\{\\[]([0-9]+)[\\}\\]][\\{\\[]([0-9]+)[\\}\\]](.*)$", wxRE_ADVANCED);
-		return exp.Matches(file.ReadLineFromFile());
-	}
+	if (file.HasMoreLines())
+		return regex_match(file.ReadLineFromFile(), line_regex);
 
 	return false;
 }
 
-void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
+void MicroDVDSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
 	TextFileReader file(filename, encoding);
-	wxRegEx exp("^[\\{\\[]([0-9]+)[\\}\\]][\\{\\[]([0-9]+)[\\}\\]](.*)$", wxRE_ADVANCED);
 
 	target->LoadDefault(false);
 
@@ -86,24 +90,21 @@ void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename,
 
 	bool isFirst = true;
 	while (file.HasMoreLines()) {
-		wxString line = file.ReadLineFromFile();
-		if (exp.Matches(line)) {
-			long f1, f2;
-			exp.GetMatch(line, 1).ToLong(&f1);
-			exp.GetMatch(line, 2).ToLong(&f2);
-			wxString text = exp.GetMatch(line, 3);
+		boost::smatch match;
+		std::string line = file.ReadLineFromFile();
+		if (regex_match(line, match, line_regex)) {
+			int f1 = boost::lexical_cast<int>(match[0]);
+			int f2 = boost::lexical_cast<int>(match[1]);
+			std::string text = match[2].str();
 
 			// If it's the first, check if it contains fps information
 			if (isFirst) {
 				isFirst = false;
 
-				if (f1 == 1 && f2 == 1) {
-					// Convert fps
-					double cfr;
-					if (text.ToDouble(&cfr)) {
-						fps = cfr;
-						continue;
-					}
+				double cfr;
+				if (agi::util::try_parse(text, &cfr)) {
+					fps = cfr;
+					continue;
 				}
 
 				// If it wasn't an fps line, ask the user for it
@@ -111,7 +112,7 @@ void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename,
 				if (!fps.IsLoaded()) return;
 			}
 
-			text.Replace("|", "\\N");
+			boost::replace_all(text, "|", "\\N");
 
 			AssDialogue *diag = new AssDialogue;
 			diag->Start = fps.TimeAtFrame(f1, agi::vfr::START);
@@ -122,7 +123,7 @@ void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename,
 	}
 }
 
-void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	agi::vfr::Framerate fps = AskForFPS(true, false);
 	if (!fps.IsLoaded()) return;
 
@@ -137,15 +138,14 @@ void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filen
 	TextFileWriter file(filename, encoding);
 
 	// Write FPS line
-	if (!fps.IsVFR()) {
-		file.WriteLineToFile(wxString::Format("{1}{1}%.6f", fps.FPS()));
-	}
+	if (!fps.IsVFR())
+		file.WriteLineToFile(str(boost::format("{1}{1}%.6f") % fps.FPS()));
 
 	// Write lines
 	for (auto current : copy.Line | agi::of_type<AssDialogue>()) {
 		int start = fps.FrameAtTime(current->Start, agi::vfr::START);
 		int end = fps.FrameAtTime(current->End, agi::vfr::END);
 
-		file.WriteLineToFile(wxString::Format("{%i}{%i}%s", start, end, current->Text.get()));
+		file.WriteLineToFile(str(boost::format("{%i}{%i}%s") % start % end % boost::replace_all_copy(current->Text.get(), "\\N", "|")));
 	}
 }
diff --git a/aegisub/src/subtitle_format_microdvd.h b/aegisub/src/subtitle_format_microdvd.h
index 19833808a5eb462d624d3a01d86784ee3634e8f3..96fc4017a724962a34d48a5ac5e3537e999ca9e9 100644
--- a/aegisub/src/subtitle_format_microdvd.h
+++ b/aegisub/src/subtitle_format_microdvd.h
@@ -38,11 +38,11 @@ class MicroDVDSubtitleFormat : public SubtitleFormat {
 public:
 	MicroDVDSubtitleFormat();
 
-	wxArrayString GetReadWildcards() const override;
-	wxArrayString GetWriteWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
+	std::vector<std::string> GetWriteWildcards() const override;
 
-	bool CanReadFile(wxString const& filename) const override;
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
+	bool CanReadFile(agi::fs::path const& filename) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
 
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_mkv.cpp b/aegisub/src/subtitle_format_mkv.cpp
index 99af9b209ad87b316ed66a8886bb6790064f4a04..a4f10a5871cefc030d6b82c73f7d605280b1347f 100644
--- a/aegisub/src/subtitle_format_mkv.cpp
+++ b/aegisub/src/subtitle_format_mkv.cpp
@@ -43,14 +43,14 @@ MKVSubtitleFormat::MKVSubtitleFormat()
 {
 }
 
-wxArrayString MKVSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("mkv");
-	formats.Add("mka");
-	formats.Add("mks");
+std::vector<std::string> MKVSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("mkv");
+	formats.push_back("mka");
+	formats.push_back("mks");
 	return formats;
 }
 
-void MKVSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const&) const {
+void MKVSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const&) const {
 	MatroskaWrapper::GetSubtitles(filename, target);
 }
diff --git a/aegisub/src/subtitle_format_mkv.h b/aegisub/src/subtitle_format_mkv.h
index 1026d07e040ffe7e240711bc12069599e171efe3..31a1630bdd608c2d72e5f292d2ff1f9c29bb1d54 100644
--- a/aegisub/src/subtitle_format_mkv.h
+++ b/aegisub/src/subtitle_format_mkv.h
@@ -37,7 +37,7 @@
 class MKVSubtitleFormat : public SubtitleFormat {
 public:
 	MKVSubtitleFormat();
-	wxArrayString GetReadWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
 
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_srt.cpp b/aegisub/src/subtitle_format_srt.cpp
index 1cd092e7e0efc15bcf955001fead4426efcd067e..fd5112fc5124fb3239172518d5c3941e5c9c2dcc 100644
--- a/aegisub/src/subtitle_format_srt.cpp
+++ b/aegisub/src/subtitle_format_srt.cpp
@@ -40,25 +40,28 @@
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
-#include "colorspace.h"
-#include "compat.h"
 #include "utils.h"
 #include "text_file_reader.h"
 #include "text_file_writer.h"
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/format.hpp>
+#include <boost/regex.hpp>
 #include <map>
-#include <wx/regex.h>
 
 DEFINE_SIMPLE_EXCEPTION(SRTParseError, SubtitleFormatParseError, "subtitle_io/parse/srt")
 
 namespace {
 class SrtTagParser {
 	struct FontAttribs {
-		wxString face;
-		wxString size;
-		wxString color;
+		std::string face;
+		std::string size;
+		std::string color;
 	};
 
 	enum TagType {
@@ -75,20 +78,17 @@ class SrtTagParser {
 		TAG_FONT_CLOSE
 	};
 
-	wxRegEx tag_matcher;
-	wxRegEx attrib_matcher;
-	std::map<wxString,TagType> tag_name_cases;
+	const boost::regex tag_matcher;
+	const boost::regex attrib_matcher;
+	const boost::regex is_quoted;
+	std::map<std::string, TagType> tag_name_cases;
 
 public:
 	SrtTagParser()
-	: tag_matcher("^(.*?)<(/?b|/?i|/?u|/?s|/?font)([^>]*)>(.*)$", wxRE_ICASE|wxRE_ADVANCED)
-	, attrib_matcher("^[[:space:]]+(face|size|color)=('[^']*'|\"[^\"]*\"|[^[:space:]]+)", wxRE_ICASE|wxRE_ADVANCED)
+	: tag_matcher("^(.*?)<(/?b|/?i|/?u|/?s|/?font)([^>]*)>(.*)$", boost::regex::icase)
+	, attrib_matcher("^[[:space:]]+(face|size|color)=('[^']*'|\"[^\"]*\"|[^[:space:]]+)", boost::regex::icase)
+	, is_quoted("^(['\"]).*\\1$")
 	{
-		if (!tag_matcher.IsValid())
-			throw agi::InternalError("Parsing SRT: Failed compiling tag matching regex", 0);
-		if (!attrib_matcher.IsValid())
-			throw agi::InternalError("Parsing SRT: Failed compiling tag attribute matching regex", 0);
-
 		tag_name_cases["b"]  = TAG_BOLD_OPEN;
 		tag_name_cases["/b"] = TAG_BOLD_CLOSE;
 		tag_name_cases["i"]  = TAG_ITALICS_OPEN;
@@ -101,7 +101,7 @@ public:
 		tag_name_cases["/font"] = TAG_FONT_CLOSE;
 	}
 
-	wxString ToAss(wxString srt)
+	std::string ToAss(std::string srt)
 	{
 		int bold_level = 0;
 		int italics_level = 0;
@@ -109,11 +109,12 @@ public:
 		int strikeout_level = 0;
 		std::vector<FontAttribs> font_stack;
 
-		wxString ass; // result to be built
+		std::string ass; // result to be built
 
 		while (!srt.empty())
 		{
-			if (!tag_matcher.Matches(srt))
+			boost::smatch result;
+			if (!regex_match(srt, result, tag_matcher))
 			{
 				// no more tags could be matched, end of string
 				ass.append(srt);
@@ -121,17 +122,18 @@ public:
 			}
 
 			// we found a tag, translate it
-			wxString pre_text  = tag_matcher.GetMatch(srt, 1);
-			wxString tag_name  = tag_matcher.GetMatch(srt, 2);
-			wxString tag_attrs = tag_matcher.GetMatch(srt, 3);
-			wxString post_text = tag_matcher.GetMatch(srt, 4);
+			std::string pre_text  = result.str(1);
+			std::string tag_name  = result.str(2);
+			std::string tag_attrs = result.str(3);
+			std::string post_text = result.str(4);
 
 			// the text before the tag goes through unchanged
 			ass.append(pre_text);
 			// the text after the tag is the input for next iteration
 			srt = post_text;
 
-			switch (tag_name_cases[tag_name.Lower()])
+			boost::to_lower(tag_name);
+			switch (tag_name_cases[tag_name])
 			{
 			case TAG_BOLD_OPEN:
 				if (bold_level == 0)
@@ -184,41 +186,33 @@ public:
 					FontAttribs old_attribs;
 					// start out with any previous ones on stack
 					if (font_stack.size() > 0)
-					{
 						old_attribs = font_stack.back();
-					}
 					new_attribs = old_attribs;
 					// now find all attributes on this font tag
-					while (attrib_matcher.Matches(tag_attrs))
+					boost::smatch result;
+					while (regex_search(tag_attrs, result, attrib_matcher))
 					{
 						// get attribute name and values
-						wxString attr_name = attrib_matcher.GetMatch(tag_attrs, 1);
-						wxString attr_value = attrib_matcher.GetMatch(tag_attrs, 2);
+						std::string attr_name = result.str(1);
+						std::string attr_value = result.str(2);
+
 						// clean them
-						attr_name.MakeLower();
-						if ((attr_value.StartsWith("'") && attr_value.EndsWith("'")) ||
-							(attr_value.StartsWith("\"") && attr_value.EndsWith("\"")))
-						{
-							attr_value = attr_value.Mid(1, attr_value.Len()-2);
-						}
+						boost::to_lower(attr_name);
+						if (regex_match(attr_value, is_quoted))
+							attr_value = attr_value.substr(1, attr_value.size() - 2);
+
 						// handle the attributes
 						if (attr_name == "face")
-						{
-							new_attribs.face = wxString::Format("{\\fn%s}", attr_value);
-						}
+							new_attribs.face = str(boost::format("{\\fn%s}") % attr_value);
 						else if (attr_name == "size")
-						{
-							new_attribs.size = wxString::Format("{\\fs%s}", attr_value);
-						}
+							new_attribs.size = str(boost::format("{\\fs%s}") % attr_value);
 						else if (attr_name == "color")
-						{
-							new_attribs.color = wxString::Format("{\\c%s}", to_wx(agi::Color(from_wx(attr_value)).GetAssOverrideFormatted()));
-						}
+							new_attribs.color = str(boost::format("{\\c%s}") % agi::Color(attr_value).GetAssOverrideFormatted());
+
 						// remove this attribute to prepare for the next
-						size_t attr_pos, attr_len;
-						attrib_matcher.GetMatch(&attr_pos, &attr_len, 0);
-						tag_attrs.erase(attr_pos, attr_len);
+						tag_attrs = result.suffix().str();
 					}
+
 					// the attributes changed from old are then written out
 					if (new_attribs.face != old_attribs.face)
 						ass.append(new_attribs.face);
@@ -226,6 +220,7 @@ public:
 						ass.append(new_attribs.size);
 					if (new_attribs.color != old_attribs.color)
 						ass.append(new_attribs.color);
+
 					// lastly dump the new attributes state onto the stack
 					font_stack.push_back(new_attribs);
 				}
@@ -275,13 +270,13 @@ public:
 		}
 
 		// make it a little prettier, join tag groups
-		ass.Replace("}{", "", true);
+		boost::replace_all(ass, "}{", "");
 
 		return ass;
 	}
 };
 
-AssTime ReadSRTTime(wxString const& ts)
+AssTime ReadSRTTime(std::string const& ts)
 {
 	// For the sake of your sanity, please do not read this function.
 
@@ -291,22 +286,12 @@ AssTime ReadSRTTime(wxString const& ts)
 	size_t ci = 0;
 	int ms_chars = 0;
 
-	for (; ci < ts.length(); ++ci)
+	for (; ci < ts.size(); ++ci)
 	{
-		char ch = ts[ci];
-		switch (ch)
+		switch (ts[ci])
 		{
-		case '0':
-		case '1':
-		case '2':
-		case '3':
-		case '4':
-		case '5':
-		case '6':
-		case '7':
-		case '8':
-		case '9':
-			s = s * 10 + (ch - '0');
+		case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+			s = s * 10 + (ts[ci] - '0');
 			break;
 		case ':':
 			d = h;
@@ -318,43 +303,27 @@ AssTime ReadSRTTime(wxString const& ts)
 			ci++;
 			goto milliseconds;
 		default:
-			goto allparsed;
+			ci = ts.size();
 		}
 	}
-	goto allparsed;
+
 milliseconds:
-	for (; ci < ts.length(); ++ci)
+	for (; ci < ts.size(); ++ci)
 	{
-		char ch = ts[ci];
-		switch (ch)
-		{
-		case '0':
-		case '1':
-		case '2':
-		case '3':
-		case '4':
-		case '5':
-		case '6':
-		case '7':
-		case '8':
-		case '9':
-			ms = ms * 10 + (ch - '0');
-			ms_chars++;
-			break;
-		default:
-			goto allparsed;
-		}
+		if (!isdigit(ts[ci])) break;
+
+		ms = ms * 10 + (ts[ci] - '0');
+		ms_chars++;
 	}
-allparsed:
-	while (ms_chars < 3) ms *= 10, ms_chars++;
-	while (ms_chars > 3) ms /= 10, ms_chars--;
+
+	ms *= pow(10, 3 - ms_chars);
 
 	return ms + 1000*(s + 60*(m + 60*(h + d*24)));
 }
 
-wxString WriteSRTTime(AssTime const& ts)
+std::string WriteSRTTime(AssTime const& ts)
 {
-	return wxString::Format("%02d:%02d:%02d,%03d", ts.GetTimeHours(), ts.GetTimeMinutes(), ts.GetTimeSeconds(), ts.GetTimeMiliseconds());
+	return str(boost::format("%02d:%02d:%02d,%03d") % ts.GetTimeHours() % ts.GetTimeMinutes() % ts.GetTimeSeconds() % ts.GetTimeMiliseconds());
 }
 
 }
@@ -364,13 +333,13 @@ SRTSubtitleFormat::SRTSubtitleFormat()
 {
 }
 
-wxArrayString SRTSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("srt");
+std::vector<std::string> SRTSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("srt");
 	return formats;
 }
 
-wxArrayString SRTSubtitleFormat::GetWriteWildcards() const {
+std::vector<std::string> SRTSubtitleFormat::GetWriteWildcards() const {
 	return GetReadWildcards();
 }
 
@@ -382,7 +351,7 @@ enum ParseState {
 	STATE_LAST_WAS_BLANK
 };
 
-void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
+void SRTSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
 	using namespace std;
 
 	TextFileReader file(filename, encoding);
@@ -391,9 +360,7 @@ void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt
 	// See parsing algorithm at <http://devel.aegisub.org/wiki/SubtitleFormats/SRT>
 
 	// "hh:mm:ss,fff --> hh:mm:ss,fff" (e.g. "00:00:04,070 --> 00:00:10,04")
-	wxRegEx timestamp_regex("^([0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{1,}) --> ([0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{1,})");
-	if (!timestamp_regex.IsValid())
-		throw agi::InternalError("Parsing SRT: Failed compiling regex", 0);
+	const boost::regex timestamp_regex("^([0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{1,}) --> ([0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{1,})");
 
 	SrtTagParser tag_parser;
 
@@ -401,43 +368,47 @@ void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt
 	int line_num = 0;
 	int linebreak_debt = 0;
 	AssDialogue *line = 0;
-	wxString text;
+	std::string text;
 	while (file.HasMoreLines()) {
-		wxString text_line = file.ReadLineFromFile();
-		line_num++;
-		text_line.Trim(true).Trim(false);
+		std::string text_line = file.ReadLineFromFile();
+		++line_num;
+		boost::trim(text_line);
 
+		boost::smatch timestamp_match;
 		switch (state) {
 			case STATE_INITIAL:
 				// ignore leading blank lines
 				if (text_line.empty()) break;
-				if (text_line.IsNumber()) {
+				if (all(text_line, boost::is_digit())) {
 					// found the line number, throw it away and hope for timestamps
 					state = STATE_TIMESTAMP;
 					break;
 				}
-				if (timestamp_regex.Matches(text_line))
+				if (regex_match(text_line, timestamp_match, timestamp_regex))
 					goto found_timestamps;
 
-				throw SRTParseError(from_wx(wxString::Format("Parsing SRT: Expected subtitle index at line %d", line_num)), 0);
+				throw SRTParseError(str(boost::format("Parsing SRT: Expected subtitle index at line %d") % line_num), 0);
+
 			case STATE_TIMESTAMP:
-				if (!timestamp_regex.Matches(text_line))
-					throw SRTParseError(from_wx(wxString::Format("Parsing SRT: Expected timestamp pair at line %d", line_num)), 0);
+				if (!regex_match(text_line, timestamp_match, timestamp_regex))
+					throw SRTParseError(str(boost::format("Parsing SRT: Expected timestamp pair at line %d") % line_num), 0);
 found_timestamps:
 				if (line) {
 					// finalize active line
 					line->Text = tag_parser.ToAss(text);
 					text.clear();
 				}
+
 				// create new subtitle
 				line = new AssDialogue;
-				line->Start = ReadSRTTime(timestamp_regex.GetMatch(text_line, 1));
-				line->End = ReadSRTTime(timestamp_regex.GetMatch(text_line, 2));
+				line->Start = ReadSRTTime(timestamp_match.str(1));
+				line->End = ReadSRTTime(timestamp_match.str(2));
 				// store pointer to subtitle, we'll continue working on it
 				target->Line.push_back(*line);
 				// next we're reading the text
 				state = STATE_FIRST_LINE_OF_BODY;
 				break;
+
 			case STATE_FIRST_LINE_OF_BODY:
 				if (text_line.empty()) {
 					// that's not very interesting... blank subtitle?
@@ -446,9 +417,10 @@ found_timestamps:
 					linebreak_debt = 0;
 					break;
 				}
-				text.Append(text_line);
+				text.append(text_line);
 				state = STATE_REST_OF_BODY;
 				break;
+
 			case STATE_REST_OF_BODY:
 				if (text_line.empty()) {
 					// Might be either the gap between two subtitles or just a
@@ -458,25 +430,27 @@ found_timestamps:
 					linebreak_debt = 1;
 					break;
 				}
-				text.Append("\\N").Append(text_line);
+				text.append("\\N");
+				text.append(text_line);
 				break;
+
 			case STATE_LAST_WAS_BLANK:
 				++linebreak_debt;
 				if (text_line.empty()) break;
-				if (text_line.IsNumber()) {
+				if (all(text_line, boost::is_digit())) {
 					// Hopefully it's the start of a new subtitle, and the
 					// previous blank line(s) were the gap between subtitles
 					state = STATE_TIMESTAMP;
 					break;
 				}
-				if (timestamp_regex.Matches(text_line))
+				if (regex_match(text_line, timestamp_match, timestamp_regex))
 					goto found_timestamps;
 
 				// assume it's a continuation of the subtitle text
 				// resolve our line break debt and append the line text
 				while (linebreak_debt-- > 0)
-					text.Append("\\N");
-				text.Append(text_line);
+					text.append("\\N");
+				text.append(text_line);
 				state = STATE_REST_OF_BODY;
 				break;
 		}
@@ -485,12 +459,11 @@ found_timestamps:
 	if (state == 1 || state == 2)
 		throw SRTParseError("Parsing SRT: Incomplete file", 0);
 
-	if (line)
-		// an unfinalized line
+	if (line) // an unfinalized line
 		line->Text = tag_parser.ToAss(text);
 }
 
-void SRTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void SRTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	TextFileWriter file(filename, encoding);
 
 	// Convert to SRT
@@ -508,7 +481,7 @@ void SRTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename,
 	// Write lines
 	int i=0;
 	for (auto current : copy.Line | agi::of_type<AssDialogue>()) {
-		file.WriteLineToFile(wxString::Format("%d", ++i));
+		file.WriteLineToFile(std::to_string(++i));
 		file.WriteLineToFile(WriteSRTTime(current->Start) + " --> " + WriteSRTTime(current->End));
 		file.WriteLineToFile(ConvertTags(current));
 		file.WriteLineToFile("");
@@ -545,8 +518,8 @@ bool SRTSubtitleFormat::CanSave(const AssFile *file) const {
 	return true;
 }
 
-wxString SRTSubtitleFormat::ConvertTags(const AssDialogue *diag) const {
-	wxString final;
+std::string SRTSubtitleFormat::ConvertTags(const AssDialogue *diag) const {
+	std::string final;
 	std::map<char, bool> tag_states;
 	tag_states['i'] = false;
 	tag_states['b'] = false;
@@ -564,25 +537,24 @@ wxString SRTSubtitleFormat::ConvertTags(const AssDialogue *diag) const {
 					if (it != tag_states.end()) {
 						bool temp = tag.Params[0].Get(false);
 						if (temp && !it->second)
-							final += wxString::Format("<%c>", it->first);
+							final += str(boost::format("<%c>") % it->first);
 						if (!temp && it->second)
-							final += wxString::Format("</%c>", it->first);
+							final += str(boost::format("</%c>") % it->first);
 						it->second = temp;
 					}
 				}
 			}
 		}
 		// Plain text
-		else if (AssDialogueBlockPlain *plain = dynamic_cast<AssDialogueBlockPlain*>(&block)) {
-			final += to_wx(plain->GetText());
-		}
+		else if (AssDialogueBlockPlain *plain = dynamic_cast<AssDialogueBlockPlain*>(&block))
+			final += plain->GetText();
 	}
 
 	// Ensure all tags are closed
 	// Otherwise unclosed overrides might affect lines they shouldn't, see bug #809 for example
 	for (auto it : tag_states) {
 		if (it.second)
-			final += wxString::Format("</%c>", it.first);
+			final += str(boost::format("</%c>") % it.first);
 	}
 
 	return final;
diff --git a/aegisub/src/subtitle_format_srt.h b/aegisub/src/subtitle_format_srt.h
index f47a908bd557b3aaecb964069c8b20a4e7e943e9..70bb027eaec7cf63f8af4b3178cd621649f995ef 100644
--- a/aegisub/src/subtitle_format_srt.h
+++ b/aegisub/src/subtitle_format_srt.h
@@ -37,14 +37,14 @@
 class AssDialogue;
 
 class SRTSubtitleFormat : public SubtitleFormat {
-	wxString ConvertTags(const AssDialogue *diag) const;
+	std::string ConvertTags(const AssDialogue *diag) const;
 public:
 	SRTSubtitleFormat();
-	wxArrayString GetReadWildcards() const override;
-	wxArrayString GetWriteWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
+	std::vector<std::string> GetWriteWildcards() const override;
 
 	bool CanSave(const AssFile *file) const override;
 
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_transtation.cpp b/aegisub/src/subtitle_format_transtation.cpp
index 732fee17261512c22819c34d2aa12290546686ab..6f38f7e0953cac0e91733df77a0c6c7e54157ace 100644
--- a/aegisub/src/subtitle_format_transtation.cpp
+++ b/aegisub/src/subtitle_format_transtation.cpp
@@ -42,27 +42,29 @@
 #include "ass_file.h"
 #include "ass_style.h"
 #include "ass_time.h"
-#include "compat.h"
 #include "text_file_writer.h"
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+
 TranStationSubtitleFormat::TranStationSubtitleFormat()
 : SubtitleFormat("TranStation")
 {
 }
 
-wxArrayString TranStationSubtitleFormat::GetWriteWildcards() const {
-	wxArrayString formats;
-	formats.Add("transtation.txt");
+std::vector<std::string> TranStationSubtitleFormat::GetWriteWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("transtation.txt");
 	return formats;
 }
 
-bool TranStationSubtitleFormat::CanWriteFile(wxString const& filename) const {
-	return filename.Lower().EndsWith(".transtation.txt");
+bool TranStationSubtitleFormat::CanWriteFile(agi::fs::path const& filename) const {
+	return boost::iends_with(filename.string(), ".transtation.txt");
 }
 
-void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void TranStationSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	agi::vfr::Framerate fps = AskForFPS(false, true);
 	if (!fps.IsLoaded()) return;
 
@@ -95,11 +97,11 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& fi
 	file.WriteLineToFile("SUB[");
 }
 
-wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const {
+std::string TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const {
 	int valign = 0;
 	const char *halign = " "; // default is centered
 	const char *type = "N"; // no special style
-	if (AssStyle *style = file->GetStyle(from_wx(current->Style))) {
+	if (AssStyle *style = file->GetStyle(current->Style)) {
 		if (style->alignment >= 4) valign = 4;
 		if (style->alignment >= 7) valign = 9;
 		if (style->alignment == 1 || style->alignment == 4 || style->alignment == 7) halign = "L";
@@ -109,7 +111,7 @@ wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *curr
 
 	// Hack: If an italics-tag (\i1) appears anywhere in the line,
 	// make it all italics
-	if (current->Text.get().Find("\\i1") != wxNOT_FOUND) type = "I";
+	if (current->Text.get().find("\\i1") != std::string::npos) type = "I";
 
 	// Write header
 	AssTime end = current->End;
@@ -120,6 +122,6 @@ wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *curr
 	if (nextl_start > 0 && end == nextl_start)
 		end = fps.TimeAtFrame(fps.FrameAtTime(end, agi::vfr::END) - 1, agi::vfr::END);
 
-	wxString header = wxString::Format("SUB[%i%s%s ", valign, halign, type) + ft.ToSMPTE(current->Start) + ">" + ft.ToSMPTE(end) + "]\r\n";
-	return header + current->Text;
+	std::string header = str(boost::format("SUB[%i%s%s %s>%s]\r\n") % valign % halign % type % ft.ToSMPTE(current->Start) % ft.ToSMPTE(end));
+	return header + current->Text.get();
 }
diff --git a/aegisub/src/subtitle_format_transtation.h b/aegisub/src/subtitle_format_transtation.h
index f75bd9f1dc4f59f37ca30b75776dd386f31951b8..6b951e34b0cd63a759b9e5bf16c8a5669e02ce05 100644
--- a/aegisub/src/subtitle_format_transtation.h
+++ b/aegisub/src/subtitle_format_transtation.h
@@ -38,11 +38,11 @@ class AssDialogue;
 class SmpteFormatter;
 
 class TranStationSubtitleFormat : public SubtitleFormat {
-	wxString ConvertLine(AssFile *file, AssDialogue *line, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const;
+	std::string ConvertLine(AssFile *file, AssDialogue *line, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const;
 
 public:
 	TranStationSubtitleFormat();
-	bool CanWriteFile(wxString const& filename) const override;
-	wxArrayString GetWriteWildcards() const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	bool CanWriteFile(agi::fs::path const& filename) const override;
+	std::vector<std::string> GetWriteWildcards() const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_ttxt.cpp b/aegisub/src/subtitle_format_ttxt.cpp
index ea5d41998e782b247c29f7d7a71869d622d794c4..da2365c0ef80dd9d9d597de2999372f5a253d180 100644
--- a/aegisub/src/subtitle_format_ttxt.cpp
+++ b/aegisub/src/subtitle_format_ttxt.cpp
@@ -54,22 +54,22 @@ TTXTSubtitleFormat::TTXTSubtitleFormat()
 {
 }
 
-wxArrayString TTXTSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("ttxt");
+std::vector<std::string> TTXTSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("ttxt");
 	return formats;
 }
 
-wxArrayString TTXTSubtitleFormat::GetWriteWildcards() const {
+std::vector<std::string> TTXTSubtitleFormat::GetWriteWildcards() const {
 	return GetReadWildcards();
 }
 
-void TTXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
+void TTXTSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
 	target->LoadDefault(false);
 
 	// Load XML document
 	wxXmlDocument doc;
-	if (!doc.Load(filename)) throw TTXTParseError("Failed loading TTXT XML file.", 0);
+	if (!doc.Load(filename.wstring())) throw TTXTParseError("Failed loading TTXT XML file.", 0);
 
 	// Check root node name
 	if (doc.GetRoot()->GetName() != "TextStream") throw TTXTParseError("Invalid TTXT file.", 0);
@@ -109,7 +109,7 @@ void TTXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxS
 AssDialogue *TTXTSubtitleFormat::ProcessLine(wxXmlNode *node, AssDialogue *prev, int version) const {
 	// Get time
 	wxString sampleTime = node->GetAttribute("sampleTime", "00:00:00.000");
-	AssTime time(sampleTime);
+	AssTime time(from_wx(sampleTime));
 
 	// Set end time of last line
 	if (prev)
@@ -144,14 +144,14 @@ AssDialogue *TTXTSubtitleFormat::ProcessLine(wxXmlNode *node, AssDialogue *prev,
 			}
 			else if (in) finalText += chr;
 		}
-		diag->Text = finalText;
+		diag->Text = from_wx(finalText);
 	}
 
 	// Process text for 1.1
 	else {
 		text.Replace("\r", "");
 		text.Replace("\n", "\\N");
-		diag->Text = text;
+		diag->Text = from_wx(text);
 	}
 
 	return diag;
@@ -161,7 +161,7 @@ void TTXTSubtitleFormat::ProcessHeader(wxXmlNode *node) const {
 	// TODO
 }
 
-void TTXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void TTXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	// Convert to TTXT
 	AssFile copy(*src);
 	ConvertToTTXT(copy);
@@ -183,7 +183,7 @@ void TTXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename,
 	}
 
 	// Save XML
-	doc.Save(filename);
+	doc.Save(filename.wstring());
 }
 
 void TTXTSubtitleFormat::WriteHeader(wxXmlNode *root) const {
@@ -240,7 +240,7 @@ void TTXTSubtitleFormat::WriteLine(wxXmlNode *root, const AssDialogue *prev, con
 	// If it doesn't start at the end of previous, add blank
 	if (prev && prev->End != line->Start) {
 		wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, "TextSample");
-		node->AddAttribute("sampleTime", "0" + prev->End.GetAssFormated(true));
+		node->AddAttribute("sampleTime", to_wx("0" + prev->End.GetAssFormated(true)));
 		node->AddAttribute("xml:space", "preserve");
 		root->AddChild(node);
 		node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, "", ""));
@@ -248,10 +248,10 @@ void TTXTSubtitleFormat::WriteLine(wxXmlNode *root, const AssDialogue *prev, con
 
 	// Generate and insert node
 	wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, "TextSample");
-	node->AddAttribute("sampleTime", "0" + line->Start.GetAssFormated(true));
+	node->AddAttribute("sampleTime", to_wx("0" + line->Start.GetAssFormated(true)));
 	node->AddAttribute("xml:space", "preserve");
 	root->AddChild(node);
-	node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, "", line->Text));
+	node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, "", to_wx(line->Text)));
 }
 
 void TTXTSubtitleFormat::ConvertToTTXT(AssFile &file) const {
diff --git a/aegisub/src/subtitle_format_ttxt.h b/aegisub/src/subtitle_format_ttxt.h
index fc57744adbc241c53be4f4c4d92afa43c582456e..111a87b187e82d01f87bb639e2c64deaa8aeff54 100644
--- a/aegisub/src/subtitle_format_ttxt.h
+++ b/aegisub/src/subtitle_format_ttxt.h
@@ -48,9 +48,9 @@ class TTXTSubtitleFormat : public SubtitleFormat {
 
 public:
 	TTXTSubtitleFormat();
-	wxArrayString GetReadWildcards() const override;
-	wxArrayString GetWriteWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
+	std::vector<std::string> GetWriteWildcards() const override;
 
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitle_format_txt.cpp b/aegisub/src/subtitle_format_txt.cpp
index 2966a0e6657fc4a60576e8eea4ee5d136347dfc9..69bed006d9b3f37cf323c56a36080636ca9f65d8 100644
--- a/aegisub/src/subtitle_format_txt.cpp
+++ b/aegisub/src/subtitle_format_txt.cpp
@@ -38,7 +38,6 @@
 
 #include "ass_dialogue.h"
 #include "ass_file.h"
-#include "compat.h"
 #include "dialog_text_import.h"
 #include "options.h"
 #include "text_file_reader.h"
@@ -48,27 +47,30 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
 TXTSubtitleFormat::TXTSubtitleFormat()
 : SubtitleFormat("Plain-Text")
 {
 }
 
-wxArrayString TXTSubtitleFormat::GetReadWildcards() const {
-	wxArrayString formats;
-	formats.Add("txt");
+std::vector<std::string> TXTSubtitleFormat::GetReadWildcards() const {
+	std::vector<std::string> formats;
+	formats.push_back("txt");
 	return formats;
 }
 
-wxArrayString TXTSubtitleFormat::GetWriteWildcards() const {
+std::vector<std::string> TXTSubtitleFormat::GetWriteWildcards() const {
 	return GetReadWildcards();
 }
 
-bool TXTSubtitleFormat::CanWriteFile(wxString const& filename) const {
-	return (filename.Right(4).Lower() == ".txt" && filename.Right(11).Lower() != ".encore.txt" && filename.Right(16).Lower() != ".transtation.txt");
+bool TXTSubtitleFormat::CanWriteFile(agi::fs::path const& filename) const {
+	auto str = filename.string();
+	return boost::iends_with(str, ".txt") && !(boost::iends_with(str, ".encore.txt") || boost::iends_with(str, ".transtation.txt"));
 }
 
-void TXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxString const& encoding) const {
-	using namespace std;
+void TXTSubtitleFormat::ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& encoding) const {
 	DialogTextImport dlg;
 	if (dlg.ShowModal() == wxID_CANCEL) return;
 
@@ -76,58 +78,56 @@ void TXTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt
 
 	target->LoadDefault(false);
 
-	wxString actor;
-	wxString separator = to_wx(OPT_GET("Tool/Import/Text/Actor Separator")->GetString());
-	wxString comment = to_wx(OPT_GET("Tool/Import/Text/Comment Starter")->GetString());
+	std::string actor;
+	std::string separator = OPT_GET("Tool/Import/Text/Actor Separator")->GetString();
+	std::string comment = OPT_GET("Tool/Import/Text/Comment Starter")->GetString();
 
 	// Parse file
 	while (file.HasMoreLines()) {
-		wxString value = file.ReadLineFromFile();
+		std::string value = file.ReadLineFromFile();
 		if(value.empty()) continue;
 
 		// Check if this isn't a timecodes file
-		if (value.StartsWith("# timecode"))
+		if (boost::starts_with(value, "# timecode"))
 			throw SubtitleFormatParseError("File is a timecode file, cannot load as subtitles.", 0);
 
 		// Read comment data
 		bool isComment = false;
-		if (!comment.empty() && value.StartsWith(comment)) {
+		if (!comment.empty() && boost::starts_with(value, comment)) {
 			isComment = true;
-			value = value.Mid(comment.size());
+			value.erase(0, comment.size());
 		}
 
 		// Read actor data
 		if (!isComment && !separator.empty()) {
 			if (value[0] != ' ' && value[0] != '\t') {
-				int pos = value.Find(separator);
-				if (pos != wxNOT_FOUND) {
-					actor = value.Left(pos);
-					actor.Trim(false);
-					actor.Trim(true);
-					value = value.Mid(pos+1);
+				size_t pos = value.find(separator);
+				if (pos != std::string::npos) {
+					actor = value.substr(0, pos);
+					boost::trim(actor);
+					value.erase(0, pos);
 				}
 			}
 		}
 
 		// Trim spaces at start
-		value.Trim(false);
+		boost::trim_left(value);
 
 		if (value.empty())
 			isComment = true;
 
 		// Sets line up
 		AssDialogue *line = new AssDialogue;
-		line->Actor = isComment ? wxString() : actor;
+		line->Actor = isComment ? std::string() : actor;
 		line->Comment = isComment;
 		line->Text = value;
 		line->End = 0;
 
-		// Adds line
 		target->Line.push_back(*line);
 	}
 }
 
-void TXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
+void TXTSubtitleFormat::WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const {
 	size_t num_actor_names = 0, num_dialogue_lines = 0;
 
 	// Detect number of lines with Actor field filled out
@@ -144,19 +144,19 @@ void TXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename,
 	bool strip_formatting = true;
 
 	TextFileWriter file(filename, encoding);
-	file.WriteLineToFile(wxString("# Exported by Aegisub ") + GetAegisubShortVersionString());
+	file.WriteLineToFile(std::string("# Exported by Aegisub ") + GetAegisubShortVersionString());
 
 	// Write the file
 	for (auto dia : src->Line | agi::of_type<AssDialogue>()) {
-		wxString out_line;
+		std::string out_line;
 
 		if (dia->Comment)
 			out_line = "# ";
 
 		if (write_actors)
-			out_line += dia->Actor + ": ";
+			out_line += dia->Actor.get() + ": ";
 
-		wxString out_text = strip_formatting ? dia->GetStrippedText() : dia->Text;
+		std::string out_text = strip_formatting ? dia->GetStrippedText() : dia->Text;
 		out_line += out_text;
 
 		if (!out_text.empty())
diff --git a/aegisub/src/subtitle_format_txt.h b/aegisub/src/subtitle_format_txt.h
index 6a9b7d1ef58340059e993dec4b33809b5bb96f92..0c6c103599d06a131b081255279f522f741958cf 100644
--- a/aegisub/src/subtitle_format_txt.h
+++ b/aegisub/src/subtitle_format_txt.h
@@ -37,13 +37,13 @@
 class TXTSubtitleFormat : public SubtitleFormat {
 public:
 	TXTSubtitleFormat();
-	wxArrayString GetReadWildcards() const override;
-	wxArrayString GetWriteWildcards() const override;
+	std::vector<std::string> GetReadWildcards() const override;
+	std::vector<std::string> GetWriteWildcards() const override;
 
 	// TXT format supports so little that it should always require an export
 	bool CanSave(const AssFile*) const override { return false; }
 
-	bool CanWriteFile(wxString const& filename) const override;
-	void ReadFile(AssFile *target, wxString const& filename, wxString const& forceEncoding) const override;
-	void WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const override;
+	bool CanWriteFile(agi::fs::path const& filename) const override;
+	void ReadFile(AssFile *target, agi::fs::path const& filename, std::string const& forceEncoding) const override;
+	void WriteFile(const AssFile *src, agi::fs::path const& filename, std::string const& encoding) const override;
 };
diff --git a/aegisub/src/subtitles_provider.cpp b/aegisub/src/subtitles_provider.cpp
index 16345143339b90df7dc29d1bdf93ffe29b7c9956..8bd313739ebb9b3e2d818a0515ab6128b7b315ea 100644
--- a/aegisub/src/subtitles_provider.cpp
+++ b/aegisub/src/subtitles_provider.cpp
@@ -34,24 +34,16 @@
 
 #include "config.h"
 
-#include "compat.h"
 #include "options.h"
-#ifdef WITH_CSRI
 #include "subtitles_provider_csri.h"
-#endif
-#ifdef WITH_LIBASS
 #include "subtitles_provider_libass.h"
-#endif
-#if !defined(WITH_CSRI) && !defined(WITH_LIBASS)
 #include "include/aegisub/subtitles_provider.h"
-#endif
 
 SubtitlesProvider* SubtitlesProviderFactory::GetProvider() {
 	std::vector<std::string> list = GetClasses(OPT_GET("Subtitle/Provider")->GetString());
-	if (list.empty()) throw wxString("No subtitle providers are available.");
+	if (list.empty()) throw std::string("No subtitle providers are available.");
 
-	// Get provider
-	wxString error;
+	std::string error;
 	for (auto const& factory : list) {
 		try {
 			size_t pos = factory.find('/');
@@ -60,8 +52,8 @@ SubtitlesProvider* SubtitlesProviderFactory::GetProvider() {
 			if (provider) return provider;
 		}
 		catch (agi::UserCancelException const&) { throw; }
-		catch (wxString const& err) { error += factory + " factory: " + err + "\n"; }
-		catch (const char *err) { error += factory + " factory: " + wxString(err) + "\n"; }
+		catch (std::string const& err) { error += factory + " factory: " + err + "\n"; }
+		catch (const char *err) { error += factory + " factory: " + std::string(err) + "\n"; }
 		catch (...) { error += factory + " factory: Unknown error\n"; }
 	}
 
diff --git a/aegisub/src/subtitles_provider_csri.cpp b/aegisub/src/subtitles_provider_csri.cpp
index e6c6a44beee5258bf596fe572b2c48ade813bead..90c557b1200769ebc71444e79848d355a796585d 100644
--- a/aegisub/src/subtitles_provider_csri.cpp
+++ b/aegisub/src/subtitles_provider_csri.cpp
@@ -35,16 +35,18 @@
 #include "config.h"
 
 #ifdef WITH_CSRI
-
-#include <wx/thread.h>
-
 #include "subtitles_provider_csri.h"
 
 #include "ass_file.h"
-#include "text_file_writer.h"
+#include "standard_paths.h"
 #include "video_context.h"
 #include "video_frame.h"
 
+#include <libaegisub/fs.h>
+
+#include <boost/filesystem.hpp>
+#include <mutex>
+
 #ifdef WIN32
 #define CSRIAPI
 #include "../../contrib/csri/include/csri/csri.h"
@@ -52,13 +54,15 @@
 #include <csri/csri.h>
 #endif
 
-static wxMutex csri_mutex;
+// CSRI renderers are not required to be thread safe (and VSFilter very much
+// is not)
+static std::mutex csri_mutex;
 
 CSRISubtitlesProvider::CSRISubtitlesProvider(std::string type)
 : can_open_mem(true)
-, instance(0, csri_close)
+, instance(nullptr, csri_close)
 {
-	wxMutexLocker lock(csri_mutex);
+	std::lock_guard<std::mutex> lock(csri_mutex);
 
 	for (csri_rend *cur = csri_renderer_default(); cur; cur = csri_renderer_next(cur)) {
 		if (type == csri_renderer_info(cur)->name) {
@@ -75,7 +79,7 @@ CSRISubtitlesProvider::CSRISubtitlesProvider(std::string type)
 }
 
 CSRISubtitlesProvider::~CSRISubtitlesProvider() {
-	if (!tempfile.empty()) wxRemoveFile(tempfile);
+	agi::fs::Remove(tempfile);
 }
 
 void CSRISubtitlesProvider::LoadSubtitles(AssFile *subs) {
@@ -83,28 +87,22 @@ void CSRISubtitlesProvider::LoadSubtitles(AssFile *subs) {
 		std::vector<char> data;
 		subs->SaveMemory(data);
 
-		wxMutexLocker lock(csri_mutex);
+		std::lock_guard<std::mutex> lock(csri_mutex);
 		instance = csri_open_mem(renderer, &data[0], data.size(), nullptr);
 	}
-	// Open from disk
 	else {
-		if (tempfile.empty()) {
-			tempfile = wxFileName::CreateTempFileName("aegisub");
-			wxRemoveFile(tempfile);
-			tempfile += ".ass";
-		}
-		subs->Save(tempfile, false, false, wxSTRING_ENCODING);
+		if (tempfile.empty())
+			tempfile = unique_path(StandardPaths::DecodePath("?temp/csri-%%%%-%%%%-%%%%-%%%%.ass"));
+		subs->Save(tempfile, false, false, "utf-8");
 
-		wxMutexLocker lock(csri_mutex);
-		instance = csri_open_file(renderer, tempfile.utf8_str(), nullptr);
+		std::lock_guard<std::mutex> lock(csri_mutex);
+		instance = csri_open_file(renderer, tempfile.string().c_str(), nullptr);
 	}
 }
 
 void CSRISubtitlesProvider::DrawSubtitles(AegiVideoFrame &dst,double time) {
-	// Check if CSRI loaded properly
 	if (!instance) return;
 
-	// Load data into frame
 	csri_frame frame;
 	if (dst.flipped) {
 		frame.planes[0] = dst.data + (dst.h-1) * dst.pitch;
@@ -116,18 +114,11 @@ void CSRISubtitlesProvider::DrawSubtitles(AegiVideoFrame &dst,double time) {
 	}
 	frame.pixfmt = CSRI_F_BGR_;
 
-	// Set format
-	csri_fmt format;
-	format.width = dst.w;
-	format.height = dst.h;
-	format.pixfmt = frame.pixfmt;
-
-	wxMutexLocker lock(csri_mutex);
-	int error = csri_request_fmt(instance, &format);
-	if (error) return;
+	csri_fmt format = { frame.pixfmt, dst.w, dst.h };
 
-	// Render
-	csri_render(instance, &frame, time);
+	std::lock_guard<std::mutex> lock(csri_mutex);
+	if (!csri_request_fmt(instance, &format))
+		csri_render(instance, &frame, time);
 }
 
 std::vector<std::string> CSRISubtitlesProvider::GetSubTypes() {
diff --git a/aegisub/src/subtitles_provider_csri.h b/aegisub/src/subtitles_provider_csri.h
index 6f460a67085d59023079d9e97bf2ffe3580e23e1..588f6d2cd7278761cf2cc34a9d12c01e91daa38c 100644
--- a/aegisub/src/subtitles_provider_csri.h
+++ b/aegisub/src/subtitles_provider_csri.h
@@ -33,13 +33,15 @@
 ///
 
 #ifdef WITH_CSRI
-
 #include "include/aegisub/subtitles_provider.h"
 
 #include <vector>
 
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/scoped_ptr.h>
 
+#include <boost/filesystem/path.hpp>
+
 typedef void csri_rend;
 typedef void csri_inst;
 
@@ -53,13 +55,13 @@ class CSRISubtitlesProvider : public SubtitlesProvider {
 	bool can_open_mem;
 
 	/// Name of the file passed to renderers with can_open_mem false
-	wxString tempfile;
+	agi::fs::path tempfile;
 public:
 	CSRISubtitlesProvider(std::string subType);
 	~CSRISubtitlesProvider();
 
 	void LoadSubtitles(AssFile *subs);
-	void DrawSubtitles(AegiVideoFrame &dst,double time);
+	void DrawSubtitles(AegiVideoFrame &dst, double time);
 
 	static std::vector<std::string> GetSubTypes();
 };
diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp
index 69fb43f659229a2a1d1626fe1e54903bbaf841a2..8daef66556025cb8c3c98cc1af9e2f80042e5f32 100644
--- a/aegisub/src/subtitles_provider_libass.cpp
+++ b/aegisub/src/subtitles_provider_libass.cpp
@@ -35,28 +35,33 @@
 #include "config.h"
 
 #ifdef WITH_LIBASS
-
-#include <wx/filefn.h>
-#include <wx/utils.h>
+#include "subtitles_provider_libass.h"
 
 #ifdef __APPLE__
 #include <sys/param.h>
 #include <libaegisub/util_osx.h>
 #endif
 
-#include <libaegisub/log.h>
-
 #include "ass_file.h"
 #include "dialog_progress.h"
 #include "frame_main.h"
 #include "main.h"
-#include "standard_paths.h"
-#include "subtitles_provider_libass.h"
 #include "utils.h"
-#include "video_context.h"
 #include "video_frame.h"
 
-static void msg_callback(int level, const char *fmt, va_list args, void *) {
+#include <libaegisub/dispatch.h>
+#include <libaegisub/log.h>
+
+#include <boost/gil/gil_all.hpp>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+namespace {
+std::unique_ptr<agi::dispatch::Queue> cache_queue;
+ASS_Library *library;
+
+void msg_callback(int level, const char *fmt, va_list args, void *) {
 	if (level >= 7) return;
 	char buf[1024];
 #ifdef _WIN32
@@ -71,65 +76,38 @@ static void msg_callback(int level, const char *fmt, va_list args, void *) {
 		LOG_D("subtitle/provider/libass") << buf;
 }
 
-class FontConfigCacheThread : public wxThread {
-	ASS_Library *ass_library;
-	ASS_Renderer *ass_renderer;
-	FontConfigCacheThread** thisPtr;
-	ExitCode Entry() {
-		const char *config_path = nullptr;
 #ifdef __APPLE__
-		std::string conf_path = agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf";
-		config_path = conf_path.c_str();
+#define CONFIG_PATH (agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf").c_str()
+#else
+#define CONFIG_PATH nullptr
 #endif
+}
 
-		if (ass_library) ass_renderer = ass_renderer_init(ass_library);
-		ass_set_fonts(ass_renderer, nullptr, "Sans", 1, config_path, true);
-		if (ass_library) ass_renderer_done(ass_renderer);
-		*thisPtr = nullptr;
-		return EXIT_SUCCESS;
-	}
-public:
-	FontConfigCacheThread(ASS_Library *ass_library, FontConfigCacheThread **thisPtr)
-		: ass_library(ass_library)
-		, ass_renderer(nullptr)
-		, thisPtr(thisPtr)
-	{
-		*thisPtr = this;
-		Create();
-		Run();
-	}
-	FontConfigCacheThread(ASS_Renderer *ass_renderer, FontConfigCacheThread **thisPtr)
-		: ass_library(nullptr)
-		, ass_renderer(ass_renderer)
-		, thisPtr(thisPtr)
-	{
-		*thisPtr = this;
-		Create();
-		Run();
-	}
-};
-
-static void wait_for_cache_thread(FontConfigCacheThread const * const * const cache_worker) {
-	if (!*cache_worker) return;
+LibassSubtitlesProvider::LibassSubtitlesProvider(std::string)
+: ass_renderer(nullptr)
+, ass_track(nullptr)
+{
+	auto done = std::make_shared<bool>(false);
+	auto renderer = std::make_shared<ASS_Renderer*>(nullptr);
+	cache_queue->Async([=]{
+		auto ass_renderer = ass_renderer_init(library);
+		if (ass_renderer) {
+			ass_set_font_scale(ass_renderer, 1.);
+			ass_set_fonts(ass_renderer, nullptr, "Sans", 1, CONFIG_PATH, true);
+		}
+		*done = true;
+		*renderer = ass_renderer;
+	});
 
-	DialogProgress progress(wxGetApp().frame, "Updating font index", "This may take several minutes");
+	DialogProgress progress(wxGetApp().frame, _("Updating font index"), _("This may take several minutes"));
 	progress.Run([=](agi::ProgressSink *ps) {
 		ps->SetIndeterminate();
-		while (*cache_worker && !ps->IsCancelled())
-			wxMilliSleep(100);
+		while (!*done && !ps->IsCancelled())
+			std::this_thread::sleep_for(std::chrono::milliseconds(250));
 	});
-}
-
-LibassSubtitlesProvider::LibassSubtitlesProvider(std::string) {
-	wait_for_cache_thread(&cache_worker);
 
-	// Initialize renderer
-	ass_track = nullptr;
-	ass_renderer = ass_renderer_init(ass_library);
+	ass_renderer = *renderer;
 	if (!ass_renderer) throw "ass_renderer_init failed";
-	ass_set_font_scale(ass_renderer, 1.);
-	new FontConfigCacheThread(ass_renderer, &cache_worker);
-	wait_for_cache_thread(&cache_worker);
 }
 
 LibassSubtitlesProvider::~LibassSubtitlesProvider() {
@@ -138,37 +116,32 @@ LibassSubtitlesProvider::~LibassSubtitlesProvider() {
 }
 
 void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) {
-	// Prepare subtitles
 	std::vector<char> data;
 	subs->SaveMemory(data);
 
-	// Load file
 	if (ass_track) ass_free_track(ass_track);
-	ass_track = ass_read_memory(ass_library, &data[0], data.size(),(char *)"UTF-8");
+	ass_track = ass_read_memory(library, &data[0], data.size(),(char *)"UTF-8");
 	if (!ass_track) throw "libass failed to load subtitles.";
 }
 
-#define _r(c)  ((c)>>24)
-#define _g(c)  (((c)>>16)&0xFF)
-#define _b(c)  (((c)>>8)&0xFF)
-#define _a(c)  ((c)&0xFF)
+#define _r(c) ((c)>>24)
+#define _g(c) (((c)>>16)&0xFF)
+#define _b(c) (((c)>>8)&0xFF)
+#define _a(c) ((c)&0xFF)
 
 void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) {
-	// Set size
 	ass_set_frame_size(ass_renderer, frame.w, frame.h);
 
-	// Get frame
 	ASS_Image* img = ass_render_frame(ass_renderer, ass_track, int(time * 1000), nullptr);
 
 	// libass actually returns several alpha-masked monochrome images.
 	// Here, we loop through their linked list, get the colour of the current, and blend into the frame.
 	// This is repeated for all of them.
 	while (img) {
-		// Get colours
 		unsigned int opacity = 255 - ((unsigned int)_a(img->color));
 		unsigned int r = (unsigned int)_r(img->color);
 		unsigned int g = (unsigned int)_g(img->color);
-		unsigned int b = (unsigned int) _b(img->color);
+		unsigned int b = (unsigned int)_b(img->color);
 
 		// Prepare copy
 		int src_stride = img->stride;
@@ -206,18 +179,24 @@ void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) {
 			src += src_stride;
 		}
 
-		// Next image
 		img = img->next;
 	}
 }
 
 void LibassSubtitlesProvider::CacheFonts() {
-	ass_library = ass_library_init();
-	ass_set_message_cb(ass_library, msg_callback, nullptr);
-	new FontConfigCacheThread(ass_library, &cache_worker);
+	// Initialize the cache worker thread
+	cache_queue = agi::dispatch::Create();
+
+	// Initialize libass
+	library = ass_library_init();
+	ass_set_message_cb(library, msg_callback, nullptr);
+
+	// Initialize a renderer to force fontconfig to update its cache
+	cache_queue->Async([]{
+		auto ass_renderer = ass_renderer_init(library);
+		ass_set_fonts(ass_renderer, nullptr, "Sans", 1, CONFIG_PATH, true);
+		ass_renderer_done(ass_renderer);
+	});
 }
 
-ASS_Library* LibassSubtitlesProvider::ass_library;
-FontConfigCacheThread* LibassSubtitlesProvider::cache_worker = nullptr;
-
 #endif // WITH_LIBASS
diff --git a/aegisub/src/subtitles_provider_libass.h b/aegisub/src/subtitles_provider_libass.h
index 8e22200b2bd6b3f95f5548cfd4b86903874cb088..84ee24c12cd23644ebd57eba30c34341e3a1b98d 100644
--- a/aegisub/src/subtitles_provider_libass.h
+++ b/aegisub/src/subtitles_provider_libass.h
@@ -33,31 +33,22 @@
 ///
 
 #ifdef WITH_LIBASS
-
 #include "include/aegisub/subtitles_provider.h"
-extern "C" {
-#ifdef __VISUALC__
-#include "cstdint"
-#endif
 
+extern "C" {
 #include <ass/ass.h>
 }
 
-class FontConfigCacheThread;
-
 class LibassSubtitlesProvider : public SubtitlesProvider {
-	static ASS_Library* ass_library;
 	ASS_Renderer* ass_renderer;
 	ASS_Track* ass_track;
 
-	static FontConfigCacheThread *cache_worker;
-
 public:
 	LibassSubtitlesProvider(std::string);
 	~LibassSubtitlesProvider();
 
 	void LoadSubtitles(AssFile *subs);
-	void DrawSubtitles(AegiVideoFrame &dst,double time);
+	void DrawSubtitles(AegiVideoFrame &dst, double time);
 
 	static void CacheFonts();
 };
diff --git a/aegisub/src/text_file_reader.cpp b/aegisub/src/text_file_reader.cpp
index ad8ffccdbdc5a78bfa22708c73e002d88aed968e..5b5d61e5cf666886927ebc51a1f5d7c7ea273736 100644
--- a/aegisub/src/text_file_reader.cpp
+++ b/aegisub/src/text_file_reader.cpp
@@ -21,52 +21,34 @@
 
 #include "config.h"
 
-#include <algorithm>
-#include <cassert>
-#include <cerrno>
-#include <string>
+#include "text_file_reader.h"
 
-#include <wx/string.h>
+#include "charset_detect.h"
 
 #include <libaegisub/io.h>
-#include <libaegisub/log.h>
 
-#include "charset_detect.h"
-#include "compat.h"
-#include "text_file_reader.h"
+#include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
 
-TextFileReader::TextFileReader(wxString const& filename, wxString encoding, bool trim)
+TextFileReader::TextFileReader(agi::fs::path const& filename, std::string encoding, bool trim)
 : trim(trim)
 {
-	if (encoding.empty()) encoding = CharSetDetect::GetEncoding(filename);
-	file.reset(agi::io::Open(from_wx(filename), true));
-	iter = agi::line_iterator<wxString>(*file, from_wx(encoding));
+	if (encoding.empty())
+		encoding = CharSetDetect::GetEncoding(filename);
+	file.reset(agi::io::Open(filename, true));
+	iter = agi::line_iterator<std::string>(*file, encoding);
 }
 
 TextFileReader::~TextFileReader() {
 }
 
-wxString TextFileReader::ReadLineFromFile() {
-	wxString str = *iter;
+std::string TextFileReader::ReadLineFromFile() {
+	std::string str = *iter;
 	++iter;
-	if (trim) str.Trim(true).Trim(false);
-	if (str.StartsWith(L"\uFEFF")) str = str.Mid(1);
+	if (trim)
+		boost::trim(str);
+	if (boost::starts_with(str, "\xEF\xBB\xBF"))
+		str.erase(0, 3);
 	return str;
 }
-
-namespace agi {
-#ifdef _WIN32
-	template<> void line_iterator<wxString>::init() {
-		conv.reset(new agi::charset::IconvWrapper(encoding.c_str(), "utf-16le"));
-	}
-	template<> bool line_iterator<wxString>::convert(std::string &str) {
-		value = wxString(str.c_str(), wxMBConvUTF16LE(), str.size());
-		return true;
-	}
-#else
-	template<> bool line_iterator<wxString>::convert(std::string &str) {
-		value = str;
-		return true;
-	}
-#endif
-}
diff --git a/aegisub/src/text_file_reader.h b/aegisub/src/text_file_reader.h
index 68cffc9e42e7d78ddf25fb6ac51cf2a209d6f180..9b54cf3cf8d4fb883df5fdee20ab39e41e24fb35 100644
--- a/aegisub/src/text_file_reader.h
+++ b/aegisub/src/text_file_reader.h
@@ -23,10 +23,9 @@
 
 #include <fstream>
 #include <memory>
+#include <string>
 
-#include <wx/dynarray.h>
-#include <wx/string.h>
-
+#include <libaegisub/fs_fwd.h>
 #include <libaegisub/line_iterator.h>
 
 /// @class TextFileReader
@@ -34,7 +33,7 @@
 class TextFileReader {
 	std::unique_ptr<std::ifstream> file;
 	bool trim;
-	agi::line_iterator<wxString> iter;
+	agi::line_iterator<std::string> iter;
 
 	TextFileReader(const TextFileReader&);
 	TextFileReader& operator=(const TextFileReader&);
@@ -44,13 +43,13 @@ public:
 	/// @param filename File to open
 	/// @param enc      Encoding to use, or empty to autodetect
 	/// @param trim     Whether to trim whitespace from lines read
-	TextFileReader(wxString const& filename,wxString encoding="", bool trim=true);
+	TextFileReader(agi::fs::path const& filename, std::string encoding="", bool trim=true);
 	/// @brief Destructor
 	~TextFileReader();
 
 	/// @brief Read a line from the file
 	/// @return The line, possibly trimmed
-	wxString ReadLineFromFile();
+	std::string ReadLineFromFile();
 	/// @brief Check if there are any more lines to read
-	bool HasMoreLines() const { return iter != agi::line_iterator<wxString>(); }
+	bool HasMoreLines() const { return iter != agi::line_iterator<std::string>(); }
 };
diff --git a/aegisub/src/text_file_writer.cpp b/aegisub/src/text_file_writer.cpp
index 98a95b04ddd0110dd96fc532aade21c3f9d708fc..57e6179e801c0547159ed745d77085ebe31ed955 100644
--- a/aegisub/src/text_file_writer.cpp
+++ b/aegisub/src/text_file_writer.cpp
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -34,26 +21,28 @@
 
 #include "config.h"
 
-#include <fstream>
+#include "text_file_writer.h"
+
+#include "options.h"
 
 #include <libaegisub/io.h>
+#include <libaegisub/charset_conv.h>
 
-#include "charset_conv.h"
-#include "compat.h"
-#include "options.h"
-#include "text_file_writer.h"
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
 
-TextFileWriter::TextFileWriter(wxString const& filename, wxString encoding)
-: file(new agi::io::Save(from_wx(filename), true))
+TextFileWriter::TextFileWriter(agi::fs::path const& filename, std::string encoding)
+: file(new agi::io::Save(filename, true))
 , conv()
 {
-	if (encoding.empty()) encoding = to_wx(OPT_GET("App/Save Charset")->GetString());
-	if (encoding.Lower() != wxSTRING_ENCODING)
-		conv.reset(new agi::charset::IconvWrapper(wxSTRING_ENCODING, encoding.utf8_str(), true));
+	if (encoding.empty())
+		encoding = OPT_GET("App/Save Charset")->GetString();
+	if (boost::iequals(encoding, "utf-8"))
+		conv.reset(new agi::charset::IconvWrapper("utf-8", encoding.c_str(), true));
 
-	// Write the BOM
 	try {
-		WriteLineToFile(L"\uFEFF", false);
+		// Write the BOM
+		WriteLineToFile("\xEF\xBB\xBF", false);
 	}
 	catch (agi::charset::ConversionFailure&) {
 		// If the BOM could not be converted to the target encoding it isn't needed
@@ -61,29 +50,18 @@ TextFileWriter::TextFileWriter(wxString const& filename, wxString encoding)
 }
 
 TextFileWriter::~TextFileWriter() {
-	// Explicit empty destructor required with an auto_ptr to an incomplete class
+	// Explicit empty destructor required with a unique_ptr to an incomplete class
 }
 
-void TextFileWriter::WriteLineToFile(wxString line, bool addLineBreak) {
+void TextFileWriter::WriteLineToFile(std::string&& line, bool addLineBreak) {
+	if (addLineBreak)
 #ifdef _WIN32
-	if (addLineBreak) line += "\r\n";
+		line += "\r\n";
 #else
-	if (addLineBreak) line += "\n";
+		line += "\n";
 #endif
 
-	// On non-windows this cast does nothing
-	const char *data = reinterpret_cast<const char *>(line.wx_str());
-#if wxUSE_UNICODE_UTF8
-	size_t len = line.utf8_length();
-#else
-	size_t len = line.length() * sizeof(wxStringCharType);
-#endif
-
-	if (conv) {
-		std::string buf = conv->Convert(std::string(data, len));
-		file->Get().write(buf.data(), buf.size());
-	}
-	else {
-		file->Get().write(data, len);
-	}
+	if (conv)
+		line = conv->Convert(line);
+	file->Get().write(line.data(), line.size());
 }
diff --git a/aegisub/src/text_file_writer.h b/aegisub/src/text_file_writer.h
index 7282d052d88c7ede57eb3469957e46674260c9db..e97c3770d6bcba7b64e8137f5a84ebf4880a2e75 100644
--- a/aegisub/src/text_file_writer.h
+++ b/aegisub/src/text_file_writer.h
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
@@ -32,34 +19,24 @@
 /// @ingroup utility
 ///
 
+#include <memory>
+#include <string>
 
-
-#include <fstream>
-
-#include <wx/string.h>
+#include <libaegisub/fs_fwd.h>
 
 namespace agi {
 	namespace charset { class IconvWrapper; }
 	namespace io { class Save; }
 }
 
-#include <libaegisub/scoped_ptr.h>
-
 class TextFileWriter {
-	agi::scoped_ptr<agi::io::Save> file;
-	agi::scoped_ptr<agi::charset::IconvWrapper> conv;
+	std::unique_ptr<agi::io::Save> file;
+	std::unique_ptr<agi::charset::IconvWrapper> conv;
 
 public:
-	TextFileWriter(wxString const& filename, wxString encoding="");
+	TextFileWriter(agi::fs::path const& filename, std::string encoding="");
 	~TextFileWriter();
 
-	void WriteLineToFile(wxString line, bool addLineBreak=true);
+	void WriteLineToFile(std::string const& line, bool addLineBreak=true) { WriteLineToFile(std::string(line), addLineBreak); }
+	void WriteLineToFile(std::string&& line, bool addLineBreak=true);
 };
-
-#if wxUSE_UNICODE_UTF8
-#define wxSTRING_ENCODING "utf-8"
-#elif defined(_WIN32)
-#define wxSTRING_ENCODING "utf-16le"
-#else
-#define wxSTRING_ENCODING "utf-32le"
-#endif
diff --git a/aegisub/src/thesaurus.cpp b/aegisub/src/thesaurus.cpp
index 4607d71f050d5930ed1475dfd98b9e891fc159a6..7cca9b3b362473447f116d90b58c70be8a1fc139 100644
--- a/aegisub/src/thesaurus.cpp
+++ b/aegisub/src/thesaurus.cpp
@@ -23,16 +23,15 @@
 
 #include "thesaurus.h"
 
-#include <wx/dir.h>
-#include <wx/filename.h>
+#include "options.h"
+#include "standard_paths.h"
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
 
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
 #include <libaegisub/thesaurus.h>
-#include <libaegisub/util.h>
-
-#include "compat.h"
-#include "options.h"
-#include "standard_paths.h"
 
 Thesaurus::Thesaurus()
 : lang_listener(OPT_SUB("Tool/Thesaurus/Language", &Thesaurus::OnLanguageChanged, this))
@@ -47,46 +46,43 @@ Thesaurus::~Thesaurus() {
 
 void Thesaurus::Lookup(std::string word, std::vector<Entry> *result) {
 	if (!impl.get()) return;
-	agi::util::str_lower(word);
+	boost::to_lower(word);
 	impl->Lookup(word, result);
 }
 
 std::vector<std::string> Thesaurus::GetLanguageList() const {
 	if (!languages.empty()) return languages;
 
-	wxArrayString idx, dat;
+	std::vector<std::string> idx, dat;
 
 	// Get list of dictionaries
-	wxString path = StandardPaths::DecodePath("?data/dictionaries/");
-	if (wxFileName::DirExists(path)) {
-		wxDir::GetAllFiles(path, &idx, "th_*.idx", wxDIR_FILES);
-		wxDir::GetAllFiles(path, &dat, "th_*.dat", wxDIR_FILES);
-	}
-	path = StandardPaths::DecodePath(to_wx(OPT_GET("Path/Dictionary")->GetString()) + "/");
-	if (wxFileName::DirExists(path)) {
-		wxDir::GetAllFiles(path, &idx, "th_*.idx", wxDIR_FILES);
-		wxDir::GetAllFiles(path, &dat, "th_*.dat", wxDIR_FILES);
-	}
+	auto path = StandardPaths::DecodePath("?data/dictionaries/");
+	agi::fs::DirectoryIterator(path, "th_*.idx").GetAll(idx);
+	agi::fs::DirectoryIterator(path, "th_*.dat").GetAll(dat);
+
+	path = StandardPaths::DecodePath(OPT_GET("Path/Dictionary")->GetString());
+	agi::fs::DirectoryIterator(path, "th_*.idx").GetAll(idx);
+	agi::fs::DirectoryIterator(path, "th_*.dat").GetAll(dat);
+
 	if (idx.empty() || dat.empty()) return languages;
 
-	idx.Sort();
-	dat.Sort();
+	sort(begin(idx), end(idx));
+	sort(begin(dat), end(dat));
 
 	// Drop extensions and the th_ prefix
-	for (auto& fn : idx) fn = fn.Mid(3, fn.size() - 7);
-	for (auto& fn : dat) fn = fn.Mid(3, fn.size() - 7);
+	for (auto& fn : idx) fn = fn.substr(3, fn.size() - 7);
+	for (auto& fn : dat) fn = fn.substr(3, fn.size() - 7);
 
 	// Verify that each idx has a dat
 	for (size_t i = 0, j = 0; i < idx.size() && j < dat.size(); ) {
-		int cmp = idx[i].Cmp(dat[j]);
+		int cmp = idx[i].compare(dat[j]);
 		if (cmp < 0) ++i;
 		else if (cmp > 0) ++j;
 		else {
 			// Don't insert a language twice if it's in both the user dir and
 			// the app's dir
-			std::string name = from_wx(wxFileName(dat[j]).GetName().Mid(3));
-			if (languages.empty() || name != languages.back())
-				languages.push_back(name);
+			if (languages.empty() || dat[j] != languages.back())
+				languages.push_back(dat[j]);
 			++i;
 			++j;
 		}
@@ -97,27 +93,27 @@ std::vector<std::string> Thesaurus::GetLanguageList() const {
 void Thesaurus::OnLanguageChanged() {
 	impl.reset();
 
-	std::string language = OPT_GET("Tool/Thesaurus/Language")->GetString();
+	auto language = OPT_GET("Tool/Thesaurus/Language")->GetString();
 	if (language.empty()) return;
 
-	wxString path = StandardPaths::DecodePath(to_wx(OPT_GET("Path/Dictionary")->GetString()) + "/");
+	auto path = StandardPaths::DecodePath(OPT_GET("Path/Dictionary")->GetString() + "/");
 
 	// Get index and data paths
-	wxString idxpath = wxString::Format("%s/th_%s.idx", path, language);
-	wxString datpath = wxString::Format("%s/th_%s.dat", path, language);
+	auto idxpath = path/str(boost::format("th_%s.idx") % language);
+	auto datpath = path/str(boost::format("th_%s.dat") % language);
 
 	// If they aren't in the user dictionary path, check the application directory
-	if (!wxFileExists(idxpath) || !wxFileExists(datpath)) {
+	if (!agi::fs::FileExists(idxpath) || !agi::fs::FileExists(datpath)) {
 		path = StandardPaths::DecodePath("?data/dictionaries/");
-		idxpath = wxString::Format("%s/th_%s.idx", path, language);
-		datpath = wxString::Format("%s/th_%s.dat", path, language);
+		idxpath = path/str(boost::format("th_%s.idx") % language);
+		datpath = path/str(boost::format("th_%s.dat") % language);
 
-		if (!wxFileExists(idxpath) || !wxFileExists(datpath)) return;
+		if (!agi::fs::FileExists(idxpath) || !agi::fs::FileExists(datpath)) return;
 	}
 
-	LOG_I("thesaurus/file") << "Using thesaurus: " << datpath.c_str();
+	LOG_I("thesaurus/file") << "Using thesaurus: " << datpath;
 
-	impl.reset(new agi::Thesaurus(from_wx(datpath), from_wx(idxpath)));
+	impl.reset(new agi::Thesaurus(datpath, idxpath));
 }
 
 void Thesaurus::OnPathChanged() {
diff --git a/aegisub/src/thesaurus.h b/aegisub/src/thesaurus.h
index f5c0bf7fe4f35c111884eaa9e173835491d87ed9..84dc91ceeeb8501fd57eec1d6353d7bf89333163 100644
--- a/aegisub/src/thesaurus.h
+++ b/aegisub/src/thesaurus.h
@@ -19,10 +19,10 @@
 /// @ingroup thesaurus
 ///
 
+#include <memory>
 #include <string>
 #include <vector>
 
-#include <libaegisub/scoped_ptr.h>
 #include <libaegisub/signal.h>
 
 namespace agi { class Thesaurus; }
@@ -30,8 +30,8 @@ namespace agi { class Thesaurus; }
 /// @class Thesaurus
 /// @brief A wrapper around agi::Thesarus adding wx and Aegisub-specific stuff
 class Thesaurus {
-	/// The actual thesarus implementation
-	agi::scoped_ptr<agi::Thesaurus> impl;
+	/// The actual thesaurus implementation
+	std::unique_ptr<agi::Thesaurus> impl;
 	/// A cached list of languages available
 	mutable std::vector<std::string> languages;
 
diff --git a/aegisub/src/threaded_frame_source.cpp b/aegisub/src/threaded_frame_source.cpp
index a17eb8467e870a8a9c46dcb9ee62cd3a3fcc7c48..9d856423e23cdc0ac7741ac3aed074d3cf4017ff 100644
--- a/aegisub/src/threaded_frame_source.cpp
+++ b/aegisub/src/threaded_frame_source.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,224 +21,188 @@
 
 #include "threaded_frame_source.h"
 
-#include <deque>
-#include <functional>
-
-#include <boost/range/adaptor/indirected.hpp>
-#include <boost/range/algorithm_ext.hpp>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
-#include "compat.h"
 #include "export_fixstyle.h"
-#include "include/aegisub/context.h"
 #include "include/aegisub/subtitles_provider.h"
 #include "video_frame.h"
 #include "video_provider_manager.h"
 
+#include <libaegisub/dispatch.h>
+
+#include <boost/range/adaptor/indirected.hpp>
+#include <boost/range/algorithm_ext.hpp>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+
 enum {
 	NEW_SUBS_FILE = -1,
 	SUBS_FILE_ALREADY_LOADED = -2
 };
 
-// Test if a line is a dialogue line which is not visible at the given time
-struct invisible_line : public std::unary_function<AssEntry const&, bool> {
-	double time;
-	invisible_line(double time) : time(time * 1000.) { }
-	bool operator()(AssEntry const& entry) const {
-		const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&entry);
-		return diag && (diag->Start > time || diag->End <= time);
-	}
-};
-
-std::shared_ptr<AegiVideoFrame> ThreadedFrameSource::ProcFrame(int frameNum, double time, bool raw) {
+std::shared_ptr<AegiVideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
 	std::shared_ptr<AegiVideoFrame> frame(new AegiVideoFrame, [](AegiVideoFrame *frame) {
 		frame->Clear();
 		delete frame;
 	});
 
-	{
-		wxMutexLocker locker(providerMutex);
-		try {
-			frame->CopyFrom(videoProvider->GetFrame(frameNum));
-		}
-		catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
-	}
-
-	// This deliberately results in a call to LoadSubtitles while a render
-	// is pending making the queued render use the new file
-	if (!raw && provider) {
-		try {
-			wxMutexLocker locker(fileMutex);
-			if (subs && singleFrame != frameNum && singleFrame != SUBS_FILE_ALREADY_LOADED) {
-				// Generally edits and seeks come in groups; if the last thing done
-				// was seek it is more likely that the user will seek again and
-				// vice versa. As such, if this is the first frame requested after
-				// an edit, only export the currently visible lines (because the
-				// other lines will probably not be viewed before the file changes
-				// again), and if it's a different frame, export the entire file.
-				if (singleFrame != NEW_SUBS_FILE) {
-					provider->LoadSubtitles(subs.get());
-					singleFrame = SUBS_FILE_ALREADY_LOADED;
-				}
-				else {
-					AssFixStylesFilter().ProcessSubs(subs.get(), nullptr);
-
-					singleFrame = frameNum;
-					// Copying a nontrivially sized AssFile is fairly slow, so
-					// instead muck around with its innards to just temporarily
-					// remove the non-visible lines without deleting them
-					std::deque<AssEntry*> full;
-					for (auto& line : subs->Line)
-						full.push_back(&line);
-					subs->Line.remove_if(invisible_line(time));
-
-					try {
-						provider->LoadSubtitles(subs.get());
-
-						subs->Line.clear();
-						boost::push_back(subs->Line, full | boost::adaptors::indirected);
-					}
-					catch (...) {
-						subs->Line.clear();
-						boost::push_back(subs->Line, full | boost::adaptors::indirected);
-						throw;
-					}
-				}
-			}
-		}
-		catch (wxString const& err) { throw SubtitlesProviderErrorEvent(err); }
-
-		provider->DrawSubtitles(*frame, time);
+	try {
+		frame->CopyFrom(video_provider->GetFrame(frame_number));
 	}
-	return frame;
-}
-
-void *ThreadedFrameSource::Entry() {
-	while (!TestDestroy()) {
-		double time;
-		int frameNum;
-		std::unique_ptr<AssFile> newSubs;
-		std::deque<std::pair<size_t, std::unique_ptr<AssEntry>>> updates;
-		{
-			wxMutexLocker jobLocker(jobMutex);
+	catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
 
-			if (!run)
-				return EXIT_SUCCESS;
+	if (raw || !subs_provider || !subs) return frame;
 
-			if (nextTime == -1.) {
-				jobReady.Wait();
-				continue;
+	try {
+		if (single_frame != frame_number && single_frame != SUBS_FILE_ALREADY_LOADED) {
+			// Generally edits and seeks come in groups; if the last thing done
+			// was seek it is more likely that the user will seek again and
+			// vice versa. As such, if this is the first frame requested after
+			// an edit, only export the currently visible lines (because the
+			// other lines will probably not be viewed before the file changes
+			// again), and if it's a different frame, export the entire file.
+			if (single_frame != NEW_SUBS_FILE) {
+				subs_provider->LoadSubtitles(subs.get());
+				single_frame = SUBS_FILE_ALREADY_LOADED;
 			}
-
-			time = nextTime;
-			frameNum = nextFrame;
-			nextTime = -1.;
-			newSubs = move(nextSubs);
-			updates = move(changedSubs);
-		}
-
-		if (newSubs || updates.size()) {
-			wxMutexLocker fileLocker(fileMutex);
-
-			if (newSubs)
-				subs = move(newSubs);
-
-			if (updates.size()) {
-				size_t i = 0;
-				auto it = subs->Line.begin();
-				// Replace each changed line in subs with the updated version
-				for (auto& update : updates) {
-					advance(it, update.first - i);
-					i = update.first;
-					subs->Line.insert(it, *update.second.release());
-					delete &*it--;
+			else {
+				AssFixStylesFilter().ProcessSubs(subs.get(), nullptr);
+
+				single_frame = frame_number;
+				// Copying a nontrivially sized AssFile is fairly slow, so
+				// instead muck around with its innards to just temporarily
+				// remove the non-visible lines without deleting them
+				std::deque<AssEntry*> full;
+				for (auto& line : subs->Line)
+					full.push_back(&line);
+				subs->Line.remove_if([=](AssEntry const& e) -> bool {
+					const AssDialogue *diag = dynamic_cast<const AssDialogue*>(&e);
+					return diag && (diag->Start > time || diag->End <= time);
+				});
+
+				try {
+					subs_provider->LoadSubtitles(subs.get());
+
+					subs->Line.clear();
+					boost::push_back(subs->Line, full | boost::adaptors::indirected);
+				}
+				catch (...) {
+					subs->Line.clear();
+					boost::push_back(subs->Line, full | boost::adaptors::indirected);
+					throw;
 				}
 			}
-
-			singleFrame = NEW_SUBS_FILE;
-		}
-
-		try {
-			FrameReadyEvent *evt = new FrameReadyEvent(ProcFrame(frameNum, time), time);
-			evt->SetEventType(EVT_FRAME_READY);
-			parent->QueueEvent(evt);
-		}
-		catch (wxEvent const& err) {
-			// Pass error back to parent thread
-			parent->QueueEvent(err.Clone());
 		}
 	}
+	catch (std::string const& err) { throw SubtitlesProviderErrorEvent(err); }
+
+	subs_provider->DrawSubtitles(*frame, time / 1000.);
 
-	return EXIT_SUCCESS;
+	return frame;
 }
 
 static SubtitlesProvider *get_subs_provider(wxEvtHandler *parent) {
 	try {
 		return SubtitlesProviderFactory::GetProvider();
 	}
-	catch (wxString const& err) {
+	catch (std::string const& err) {
 		parent->AddPendingEvent(SubtitlesProviderErrorEvent(err));
 		return 0;
 	}
 }
 
-ThreadedFrameSource::ThreadedFrameSource(wxString videoFileName, wxEvtHandler *parent)
-: wxThread(wxTHREAD_JOINABLE)
-, provider(get_subs_provider(parent))
-, videoProvider(VideoProviderFactory::GetProvider(videoFileName))
+ThreadedFrameSource::ThreadedFrameSource(agi::fs::path const& video_filename, wxEvtHandler *parent)
+: worker(agi::dispatch::Create())
+, subs_provider(get_subs_provider(parent))
+, video_provider(VideoProviderFactory::GetProvider(video_filename))
 , parent(parent)
-, nextFrame(-1)
-, nextTime(-1.)
-, singleFrame(-1)
-, jobReady(jobMutex)
-, run(true)
+, frame_number(-1)
+, time(-1.)
+, single_frame(-1)
+, version(0)
 {
-	Create();
-	Run();
 }
 
 ThreadedFrameSource::~ThreadedFrameSource() {
-	{
-		wxMutexLocker locker(jobMutex);
-		run = false;
-		jobReady.Signal();
-	}
-	Wait();
 }
 
-void ThreadedFrameSource::LoadSubtitles(const AssFile *subs) throw() {
-	AssFile *copy = new AssFile(*subs);
-	wxMutexLocker locker(jobMutex);
-	// Set nextSubs and let the worker thread move it to subs so that we don't
-	// have to lock fileMutex on the GUI thread, as that can be locked for
-	// extended periods of time with slow-rendering subtitles
-	nextSubs.reset(copy);
-	changedSubs.clear();
+void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
+	uint_fast32_t req_version = ++version;
+
+	AssFile *copy = new AssFile(*new_subs);
+	worker->Async([=]{
+		subs.reset(copy);
+		single_frame = NEW_SUBS_FILE;
+		ProcAsync(req_version);
+	});
 }
 
-void ThreadedFrameSource::UpdateSubtitles(const AssFile *subs, std::set<const AssEntry*> changes) throw() {
-	std::deque<std::pair<size_t, std::unique_ptr<AssEntry>>> changed;
+void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set<const AssEntry*> const& changes) throw() {
+	uint_fast32_t req_version = ++version;
+
+	// Copy just the lines which were changed, then replace the lines at the
+	// same indices in the worker's copy of the file with the new entries
+	std::deque<std::pair<size_t, AssEntry*>> changed;
 	size_t i = 0;
-	for (auto const& e : subs->Line) {
+	for (auto const& e : new_subs->Line) {
 		if (changes.count(&e))
-			changed.emplace_back(i, std::unique_ptr<AssEntry>(e.Clone()));
+			changed.emplace_back(i, e.Clone());
 		++i;
 	}
 
-	wxMutexLocker locker(jobMutex);
-	changedSubs = std::move(changed);
+	worker->Async([=]{
+		size_t i = 0;
+		auto it = subs->Line.begin();
+		for (auto& update : changed) {
+			advance(it, update.first - i);
+			i = update.first;
+			subs->Line.insert(it, *update.second);
+			delete &*it--;
+		}
+
+		single_frame = NEW_SUBS_FILE;
+		ProcAsync(req_version);
+	});
+}
+
+void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
+	uint_fast32_t req_version = ++version;
+
+	worker->Async([=]{
+		time = new_time;
+		frame_number = new_frame;
+		ProcAsync(req_version);
+	});
 }
 
-void ThreadedFrameSource::RequestFrame(int frame, double time) throw() {
-	wxMutexLocker locker(jobMutex);
-	nextTime  = time;
-	nextFrame = frame;
-	jobReady.Signal();
+void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
+	// Only actually produce the frame if there's no queued changes waiting
+	if (req_version < version || frame_number < 0) return;
+
+	try {
+		FrameReadyEvent *evt = new FrameReadyEvent(ProcFrame(frame_number, time), time);
+		evt->SetEventType(EVT_FRAME_READY);
+		parent->QueueEvent(evt);
+	}
+	catch (wxEvent const& err) {
+		// Pass error back to parent thread
+		parent->QueueEvent(err.Clone());
+	}
 }
 
 std::shared_ptr<AegiVideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
-	return ProcFrame(frame, time, raw);
+	std::shared_ptr<AegiVideoFrame> ret;
+	std::mutex m;
+	std::condition_variable cv;
+	std::unique_lock<std::mutex> l(m);
+	worker->Async([&]{
+		std::unique_lock<std::mutex> l(m);
+		ret = ProcFrame(frame, time, raw);
+		cv.notify_all();
+	});
+	cv.wait(l, [&]{ return !!ret; }); // predicate is to deal with spurious wakeups
+	return ret;
 }
 
 wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
@@ -250,8 +214,8 @@ VideoProviderErrorEvent::VideoProviderErrorEvent(VideoProviderError const& err)
 {
 	SetEventType(EVT_VIDEO_ERROR);
 }
-SubtitlesProviderErrorEvent::SubtitlesProviderErrorEvent(wxString err)
-: agi::Exception(from_wx(err), nullptr)
+SubtitlesProviderErrorEvent::SubtitlesProviderErrorEvent(std::string const& err)
+: agi::Exception(err, nullptr)
 {
 	SetEventType(EVT_SUBTITLES_ERROR);
 }
diff --git a/aegisub/src/threaded_frame_source.h b/aegisub/src/threaded_frame_source.h
index 62b101793979d12c834e371a6b975dd6bb287de6..dc2e26c843786e5e245da3891a5e6c4eaeb228d3 100644
--- a/aegisub/src/threaded_frame_source.h
+++ b/aegisub/src/threaded_frame_source.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,15 +19,15 @@
 /// @ingroup video
 ///
 
+#include <libaegisub/exception.h>
+#include <libaegisub/fs_fwd.h>
+
+#include <atomic>
 #include <deque>
 #include <memory>
 #include <set>
 
 #include <wx/event.h>
-#include <wx/thread.h>
-
-#include <libaegisub/exception.h>
-#include <libaegisub/scoped_ptr.h>
 
 class AegiVideoFrame;
 class AssEntry;
@@ -35,39 +35,41 @@ class AssFile;
 class SubtitlesProvider;
 class VideoProvider;
 class VideoProviderError;
+namespace agi { namespace dispatch { class Queue; } }
 
 /// @class ThreadedFrameSource
 /// @brief An asynchronous video decoding and subtitle rendering wrapper
-class ThreadedFrameSource : public wxThread {
+class ThreadedFrameSource {
+	/// Asynchronous work queue
+	std::unique_ptr<agi::dispatch::Queue> worker;
+
 	/// Subtitles provider
-	agi::scoped_ptr<SubtitlesProvider> provider;
+	std::unique_ptr<SubtitlesProvider> subs_provider;
 	/// Video provider
-	agi::scoped_ptr<VideoProvider> videoProvider;
+	std::unique_ptr<VideoProvider> video_provider;
 	/// Event handler to send FrameReady events to
 	wxEvtHandler *parent;
 
-	int nextFrame;   ///< Next queued frame, or -1 for none
-	double nextTime; ///< Next queued time
-	std::unique_ptr<AssFile> nextSubs; ///< Next queued AssFile
-	std::deque<std::pair<size_t, std::unique_ptr<AssEntry>>> changedSubs;
+	int frame_number; ///< Last frame number requested
+	double time; ///< Time of the frame to pass to the subtitle renderer
 
-	/// Subtitles to be loaded the next time a frame is requested
+	/// Copy of the subtitles file to avoid having to touch the project context
 	std::unique_ptr<AssFile> subs;
-	/// If subs is set and this is not -1, frame which subs was limited to when
-	/// it was last sent to the subtitle provider
-	int singleFrame;
 
-	wxMutex fileMutex;     ///< Mutex for subs and singleFrame
-	wxMutex jobMutex;      ///< Mutex for nextFrame, nextTime and nextSubs
-	wxMutex providerMutex; ///< Mutex for video provider
-	wxMutex evtMutex;      ///< Mutex for FrameReadyEvents associated with this
+	/// If >= 0, the subtitles provider current has just the lines visible on
+	/// that frame loaded. If -1, the entire file is loaded. If -2, the
+	/// currently loaded file is out of date.
+	int single_frame;
 
-	wxCondition jobReady;  ///< Signal for indicating that a frame has be requested
+	std::shared_ptr<AegiVideoFrame> ProcFrame(int frame, double time, bool raw = false);
 
-	bool run; ///< Should the thread continue to run
+	/// Produce a frame if req_version is still the current version
+	void ProcAsync(uint_fast32_t req_version);
+
+	/// Monotonic counter used to drop frames when changes arrive faster than
+	/// they can be rendered
+	std::atomic<uint_fast32_t> version;
 
-	void *Entry();
-	std::shared_ptr<AegiVideoFrame> ProcFrame(int frameNum, double time, bool raw = false);
 public:
 	/// @brief Load the passed subtitle file
 	/// @param subs File to load
@@ -82,7 +84,7 @@ public:
 	///
 	/// This function only supports changes to existing lines, and not
 	/// insertions or deletions.
-	void UpdateSubtitles(const AssFile *subs, std::set<const AssEntry *> changes) throw();
+	void UpdateSubtitles(const AssFile *subs, std::set<const AssEntry *> const& changes) throw();
 
 	/// @brief Queue a request for a frame
 	/// @brief frame Frame number
@@ -99,45 +101,40 @@ public:
 	std::shared_ptr<AegiVideoFrame> GetFrame(int frame, double time, bool raw = false);
 
 	/// Get a reference to the video provider this is using
-	VideoProvider *GetVideoProvider() const { return videoProvider.get(); }
+	VideoProvider *GetVideoProvider() const { return video_provider.get(); }
 
 	/// @brief Constructor
 	/// @param videoFileName File to open
 	/// @param parent Event handler to send FrameReady events to
-	ThreadedFrameSource(wxString videoFileName, wxEvtHandler *parent);
+	ThreadedFrameSource(agi::fs::path const& filename, wxEvtHandler *parent);
 	~ThreadedFrameSource();
 };
 
-/// @class FrameReadyEvent
-/// @brief Event which signals that a requested frame is ready
-class FrameReadyEvent : public wxEvent {
-public:
+/// Event which signals that a requested frame is ready
+struct FrameReadyEvent : public wxEvent {
 	/// Frame which is ready
 	std::shared_ptr<AegiVideoFrame> frame;
 	/// Time which was used for subtitle rendering
 	double time;
 	wxEvent *Clone() const { return new FrameReadyEvent(*this); };
 	FrameReadyEvent(std::shared_ptr<AegiVideoFrame> frame, double time)
-		: frame(frame), time(time) {
-	}
+	: frame(frame), time(time) { }
 };
 
 // These exceptions are wxEvents so that they can be passed directly back to
 // the parent thread as events
-class VideoProviderErrorEvent : public wxEvent, public agi::Exception {
-public:
+struct VideoProviderErrorEvent : public wxEvent, public agi::Exception {
 	const char * GetName() const { return "video/error"; }
 	wxEvent *Clone() const { return new VideoProviderErrorEvent(*this); };
 	agi::Exception *Copy() const { return new VideoProviderErrorEvent(*this); };
 	VideoProviderErrorEvent(VideoProviderError const& err);
 };
 
-class SubtitlesProviderErrorEvent : public wxEvent, public agi::Exception {
-public:
+struct SubtitlesProviderErrorEvent : public wxEvent, public agi::Exception {
 	const char * GetName() const { return "subtitles/error"; }
 	wxEvent *Clone() const { return new SubtitlesProviderErrorEvent(*this); };
 	agi::Exception *Copy() const { return new SubtitlesProviderErrorEvent(*this); };
-	SubtitlesProviderErrorEvent(wxString msg);
+	SubtitlesProviderErrorEvent(std::string const& msg);
 };
 
 wxDECLARE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
diff --git a/aegisub/src/timeedit_ctrl.cpp b/aegisub/src/timeedit_ctrl.cpp
index 67c53e0164e9c9723ceb0681de8c1bad638d91f2..1945b5e197c1dc2fc2ea00df9ab3d03f55a6d3f6 100644
--- a/aegisub/src/timeedit_ctrl.cpp
+++ b/aegisub/src/timeedit_ctrl.cpp
@@ -38,11 +38,6 @@
 
 #include <functional>
 
-#include <wx/clipbrd.h>
-#include <wx/dataobj.h>
-#include <wx/menu.h>
-#include <wx/valtext.h>
-
 #include "ass_time.h"
 #include "compat.h"
 #include "include/aegisub/context.h"
@@ -50,6 +45,11 @@
 #include "utils.h"
 #include "video_context.h"
 
+#include <wx/clipbrd.h>
+#include <wx/dataobj.h>
+#include <wx/menu.h>
+#include <wx/valtext.h>
+
 #define TimeEditWindowStyle
 
 enum {
@@ -57,8 +57,8 @@ enum {
 	Time_Edit_Paste
 };
 
-TimeEdit::TimeEdit(wxWindow* parent, wxWindowID id, agi::Context *c, const wxString& value, const wxSize& size, bool asEnd)
-: wxTextCtrl(parent, id, value, wxDefaultPosition, size, wxTE_CENTRE | wxTE_PROCESS_ENTER)
+TimeEdit::TimeEdit(wxWindow* parent, wxWindowID id, agi::Context *c, const std::string& value, const wxSize& size, bool asEnd)
+: wxTextCtrl(parent, id, to_wx(value), wxDefaultPosition, size, wxTE_CENTRE | wxTE_PROCESS_ENTER)
 , c(c)
 , byFrame(false)
 , isEnd(asEnd)
@@ -84,7 +84,7 @@ TimeEdit::TimeEdit(wxWindow* parent, wxWindowID id, agi::Context *c, const wxStr
 	SetValidator(val);
 
 	// Other stuff
-	if (!value) SetValue(time.GetAssFormated());
+	if (value.empty()) SetValue(to_wx(time.GetAssFormated()));
 
 	Bind(wxEVT_COMMAND_MENU_SELECTED, std::bind(&TimeEdit::CopyTime, this), Time_Edit_Copy);
 	Bind(wxEVT_COMMAND_MENU_SELECTED, std::bind(&TimeEdit::PasteTime, this), Time_Edit_Paste);
@@ -116,7 +116,6 @@ void TimeEdit::SetByFrame(bool enableByFrame) {
 	UpdateText();
 }
 
-
 void TimeEdit::OnModified(wxCommandEvent &event) {
 	event.Skip();
 	if (byFrame) {
@@ -125,15 +124,14 @@ void TimeEdit::OnModified(wxCommandEvent &event) {
 		time = c->videoController->TimeAtFrame(temp, isEnd ? agi::vfr::END : agi::vfr::START);
 	}
 	else if (insert)
-		time = GetValue();
+		time = from_wx(GetValue());
 }
 
-
 void TimeEdit::UpdateText() {
 	if (byFrame)
 		ChangeValue(wxString::Format("%d", c->videoController->FrameAtTime(time, isEnd ? agi::vfr::END : agi::vfr::START)));
 	else
-		ChangeValue(time.GetAssFormated());
+		ChangeValue(to_wx(time.GetAssFormated()));
 }
 
 void TimeEdit::OnKeyDown(wxKeyEvent &event) {
@@ -168,17 +166,18 @@ void TimeEdit::OnKeyDown(wxKeyEvent &event) {
 
 	event.Skip(false);
 
-	long start = GetInsertionPoint();
-	wxString text = GetValue();
-
 	// Delete does nothing
 	if (key == WXK_DELETE) return;
+
+	long start = GetInsertionPoint();
 	// Back just moves cursor back one without deleting
 	if (key == WXK_BACK) {
 		if (start > 0)
 			SetInsertionPoint(start - 1);
 		return;
 	}
+
+	std::string text = from_wx(GetValue());
 	// Cursor is at the end so do nothing
 	if (start >= (long)text.size()) return;
 
@@ -193,8 +192,9 @@ void TimeEdit::OnKeyDown(wxKeyEvent &event) {
 	}
 
 	// Overwrite the digit
-	time = text.Left(start) + (char)key + text.Mid(start + 1);
-	SetValue(time.GetAssFormated());
+	text[start] = (char)key;
+	time = text;
+	SetValue(to_wx(time.GetAssFormated()));
 	SetInsertionPoint(start + 1);
 }
 
@@ -221,7 +221,7 @@ void TimeEdit::OnFocusLost(wxFocusEvent &evt) {
 }
 
 void TimeEdit::CopyTime() {
-	SetClipboard(GetValue());
+	SetClipboard(from_wx(GetValue()));
 }
 
 void TimeEdit::PasteTime() {
@@ -230,8 +230,8 @@ void TimeEdit::PasteTime() {
 		return;
 	}
 
-	wxString text = GetClipboard();
-	if (!text) return;
+	std::string text(GetClipboard());
+	if (text.empty()) return;
 
 	AssTime tempTime(text);
 	if (tempTime.GetAssFormated() == text) {
diff --git a/aegisub/src/timeedit_ctrl.h b/aegisub/src/timeedit_ctrl.h
index 1c3693ce541815f8898a019e59fdc0a33ff43e8d..7e5a19b1a2a37889de030b63b616cd223875aad9 100644
--- a/aegisub/src/timeedit_ctrl.h
+++ b/aegisub/src/timeedit_ctrl.h
@@ -90,5 +90,5 @@ public:
 	/// @param value Initial value. Must be a valid time string or empty
 	/// @param size Initial control size
 	/// @param asEnd Treat the time as a line end time (rather than start) for time <-> frame number conversions
-	TimeEdit(wxWindow* parent, wxWindowID id, agi::Context *c, const wxString& value = wxString(), const wxSize& size = wxDefaultSize, bool asEnd = false);
+	TimeEdit(wxWindow* parent, wxWindowID id, agi::Context *c, const std::string& value = std::string(), const wxSize& size = wxDefaultSize, bool asEnd = false);
 };
diff --git a/aegisub/src/utils.cpp b/aegisub/src/utils.cpp
index e0a18df10c19af7e3aa289b40f7a358d38d4f10e..f9951c60858d0ce11b8cd8056bd82d8a83cd4c6a 100644
--- a/aegisub/src/utils.cpp
+++ b/aegisub/src/utils.cpp
@@ -36,49 +36,29 @@
 
 #include "utils.h"
 
+#include "compat.h"
+#include "options.h"
+
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
+
 #ifdef __UNIX__
 #include <unistd.h>
 #endif
+#include <boost/filesystem.hpp>
+#include <boost/format.hpp>
 #include <map>
 
 #include <wx/clipbrd.h>
-#include <wx/dir.h>
 #include <wx/filename.h>
 #include <wx/stdpaths.h>
 #include <wx/window.h>
 
-#include <libaegisub/log.h>
-
 #ifdef __APPLE__
 #include <libaegisub/util_osx.h>
 #endif
 
-#include "compat.h"
-#include "options.h"
-
-wxDEFINE_EVENT(EVT_CALL_THUNK, wxThreadEvent);
-
-wxString MakeRelativePath(wxString _path, wxString reference) {
-	if (_path.empty() || _path[0] == '?') return _path;
-	wxFileName path(_path);
-	wxFileName refPath(reference);
-	path.MakeRelativeTo(refPath.GetPath());
-	return path.GetFullPath();
-}
-
-wxString DecodeRelativePath(wxString _path,wxString reference) {
-	if (_path.empty() || _path[0] == '?') return _path;
-	wxFileName path(_path);
-	wxFileName refPath(reference);
-	if (!path.IsAbsolute()) path.MakeAbsolute(refPath.GetPath());
-#ifdef __UNIX__
-	return path.GetFullPath(wxPATH_UNIX); // also works on windows
-										  // No, it doesn't, this ommits drive letter in Windows. ~ amz
-#else
-	return path.GetFullPath();
-#endif
-}
-
 wxString AegiFloatToString(double value) {
 	return wxString::Format("%g",value);
 }
@@ -119,8 +99,7 @@ int SmallestPowerOf2(int x) {
 	return x;
 }
 
-bool IsWhitespace(wchar_t c)
-{
+bool IsWhitespace(wchar_t c) {
 	const wchar_t whitespaces[] = {
 		// http://en.wikipedia.org/wiki/Space_(punctuation)#Table_of_spaces
 		0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x0085, 0x00A0,
@@ -128,81 +107,11 @@ bool IsWhitespace(wchar_t c)
 		0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F,
 		0x025F, 0x3000, 0xFEFF
 	};
-	const size_t num_chars = sizeof(whitespaces) / sizeof(whitespaces[0]);
-	return std::binary_search(whitespaces, whitespaces + num_chars, c);
-}
-
-bool StringEmptyOrWhitespace(const wxString &str)
-{
-	for (size_t i = 0; i < str.size(); ++i)
-		if (!IsWhitespace(str[i]))
-			return false;
-
-	return true;
-}
-
-int AegiStringToInt(const wxString &str,int start,int end) {
-	// Initialize to zero and get length if end set to -1
-	int sign = 1;
-	int value = 0;
-	if (end == -1) end = str.Length();
-
-	for (int pos=start;pos<end;pos++) {
-		// Get value and check if it's a number
-		int val = (int)(str[pos]);
-		if (val == ' ' || val == '\t') continue;
-		if (val == '-') sign = -1;
-		if (val < '0' || val > '9') break;
-
-		// Shift value to next decimal place and increment the value just read
-		value = value * 10 + (val - '0');
-	}
-
-	return value*sign;
+	return std::binary_search(whitespaces, std::end(whitespaces), c);
 }
 
-int AegiStringToFix(const wxString &str,size_t decimalPlaces,int start,int end) {
-	// Parts of the number
-	int sign = 1;
-	int major = 0;
-	int minor = 0;
-	if (end == -1) end = str.Length();
-	bool inMinor = false;
-	int *dst = &major;
-	size_t mCount = 0;
-
-	for (int pos=start;pos<end;pos++) {
-		// Get value and check if it's a number
-		int val = (int)(str[pos]);
-		if (val == ' ' || val == '\t') continue;
-		if (val == '-') sign = -1;
-
-		// Switch to minor
-		if (val == '.' || val == ',') {
-			if (inMinor) break;
-			inMinor = true;
-			dst = &minor;
-			mCount = 0;
-			continue;
-		}
-		if (val < '0' || val > '9') break;
-		*dst = (*dst * 10) + (val - '0');
-		mCount++;
-	}
-
-	// Change minor to have the right number of decimal places
-	while (mCount > decimalPlaces) {
-		minor /= 10;
-		mCount--;
-	}
-	while (mCount < decimalPlaces) {
-		minor *= 10;
-		mCount++;
-	}
-
-	// Shift major and return
-	for (size_t i=0;i<decimalPlaces;i++) major *= 10;
-	return (major + minor)*sign;
+bool StringEmptyOrWhitespace(const wxString &str) {
+	return std::all_of(str.begin(), str.end(), IsWhitespace);
 }
 
 void RestartAegisub() {
@@ -241,7 +150,7 @@ bool ForwardMouseWheelEvent(wxWindow *source, wxMouseEvent &evt) {
 	return false;
 }
 
-wxString GetClipboard() {
+std::string GetClipboard() {
 	wxString data;
 	wxClipboard *cb = wxClipboard::Get();
 	if (cb->Open()) {
@@ -252,13 +161,13 @@ wxString GetClipboard() {
 		}
 		cb->Close();
 	}
-	return data;
+	return from_wx(data);
 }
 
-void SetClipboard(wxString const& new_data) {
+void SetClipboard(std::string const& new_data) {
 	wxClipboard *cb = wxClipboard::Get();
 	if (cb->Open()) {
-		cb->SetData(new wxTextDataObject(new_data));
+		cb->SetData(new wxTextDataObject(to_wx(new_data)));
 		cb->Close();
 		cb->Flush();
 	}
@@ -273,64 +182,28 @@ void SetClipboard(wxBitmap const& new_data) {
 	}
 }
 
-namespace {
-class cache_cleaner : public wxThread {
-	wxString directory;
-	wxString file_type;
-	int64_t max_size;
-	size_t max_files;
-
-	ExitCode Entry() {
-		static wxMutex cleaning_mutex;
-		wxMutexLocker lock(cleaning_mutex);
-
-		if (!lock.IsOk()) {
-			LOG_D("utils/clean_cache") << "cleaning already in progress, thread exiting";
-			return (ExitCode)1;
-		}
-
-		wxDir cachedir;
-		if (!cachedir.Open(directory)) {
-			LOG_D("utils/clean_cache") << "couldn't open cache directory " << from_wx(directory);
-			return (wxThread::ExitCode)1;
+void CleanCache(agi::fs::path const& directory, std::string const& file_type, uint64_t max_size, uint64_t max_files) {
+	static std::unique_ptr<agi::dispatch::Queue> queue;
+	if (!queue)
+		queue = agi::dispatch::Create();
+
+	max_size <<= 20;
+	if (max_files == 0)
+		max_files = std::numeric_limits<uint64_t>::max();
+	queue->Async([=]{
+		LOG_D("utils/clean_cache") << "cleaning " << directory/file_type;
+		uint64_t total_size = 0;
+		std::multimap<int64_t, agi::fs::path> cachefiles;
+		for (auto const& file : agi::fs::DirectoryIterator(directory, file_type)) {
+			agi::fs::path path = directory/file;
+			cachefiles.insert(std::make_pair(agi::fs::ModifiedTime(path), path));
+			total_size += agi::fs::Size(path);
 		}
 
-		// sleep for a bit so we (hopefully) don't thrash the disk too much while indexing is in progress
-		wxThread::This()->Sleep(2000);
-
-		if (!cachedir.HasFiles(file_type)) {
-			LOG_D("utils/clean_cache") << "no files of the checked type in directory, exiting";
-			return (wxThread::ExitCode)0;
-		}
-
-		// unusually paranoid sanity check
-		// (someone might have deleted the file(s) after we did HasFiles() above; does wxDir.Open() lock the dir?)
-		wxString curfn_str;
-		if (!cachedir.GetFirst(&curfn_str, file_type, wxDIR_FILES)) {
-			LOG_D("utils/clean_cache") << "undefined error";
-			return (wxThread::ExitCode)1;
-		}
-
-		int64_t total_size = 0;
-		std::multimap<int64_t,wxFileName> cachefiles;
-		do {
-			wxFileName curfn(directory, curfn_str);
-			wxDateTime curatime;
-			curfn.GetTimes(&curatime, nullptr, nullptr);
-			cachefiles.insert(std::make_pair(curatime.GetTicks(), curfn));
-			total_size += curfn.GetSize().GetValue();
-
-			wxThread::This()->Sleep(250);
-		} while (cachedir.GetNext(&curfn_str));
-
 		if (cachefiles.size() <= max_files && total_size <= max_size) {
-			LOG_D("utils/clean_cache")
-				<< "cache does not need cleaning (maxsize=" << max_size
-				<< ", cursize=" << total_size
-				<< "; maxfiles=" << max_files
-				<< ", numfiles=" << cachefiles.size()
-				<< "), exiting";
-			return (wxThread::ExitCode)0;
+			LOG_D("utils/clean_cache") << boost::format("cache does not need cleaning (maxsize=%d, cursize=%d, maxfiles=%d, numfiles=%d), exiting")
+				% max_size % total_size % max_files % cachefiles.size();
+			return;
 		}
 
 		int deleted = 0;
@@ -339,59 +212,25 @@ class cache_cleaner : public wxThread {
 			if ((total_size <= max_size && cachefiles.size() - deleted <= max_files) || cachefiles.size() - deleted < 2)
 				break;
 
-			int64_t fsize = i.second.GetSize().GetValue();
-#ifdef __WXMSW__
-			int res = wxRemove(i.second.GetFullPath());
-#else
-			int res = unlink(i.second.GetFullPath().fn_str());
-#endif
-			if (res) {
-				LOG_D("utils/clean_cache") << "failed to remove file " << from_wx(i.second.GetFullPath());
+			uint64_t size = agi::fs::Size(i.second);
+			try {
+				agi::fs::Remove(i.second);
+				LOG_D("utils/clean_cache") << "deleted " << i.second;
+			}
+			catch  (agi::Exception const& e) {
+				LOG_D("utils/clean_cache") << "failed to delete file " << i.second << ": " << e.GetChainedMessage();
 				continue;
 			}
 
-			total_size -= fsize;
+			total_size -= size;
 			++deleted;
-
-			wxThread::This()->Sleep(250);
 		}
 
 		LOG_D("utils/clean_cache") << "deleted " << deleted << " files, exiting";
-		return (wxThread::ExitCode)0;
-	}
-
-public:
-	cache_cleaner(wxString const& directory, wxString const& file_type, int64_t max_size, int64_t max_files)
-	: wxThread(wxTHREAD_DETACHED)
-	, directory(directory)
-	, file_type(file_type)
-	, max_size(max_size << 20)
-	{
-		if (max_files < 1)
-			this->max_files = (size_t)-1;
-		else
-			this->max_files = (size_t)max_files;
-	}
-};
+	});
 }
 
-void CleanCache(wxString const& directory, wxString const& file_type, int64_t max_size, int64_t max_files) {
-	LOG_D("utils/clean_cache") << "attempting to start cleaner thread";
-	wxThread *CleaningThread = new cache_cleaner(directory, file_type, max_size, max_files);
-
-	if (CleaningThread->Create() != wxTHREAD_NO_ERROR) {
-		LOG_D("utils/clean_cache") << "thread creation failed";
-		delete CleaningThread;
-	}
-	else if (CleaningThread->Run() != wxTHREAD_NO_ERROR) {
-		LOG_D("utils/clean_cache") << "failed to start thread";
-		delete CleaningThread;
-	}
-
-	LOG_D("utils/clean_cache") << "thread started successfully";
-}
-
-size_t MaxLineLength(wxString const& text) {
+size_t MaxLineLength(std::string const& text) {
 	size_t max_line_length = 0;
 	size_t current_line_length = 0;
 	bool last_was_slash = false;
@@ -441,3 +280,4 @@ size_t MaxLineLength(wxString const& text) {
 void AddFullScreenButton(wxWindow *) { }
 void SetFloatOnParent(wxWindow *) { }
 #endif
+
diff --git a/aegisub/src/utils.h b/aegisub/src/utils.h
index 1ed48b3b3fd60a7c15c937977846d42c21d81764..aa31bdc7a15365deffa744822225ebbc70989f10 100644
--- a/aegisub/src/utils.h
+++ b/aegisub/src/utils.h
@@ -32,12 +32,12 @@
 /// @ingroup utility
 ///
 
-
 #pragma once
 
-#include <cstdint>
+#include <libaegisub/fs_fwd.h>
 
 #include <algorithm>
+#include <cstdint>
 #include <functional>
 #include <utility>
 #include <vector>
@@ -50,10 +50,6 @@
 class wxMouseEvent;
 class wxWindow;
 
-/// @brief Make a path relative to reference
-wxString MakeRelativePath(wxString path,wxString reference);
-/// @brief Extract original path from relative
-wxString DecodeRelativePath(wxString path,wxString reference);
 wxString AegiFloatToString(double value);
 wxString AegiIntegerToString(int value);
 wxString PrettySize(int bytes);
@@ -69,14 +65,8 @@ bool IsWhitespace(wchar_t c);
 /// Check if every character in str is whitespace
 bool StringEmptyOrWhitespace(const wxString &str);
 
-/// @brief String to integer
-///
-/// wxString::ToLong() is slow and not as flexible
-int AegiStringToInt(const wxString &str,int start=0,int end=-1);
-int AegiStringToFix(const wxString &str,size_t decimalPlaces,int start=0,int end=-1);
-
 /// Get the length in characters of the longest line in the given text
-size_t MaxLineLength(wxString const& text);
+size_t MaxLineLength(std::string const& text);
 
 /// @brief Launch a new copy of Aegisub.
 ///
@@ -102,7 +92,7 @@ bool ForwardMouseWheelEvent(wxWindow *source, wxMouseEvent &evt);
 /// @param file_type Wildcard pattern for files to clean up
 /// @param max_size Maximum size of directory in MB
 /// @param max_files Maximum number of files
-void CleanCache(wxString const& directory, wxString const& file_type, int64_t max_size, int64_t max_files = -1);
+void CleanCache(agi::fs::path const& directory, std::string const& file_type, uint64_t max_size, uint64_t max_files = -1);
 
 /// @brief Templated abs() function
 template <typename T> T tabs(T x) { return x < 0 ? -x : x; }
@@ -112,9 +102,9 @@ template <typename T> T tabs(T x) { return x < 0 ? -x : x; }
 template<typename T> inline T mid(T a, T b, T c) { return std::max(a, std::min(b, c)); }
 
 /// Get the text contents of the clipboard, or empty string on failure
-wxString GetClipboard();
+std::string GetClipboard();
 /// Try to set the clipboard to the given string
-void SetClipboard(wxString const& new_value);
+void SetClipboard(std::string const& new_value);
 void SetClipboard(wxBitmap const& new_value);
 
 #ifndef FORCEINLINE
@@ -144,36 +134,6 @@ void delete_clear(T& container) {
 	}
 }
 
-/// Helper class for background_delete_clear
-template<class Container>
-class BackgroundDeleter : public wxThread {
-	Container cont;
-	wxThread::ExitCode Entry() {
-		cont.clear_and_dispose(delete_ptr());
-		return (wxThread::ExitCode)0;
-	}
-public:
-	BackgroundDeleter(Container &source)
-	: wxThread(wxTHREAD_DETACHED)
-	{
-		using std::swap;
-		swap(cont, source);
-
-		Create();
-		SetPriority(WXTHREAD_MIN_PRIORITY);
-		Run();
-	}
-};
-
-/// Clear a container of pointers and delete the pointed to members on a
-/// background thread
-template<class T>
-void background_delete_clear(T& container) {
-	if (!container.empty())
-		new BackgroundDeleter<T>(container);
-}
-
-
 template<class Out>
 struct cast {
 	template<class In>
@@ -187,18 +147,5 @@ struct cast {
 	}
 };
 
-wxDECLARE_EVENT(EVT_CALL_THUNK, wxThreadEvent);
-
-template<typename Function>
-void InvokeOnMainThreadAsync(Function const& f) {
-	wxThreadEvent *evt = new wxThreadEvent(EVT_CALL_THUNK);
-	evt->SetPayload<std::function<void()>>(f);
-	wxTheApp->QueueEvent(evt);
-}
-
-template<typename Function>
-void InvokeOnMainThread(Function const& f) {
-	wxSemaphore sema(0, 1);
-	InvokeOnMainThreadAsync([&] { f(); sema.Post(); });
-	sema.Wait();
-}
+agi::fs::path OpenFileSelector(wxString const& message, std::string const& option_name, std::string const& default_filename, std::string const& default_extension, wxString const& wildcard, wxWindow *parent);
+agi::fs::path SaveFileSelector(wxString const& message, std::string const& option_name, std::string const& default_filename, std::string const& default_extension, wxString const& wildcard, wxWindow *parent);
diff --git a/aegisub/src/validators.cpp b/aegisub/src/validators.cpp
index 080d2d9bfb6e94bf7c98d8d3c104dfc670365eba..9cbce4b731e41d82f294435181766537c356da6f 100644
--- a/aegisub/src/validators.cpp
+++ b/aegisub/src/validators.cpp
@@ -34,10 +34,15 @@
 
 #include "config.h"
 
-#include <wx/textctrl.h>
+#include "validators.h"
 
+#include "compat.h"
 #include "utils.h"
-#include "validators.h"
+
+#include <libaegisub/exception.h>
+
+#include <wx/combobox.h>
+#include <wx/textctrl.h>
 
 NumValidator::NumValidator(wxString val, bool isfloat, bool issigned)
 : fValue(0)
@@ -202,3 +207,25 @@ bool NumValidator::TransferFromWindow() {
 
 	return true;
 }
+
+bool StringBinder::TransferFromWindow() {
+	wxWindow *window = GetWindow();
+	if (wxTextCtrl *ctrl = dynamic_cast<wxTextCtrl*>(window))
+		*value = from_wx(ctrl->GetValue());
+	else if (wxComboBox *ctrl = dynamic_cast<wxComboBox*>(window))
+		*value = from_wx(ctrl->GetValue());
+	else
+		throw agi::InternalError("Unsupported control type", 0);
+	return true;
+}
+
+bool StringBinder::TransferToWindow() {
+	wxWindow *window = GetWindow();
+	if (wxTextCtrl *ctrl = dynamic_cast<wxTextCtrl*>(window))
+		ctrl->SetValue(to_wx(*value));
+	else if (wxComboBox *ctrl = dynamic_cast<wxComboBox*>(window))
+		ctrl->SetValue(to_wx(*value));
+	else
+		throw agi::InternalError("Unsupported control type", 0);
+	return true;
+}
diff --git a/aegisub/src/validators.h b/aegisub/src/validators.h
index fc07ec24b412f34411ccfca5f0b0f50690ab4421..51bdf4bb728a4efd9cea5640af544a987a5897c1 100644
--- a/aegisub/src/validators.h
+++ b/aegisub/src/validators.h
@@ -34,6 +34,8 @@
 
 #include <libaegisub/exception.h>
 
+#include <string>
+
 #include <wx/radiobox.h>
 #include <wx/validate.h>
 
@@ -123,3 +125,15 @@ template<typename T>
 EnumBinder<T> MakeEnumBinder(T *value) {
 	return EnumBinder<T>(value);
 }
+
+class StringBinder : public wxValidator {
+	std::string *value;
+
+	wxObject* Clone() const { return new StringBinder(value); }
+	bool Validate(wxWindow*) { return true;}
+	bool TransferToWindow();
+	bool TransferFromWindow();
+
+public:
+	explicit StringBinder(std::string *value) : value(value) { }
+};
diff --git a/aegisub/src/vector2d.cpp b/aegisub/src/vector2d.cpp
index b39ea6a568754ac100605864ea0beb9366692412..ab6d492a9af4cd8eb7b933212449b68e02290e87 100644
--- a/aegisub/src/vector2d.cpp
+++ b/aegisub/src/vector2d.cpp
@@ -23,6 +23,7 @@
 
 #include "vector2d.h"
 
+#include <boost/format.hpp>
 #include <limits>
 
 Vector2D::Vector2D()
@@ -77,21 +78,21 @@ Vector2D::operator unspecified_bool_type() const {
 	return *this == Vector2D() ? 0 : &Vector2D::x;
 }
 
-wxString Vector2D::PStr(char sep) const {
+std::string Vector2D::PStr(char sep) const {
 	return "(" + Str(sep) + ")";
 }
 
-wxString Vector2D::DStr(char sep) const {
-	return wxString::Format("%d%c%d", (int)x, sep, (int)y);
+std::string Vector2D::DStr(char sep) const {
+	return str(boost::format("%d%c%d") % (int)x % sep % (int)y);
 }
 
-static wxString float_to_string(float val) {
-	wxString s = wxString::Format("%.3f", val);
+static std::string float_to_string(float val) {
+	std::string s = str(boost::format("%.3f") % val);
 	size_t pos = s.find_last_not_of("0");
 	if (pos != s.find(".")) ++pos;
-	return s.Left(pos);
+	return s.substr(0, pos);
 }
 
-wxString Vector2D::Str(char sep) const {
+std::string Vector2D::Str(char sep) const {
 	return float_to_string(x) + sep + float_to_string(y);
 }
diff --git a/aegisub/src/vector2d.h b/aegisub/src/vector2d.h
index 146192f1142364028eca2f11e5e73f3942453ddf..41811c70988e6538b957d148662b640b7d63060d 100644
--- a/aegisub/src/vector2d.h
+++ b/aegisub/src/vector2d.h
@@ -22,7 +22,7 @@
 #pragma once
 
 #include <cmath>
-
+#include <string>
 #include <wx/gdicmn.h>
 
 class Vector2D {
@@ -71,11 +71,11 @@ public:
 	float Angle() const { return atan2(y, x); }
 
 	/// Get as string with given separator
-	wxString Str(char sep = ',') const;
+	std::string Str(char sep = ',') const;
 	/// Get as string surrounded by parentheses with given separator
-	wxString PStr(char sep = ',') const;
+	std::string PStr(char sep = ',') const;
 	/// Get as string with given separator with values rounded to ints
-	wxString DStr(char sep = ',') const;
+	std::string DStr(char sep = ',') const;
 
 	static Vector2D FromAngle(float angle) { return Vector2D(cos(-angle), sin(-angle)); }
 };
diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp
index f2d6e93013584d133328b6034ecf511138e7a903..0cd32b060a7633eb1111c63c6a0b7997366969d5 100644
--- a/aegisub/src/video_context.cpp
+++ b/aegisub/src/video_context.cpp
@@ -34,20 +34,10 @@
 
 #include "config.h"
 
-#include <cstring>
-
-#include <wx/clipbrd.h>
-#include <wx/config.h>
-#include <wx/filename.h>
-#include <wx/image.h>
-#include <wx/msgdlg.h>
-
-#include <libaegisub/keyframe.h>
-#include <libaegisub/log.h>
+#include "video_context.h"
 
 #include "ass_dialogue.h"
 #include "ass_file.h"
-#include "ass_style.h"
 #include "ass_time.h"
 #include "audio_controller.h"
 #include "compat.h"
@@ -56,21 +46,25 @@
 #include "mkv_wrap.h"
 #include "options.h"
 #include "selection_controller.h"
-#include "standard_paths.h"
 #include "time_range.h"
 #include "threaded_frame_source.h"
 #include "utils.h"
-#include "video_context.h"
 #include "video_frame.h"
 
+#include <libaegisub/fs.h>
+#include <libaegisub/keyframe.h>
+#include <libaegisub/path.h>
+
+#include <wx/msgdlg.h>
+
 VideoContext::VideoContext()
 : playback(this)
-, startMS(0)
-, endFrame(0)
+, start_ms(0)
+, end_frame(0)
 , frame_n(0)
-, arValue(1.)
-, arType(0)
-, hasSubtitles(false)
+, ar_value(1.)
+, ar_type(0)
+, has_subtitles(false)
 , playAudioOnStep(OPT_GET("Audio/Plays When Stepping Video"))
 , no_amend(false)
 {
@@ -99,24 +93,24 @@ VideoContext *VideoContext::Get() {
 }
 
 void VideoContext::Reset() {
-	StandardPaths::SetPathValue("?video", "");
+	config::path->SetToken("?video", "");
 
 	// Remove video data
 	Stop();
 	frame_n = 0;
 
 	// Clean up video data
-	videoFile.clear();
+	video_filename.clear();
 
 	// Remove provider
 	provider.reset();
-	videoProvider = 0;
+	video_provider = 0;
 
-	keyFrames.clear();
-	keyFramesFilename.clear();
-	videoFPS = agi::vfr::Framerate();
-	KeyframesOpen(keyFrames);
-	if (!ovrFPS.IsLoaded()) TimecodesOpen(videoFPS);
+	keyframes.clear();
+	keyframes_filename.clear();
+	video_fps = agi::vfr::Framerate();
+	KeyframesOpen(keyframes);
+	if (!ovr_fps.IsLoaded()) TimecodesOpen(video_fps);
 }
 
 void VideoContext::SetContext(agi::Context *context) {
@@ -125,7 +119,7 @@ void VideoContext::SetContext(agi::Context *context) {
 	context->ass->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
 }
 
-void VideoContext::SetVideo(const wxString &filename) {
+void VideoContext::SetVideo(const agi::fs::path &filename) {
 	Reset();
 	if (filename.empty()) {
 		VideoOpen();
@@ -135,8 +129,8 @@ void VideoContext::SetVideo(const wxString &filename) {
 	bool commit_subs = false;
 	try {
 		provider.reset(new ThreadedFrameSource(filename, this));
-		videoProvider = provider->GetVideoProvider();
-		videoFile = filename;
+		video_provider = provider->GetVideoProvider();
+		video_filename = filename;
 
 		// Check that the script resolution matches the video resolution
 		int sx = context->ass->GetScriptInfoAsInt("PlayResX");
@@ -147,8 +141,8 @@ void VideoContext::SetVideo(const wxString &filename) {
 		// If the script resolution hasn't been set at all just force it to the
 		// video resolution
 		if (sx == 0 && sy == 0) {
-			context->ass->SetScriptInfo("PlayResX", wxString::Format("%d", vx));
-			context->ass->SetScriptInfo("PlayResY", wxString::Format("%d", vy));
+			context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
+			context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
 			commit_subs = true;
 		}
 		// If it has been set to something other than a multiple of the video
@@ -165,8 +159,8 @@ void VideoContext::SetVideo(const wxString &filename) {
 					break;
 				// Fallthrough to case 2
 			case 2: // Always change script res
-				context->ass->SetScriptInfo("PlayResX", wxString::Format("%d", vx));
-				context->ass->SetScriptInfo("PlayResY", wxString::Format("%d", vy));
+				context->ass->SetScriptInfo("PlayResX", std::to_string(vx));
+				context->ass->SetScriptInfo("PlayResY", std::to_string(vy));
 				commit_subs = true;
 				break;
 			default: // Never change
@@ -174,44 +168,44 @@ void VideoContext::SetVideo(const wxString &filename) {
 			}
 		}
 
-		keyFrames = videoProvider->GetKeyFrames();
+		keyframes = video_provider->GetKeyFrames();
 
 		// Set frame rate
-		videoFPS = videoProvider->GetFPS();
-		if (ovrFPS.IsLoaded()) {
+		video_fps = video_provider->GetFPS();
+		if (ovr_fps.IsLoaded()) {
 			int ovr = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"), _("Replace timecodes?"), wxYES_NO | wxICON_QUESTION);
 			if (ovr == wxYES) {
-				ovrFPS = agi::vfr::Framerate();
-				ovrTimecodeFile.clear();
+				ovr_fps = agi::vfr::Framerate();
+				timecodes_filename.clear();
 			}
 		}
 
 		// Set aspect ratio
-		double dar = videoProvider->GetDAR();
+		double dar = video_provider->GetDAR();
 		if (dar > 0)
 			SetAspectRatio(4, dar);
 
 		// Set filename
-		config::mru->Add("Video", from_wx(filename));
-		StandardPaths::SetPathValue("?video", wxFileName(filename).GetPath());
+		config::mru->Add("Video", filename);
+		config::path->SetToken("?video", filename);
 
 		// Show warning
-		wxString warning = videoProvider->GetWarning();
-		if (!warning.empty()) wxMessageBox(warning, "Warning", wxICON_WARNING | wxOK);
+		std::string warning = video_provider->GetWarning();
+		if (!warning.empty())
+			wxMessageBox(to_wx(warning), "Warning", wxICON_WARNING | wxOK);
 
-		hasSubtitles = false;
-		if (filename.Right(4).Lower() == ".mkv") {
-			hasSubtitles = MatroskaWrapper::HasSubtitles(filename);
-		}
+		has_subtitles = false;
+		if (agi::fs::HasExtension(filename, "mkv"))
+			has_subtitles = MatroskaWrapper::HasSubtitles(filename);
 
 		provider->LoadSubtitles(context->ass);
 		VideoOpen();
-		KeyframesOpen(keyFrames);
+		KeyframesOpen(keyframes);
 		TimecodesOpen(FPS());
 	}
 	catch (agi::UserCancelException const&) { }
-	catch (agi::FileNotAccessibleError const& err) {
-		config::mru->Remove("Video", from_wx(filename));
+	catch (agi::fs::FileSystemError const& err) {
+		config::mru->Remove("Video", filename);
 		wxMessageBox(to_wx(err.GetMessage()), "Error setting video", wxOK | wxICON_ERROR | wxCENTER);
 	}
 	catch (VideoProviderError const& err) {
@@ -227,7 +221,7 @@ void VideoContext::SetVideo(const wxString &filename) {
 void VideoContext::Reload() {
 	if (IsLoaded()) {
 		int frame = frame_n;
-		SetVideo(videoFile.Clone());
+		SetVideo(agi::fs::path(video_filename)); // explicitly copy videoFile since it's cleared in SetVideo
 		JumpToFrame(frame);
 	}
 }
@@ -248,8 +242,8 @@ void VideoContext::OnSubtitlesCommit(int type, std::set<const AssEntry *> const&
 void VideoContext::OnSubtitlesSave() {
 	no_amend = true;
 
-	context->ass->SetScriptInfo("VFR File", MakeRelativePath(GetTimecodesName(), context->ass->filename));
-	context->ass->SetScriptInfo("Keyframes File", MakeRelativePath(GetKeyFramesName(), context->ass->filename));
+	context->ass->SetScriptInfo("VFR File", config::path->MakeRelative(GetTimecodesName(), "?script").generic_string());
+	context->ass->SetScriptInfo("Keyframes File", config::path->MakeRelative(GetKeyFramesName(), "?script").generic_string());
 
 	if (!IsLoaded()) {
 		context->ass->SetScriptInfo("Video File", "");
@@ -258,16 +252,16 @@ void VideoContext::OnSubtitlesSave() {
 		return;
 	}
 
-	wxString ar;
-	if (arType == 4)
-		ar = wxString::Format("c%g", arValue);
+	std::string ar;
+	if (ar_type == 4)
+		ar = "c" + std::to_string(ar_value);
 	else
-		ar = wxString::Format("%d", arType);
+		ar = "c" + std::to_string(ar_type);
 
-	context->ass->SetScriptInfo("Video File", MakeRelativePath(videoFile, context->ass->filename));
-	context->ass->SetScriptInfo("YCbCr Matrix", videoProvider->GetColorSpace());
+	context->ass->SetScriptInfo("Video File", config::path->MakeRelative(video_filename, "?script").generic_string());
+	context->ass->SetScriptInfo("YCbCr Matrix", video_provider->GetColorSpace());
 	context->ass->SetScriptInfo("Video Aspect Ratio", ar);
-	context->ass->SetScriptInfo("Video Position", wxString::Format("%d", frame_n));
+	context->ass->SetScriptInfo("Video Position", std::to_string(frame_n));
 }
 
 void VideoContext::JumpToFrame(int n) {
@@ -291,44 +285,33 @@ void VideoContext::JumpToTime(int ms, agi::vfr::Time end) {
 }
 
 void VideoContext::GetFrameAsync(int n) {
-	provider->RequestFrame(n, TimeAtFrame(n) / 1000.0);
+	provider->RequestFrame(n, TimeAtFrame(n));
 }
 
 std::shared_ptr<AegiVideoFrame> VideoContext::GetFrame(int n, bool raw) {
-	return provider->GetFrame(n, TimeAtFrame(n) / 1000.0, raw);
+	return provider->GetFrame(n, TimeAtFrame(n), raw);
 }
 
-int VideoContext::GetWidth() const {
-	return videoProvider->GetWidth();
-}
-int VideoContext::GetHeight() const {
-	return videoProvider->GetHeight();
-}
-
-int VideoContext::GetLength() const {
-	return videoProvider->GetFrameCount();
-}
+int VideoContext::GetWidth() const { return video_provider->GetWidth(); }
+int VideoContext::GetHeight() const { return video_provider->GetHeight(); }
+int VideoContext::GetLength() const { return video_provider->GetFrameCount(); }
 
 void VideoContext::NextFrame() {
-	if (!videoProvider || IsPlaying() || frame_n == videoProvider->GetFrameCount())
+	if (!video_provider || IsPlaying() || frame_n == video_provider->GetFrameCount())
 		return;
 
 	JumpToFrame(frame_n + 1);
-	// Start playing audio
-	if (playAudioOnStep->GetBool()) {
+	if (playAudioOnStep->GetBool())
 		context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n - 1), TimeAtFrame(frame_n)));
-	}
 }
 
 void VideoContext::PrevFrame() {
-	if (!videoProvider || IsPlaying() || frame_n == 0)
+	if (!video_provider || IsPlaying() || frame_n == 0)
 		return;
 
 	JumpToFrame(frame_n - 1);
-	// Start playing audio
-	if (playAudioOnStep->GetBool()) {
+	if (playAudioOnStep->GetBool())
 		context->audioController->PlayRange(TimeRange(TimeAtFrame(frame_n), TimeAtFrame(frame_n + 1)));
-	}
 }
 
 void VideoContext::Play() {
@@ -339,15 +322,12 @@ void VideoContext::Play() {
 
 	if (!IsLoaded()) return;
 
-	// Set variables
-	startMS = TimeAtFrame(frame_n);
-	endFrame = GetLength() - 1;
+	start_ms = TimeAtFrame(frame_n);
+	end_frame = GetLength() - 1;
 
-	// Start playing audio
-	context->audioController->PlayToEnd(startMS);
+	context->audioController->PlayToEnd(start_ms);
 
-	// Start timer
-	playTime.Start();
+	playback_start_time = std::chrono::steady_clock::now();
 	playback.Start(10);
 }
 
@@ -357,19 +337,16 @@ void VideoContext::PlayLine() {
 	AssDialogue *curline = context->selectionController->GetActiveLine();
 	if (!curline) return;
 
-	// Start playing audio
 	context->audioController->PlayRange(TimeRange(curline->Start, curline->End));
 
 	// Round-trip conversion to convert start to exact
-	int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start,agi::vfr::START);
-	startMS = TimeAtFrame(startFrame);
-	endFrame = FrameAtTime(context->selectionController->GetActiveLine()->End,agi::vfr::END) + 1;
+	int startFrame = FrameAtTime(context->selectionController->GetActiveLine()->Start, agi::vfr::START);
+	start_ms = TimeAtFrame(startFrame);
+	end_frame = FrameAtTime(context->selectionController->GetActiveLine()->End, agi::vfr::END) + 1;
 
-	// Jump to start
 	JumpToFrame(startFrame);
 
-	// Start timer
-	playTime.Start();
+	playback_start_time = std::chrono::steady_clock::now();
 	playback.Start(10);
 }
 
@@ -381,21 +358,17 @@ void VideoContext::Stop() {
 }
 
 void VideoContext::OnPlayTimer(wxTimerEvent &) {
-	int nextFrame = FrameAtTime(startMS + playTime.Time());
+	using namespace std::chrono;
+	int next_frame = FrameAtTime(start_ms + duration_cast<milliseconds>(steady_clock::now() - playback_start_time).count());
+	if (next_frame == frame_n) return;
 
-	// Same frame
-	if (nextFrame == frame_n) return;
-
-	// End
-	if (nextFrame >= endFrame) {
+	if (next_frame >= end_frame)
 		Stop();
-		return;
+	else {
+		frame_n = next_frame;
+		GetFrameAsync(frame_n);
+		Seek(frame_n);
 	}
-
-	// Jump to next frame
-	frame_n = nextFrame;
-	GetFrameAsync(frame_n);
-	Seek(frame_n);
 }
 
 double VideoContext::GetARFromType(int type) const {
@@ -409,87 +382,83 @@ double VideoContext::GetARFromType(int type) const {
 void VideoContext::SetAspectRatio(int type, double value) {
 	if (type != 4) value = GetARFromType(type);
 
-	arType = type;
-	arValue = mid(.5, value, 5.);
-	ARChange(arType, arValue);
+	ar_type = type;
+	ar_value = mid(.5, value, 5.);
+	ARChange(ar_type, ar_value);
 }
 
-void VideoContext::LoadKeyframes(wxString filename) {
-	if (filename == keyFramesFilename || filename.empty()) return;
+void VideoContext::LoadKeyframes(agi::fs::path const& filename) {
+	if (filename == keyframes_filename || filename.empty()) return;
 	try {
-		keyFrames = agi::keyframe::Load(from_wx(filename));
-		keyFramesFilename = filename;
-		KeyframesOpen(keyFrames);
-		config::mru->Add("Keyframes", from_wx(filename));
+		keyframes = agi::keyframe::Load(filename);
+		keyframes_filename = filename;
+		KeyframesOpen(keyframes);
+		config::mru->Add("Keyframes", filename);
 	}
 	catch (agi::keyframe::Error const& err) {
 		wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
-		config::mru->Remove("Keyframes", from_wx(filename));
+		config::mru->Remove("Keyframes", filename);
 	}
-	catch (agi::FileSystemError const&) {
-		wxLogError("Could not open file " + filename);
-		config::mru->Remove("Keyframes", from_wx(filename));
+	catch (agi::fs::FileSystemError const& err) {
+		wxMessageBox(to_wx(err.GetMessage()), "Error opening keyframes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
+		config::mru->Remove("Keyframes", filename);
 	}
 }
 
-void VideoContext::SaveKeyframes(wxString filename) {
-	agi::keyframe::Save(from_wx(filename), GetKeyFrames());
-	config::mru->Add("Keyframes", from_wx(filename));
+void VideoContext::SaveKeyframes(agi::fs::path const& filename) {
+	agi::keyframe::Save(filename, GetKeyFrames());
+	config::mru->Add("Keyframes", filename);
 }
 
 void VideoContext::CloseKeyframes() {
-	keyFramesFilename.clear();
-	if (videoProvider)
-		keyFrames = videoProvider->GetKeyFrames();
+	keyframes_filename.clear();
+	if (video_provider)
+		keyframes = video_provider->GetKeyFrames();
 	else
-		keyFrames.clear();
-	KeyframesOpen(keyFrames);
+		keyframes.clear();
+	KeyframesOpen(keyframes);
 }
 
-void VideoContext::LoadTimecodes(wxString filename) {
-	if (filename == ovrTimecodeFile || filename.empty()) return;
+void VideoContext::LoadTimecodes(agi::fs::path const& filename) {
+	if (filename == timecodes_filename || filename.empty()) return;
 	try {
-		ovrFPS = agi::vfr::Framerate(from_wx(filename));
-		ovrTimecodeFile = filename;
-		config::mru->Add("Timecodes", from_wx(filename));
+		ovr_fps = agi::vfr::Framerate(filename);
+		timecodes_filename = filename;
+		config::mru->Add("Timecodes", filename);
 		OnSubtitlesCommit(0, std::set<const AssEntry*>());
-		TimecodesOpen(ovrFPS);
+		TimecodesOpen(ovr_fps);
 	}
-	catch (const agi::FileSystemError&) {
-		wxLogError("Could not open file " + filename);
-		config::mru->Remove("Timecodes", from_wx(filename));
+	catch (agi::fs::FileSystemError const& err) {
+		wxMessageBox(to_wx(err.GetMessage()), "Error opening timecodes file", wxOK | wxICON_ERROR | wxCENTER, context->parent);
+		config::mru->Remove("Timecodes", filename);
 	}
 	catch (const agi::vfr::Error& e) {
-		wxLogError("Timecode file parse error: %s", e.GetMessage());
+		wxLogError("Timecode file parse error: %s", to_wx(e.GetMessage()));
+		config::mru->Remove("Timecodes", filename);
 	}
 }
-void VideoContext::SaveTimecodes(wxString filename) {
+void VideoContext::SaveTimecodes(agi::fs::path const& filename) {
 	try {
-		FPS().Save(from_wx(filename), IsLoaded() ? GetLength() : -1);
-		config::mru->Add("Timecodes", from_wx(filename));
+		FPS().Save(filename, IsLoaded() ? GetLength() : -1);
+		config::mru->Add("Timecodes", filename);
 	}
-	catch(const agi::FileSystemError&) {
-		wxLogError("Could not write to " + filename);
+	catch (agi::fs::FileSystemError const& err) {
+		wxMessageBox(to_wx(err.GetMessage()), "Error saving timecodes", wxOK | wxICON_ERROR | wxCENTER, context->parent);
 	}
 }
 void VideoContext::CloseTimecodes() {
-	ovrFPS = agi::vfr::Framerate();
-	ovrTimecodeFile.clear();
+	ovr_fps = agi::vfr::Framerate();
+	timecodes_filename.clear();
 	OnSubtitlesCommit(0, std::set<const AssEntry*>());
-	TimecodesOpen(videoFPS);
+	TimecodesOpen(video_fps);
 }
 
 int VideoContext::TimeAtFrame(int frame, agi::vfr::Time type) const {
-	if (ovrFPS.IsLoaded()) {
-		return ovrFPS.TimeAtFrame(frame, type);
-	}
-	return videoFPS.TimeAtFrame(frame, type);
+	return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).TimeAtFrame(frame, type);
 }
+
 int VideoContext::FrameAtTime(int time, agi::vfr::Time type) const {
-	if (ovrFPS.IsLoaded()) {
-		return ovrFPS.FrameAtTime(time, type);
-	}
-	return videoFPS.FrameAtTime(time, type);
+	return (ovr_fps.IsLoaded() ? ovr_fps : video_fps).FrameAtTime(time, type);
 }
 
 void VideoContext::OnVideoError(VideoProviderErrorEvent const& err) {
@@ -503,10 +472,3 @@ void VideoContext::OnSubtitlesError(SubtitlesProviderErrorEvent const& err) {
 		"Failed rendering subtitles. Error message reported: %s",
 		to_wx(err.GetMessage()));
 }
-
-void VideoContext::OnExit() {
-	// On unix wxThreadModule will shut down any still-running threads (and
-	// display a warning that it's doing so) before the destructor for
-	// VideoContext runs, so manually kill the thread
-	Get()->provider.reset();
-}
diff --git a/aegisub/src/video_context.h b/aegisub/src/video_context.h
index d69ac97497f947da67c0a99698b85db6b394b9a3..eb6a5b91b6b00b070b71348ee9e8a7db5faa2a49 100644
--- a/aegisub/src/video_context.h
+++ b/aegisub/src/video_context.h
@@ -32,24 +32,25 @@
 /// @ingroup video
 ///
 
+#include <libaegisub/fs_fwd.h>
+#include <libaegisub/signal.h>
+#include <libaegisub/vfr.h>
+
+#include <boost/filesystem/path.hpp>
+#include <chrono>
 #include <ctime>
 #include <list>
 #include <memory>
 #include <set>
 
 #include <wx/timer.h>
-#include <wx/stopwatch.h>
-
-#include <libaegisub/scoped_ptr.h>
-#include <libaegisub/signal.h>
-#include <libaegisub/vfr.h>
 
 class AegiVideoFrame;
 class AssEntry;
-class SubtitlesProviderErrorEvent;
+struct SubtitlesProviderErrorEvent;
 class ThreadedFrameSource;
 class VideoProvider;
-class VideoProviderErrorEvent;
+struct VideoProviderErrorEvent;
 
 namespace agi {
 	struct Context;
@@ -78,33 +79,33 @@ class VideoContext : public wxEvtHandler {
 
 	/// The video provider owned by the threaded frame source, or nullptr if no
 	/// video is open
-	VideoProvider *videoProvider;
+	VideoProvider *video_provider;
 
 	/// Asynchronous provider of video frames
-	agi::scoped_ptr<ThreadedFrameSource> provider;
+	std::unique_ptr<ThreadedFrameSource> provider;
 
 	/// Filename of currently open video
-	wxString videoFile;
+	agi::fs::path video_filename;
 
 	/// List of frame numbers which are keyframes
-	std::vector<int> keyFrames;
+	std::vector<int> keyframes;
 
 	/// File name of the currently open keyframes or empty if keyframes are not overridden
-	wxString keyFramesFilename;
+	agi::fs::path keyframes_filename;
 
 	/// Playback timer used to periodically check if we should go to the next
 	/// frame while playing video
 	wxTimer playback;
 
-	/// Time since playback was last started
-	wxStopWatch playTime;
+	/// Time when playback was last started
+	std::chrono::steady_clock::time_point playback_start_time;
 
 	/// The start time of the first frame of the current playback; undefined if
 	/// video is not currently playing
-	int startMS;
+	int start_ms;
 
 	/// The last frame to play if video is currently playing
-	int endFrame;
+	int end_frame;
 
 	/// The frame number which was last requested from the video provider,
 	/// which may not be the same thing as the currently displayed frame
@@ -112,20 +113,20 @@ class VideoContext : public wxEvtHandler {
 
 	/// The picture aspect ratio of the video if the aspect ratio has been
 	/// overridden by the user
-	double arValue;
+	double ar_value;
 
 	/// @brief The current AR type
 	///
 	/// 0 is square pixels; 1-3 are predefined ARs; 4 is custom, where the real
 	/// AR is in arValue
-	int arType;
+	int ar_type;
 
 	/// Does the currently loaded video file have subtitles muxed into it?
-	bool hasSubtitles;
+	bool has_subtitles;
 
 	/// Filename of the currently loaded timecodes file, or empty if timecodes
 	/// have not been overridden
-	wxString ovrTimecodeFile;
+	agi::fs::path timecodes_filename;
 
 	/// Cached option for audio playing when frame stepping
 	const agi::OptionValue* playAudioOnStep;
@@ -139,9 +140,9 @@ class VideoContext : public wxEvtHandler {
 	void OnPlayTimer(wxTimerEvent &event);
 
 	/// The timecodes from the video file
-	agi::vfr::Framerate videoFPS;
+	agi::vfr::Framerate video_fps;
 	/// External timecode which have been loaded, if any
-	agi::vfr::Framerate ovrFPS;
+	agi::vfr::Framerate ovr_fps;
 
 	void OnVideoError(VideoProviderErrorEvent const& err);
 	void OnSubtitlesError(SubtitlesProviderErrorEvent const& err);
@@ -164,7 +165,7 @@ public:
 	void SetContext(agi::Context *context);
 
 	/// @brief Get the video provider used for the currently open video
-	VideoProvider *GetProvider() const { return videoProvider; }
+	VideoProvider *GetProvider() const { return video_provider; }
 
 	/// Synchronously get a video frame
 	/// @param n Frame number to get
@@ -177,16 +178,16 @@ public:
 	void GetFrameAsync(int n);
 
 	/// Is there a video loaded?
-	bool IsLoaded() const { return !!videoProvider; }
+	bool IsLoaded() const { return !!video_provider; }
 
 	/// Get the file name of the currently open video, if any
-	wxString GetVideoName() const { return videoFile; }
+	agi::fs::path GetVideoName() const { return video_filename; }
 
 	/// Is the video currently playing?
 	bool IsPlaying() const { return playback.IsRunning(); }
 
 	/// Does the video file loaded have muxed subtitles that we can load?
-	bool HasSubtitles() const { return hasSubtitles; }
+	bool HasSubtitles() const { return has_subtitles; }
 
 	/// Get the width of the currently open video
 	int GetWidth() const;
@@ -209,14 +210,14 @@ public:
 	void SetAspectRatio(int type, double value=1.0);
 
 	/// Get the current AR type
-	int GetAspectRatioType() const { return arType; }
+	int GetAspectRatioType() const { return ar_type; }
 
 	/// Get the current aspect ratio of the video
-	double GetAspectRatioValue() const { return arValue; }
+	double GetAspectRatioValue() const { return ar_value; }
 
 	/// @brief Open a new video
 	/// @param filename Video to open, or empty to close the current video
-	void SetVideo(const wxString &filename);
+	void SetVideo(const agi::fs::path &filename);
 	/// @brief Close and reopen the current video
 	void Reload();
 
@@ -245,27 +246,26 @@ public:
 	DEFINE_SIGNAL_ADDERS(TimecodesOpen, AddTimecodesListener)
 	DEFINE_SIGNAL_ADDERS(ARChange, AddARChangeListener)
 
-	const std::vector<int>& GetKeyFrames() const { return keyFrames; };
-	wxString GetKeyFramesName() const { return keyFramesFilename; }
-	void LoadKeyframes(wxString filename);
-	void SaveKeyframes(wxString filename);
+	const std::vector<int>& GetKeyFrames() const { return keyframes; };
+	agi::fs::path GetKeyFramesName() const { return keyframes_filename; }
+	void LoadKeyframes(agi::fs::path const& filename);
+	void SaveKeyframes(agi::fs::path const& filename);
 	void CloseKeyframes();
-	bool OverKeyFramesLoaded() const { return !keyFramesFilename.empty(); }
-	bool KeyFramesLoaded() const { return !keyFrames.empty(); }
+	bool OverKeyFramesLoaded() const { return !keyframes_filename.empty(); }
+	bool KeyFramesLoaded() const { return !keyframes.empty(); }
 
-	wxString GetTimecodesName() const { return ovrTimecodeFile; }
-	void LoadTimecodes(wxString filename);
-	void SaveTimecodes(wxString filename);
+	agi::fs::path GetTimecodesName() const { return timecodes_filename; }
+	void LoadTimecodes(agi::fs::path const& filename);
+	void SaveTimecodes(agi::fs::path const& filename);
 	void CloseTimecodes();
-	bool OverTimecodesLoaded() const { return ovrFPS.IsLoaded(); }
-	bool TimecodesLoaded() const { return videoFPS.IsLoaded() || ovrFPS.IsLoaded(); };
+	bool OverTimecodesLoaded() const { return ovr_fps.IsLoaded(); }
+	bool TimecodesLoaded() const { return video_fps.IsLoaded() || ovr_fps.IsLoaded(); };
 
-	const agi::vfr::Framerate& FPS() const { return ovrFPS.IsLoaded() ? ovrFPS : videoFPS; }
-	const agi::vfr::Framerate& VideoFPS() const { return videoFPS; }
+	const agi::vfr::Framerate& FPS() const { return ovr_fps.IsLoaded() ? ovr_fps : video_fps; }
+	const agi::vfr::Framerate& VideoFPS() const { return video_fps; }
 
 	int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
 	int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
 
 	static VideoContext *Get();
-	static void OnExit();
 };
diff --git a/aegisub/src/video_display.cpp b/aegisub/src/video_display.cpp
index ce1963b89db5d9a2548eda62f51bec92182fbb30..834d326500d454972fedf8db9317d1fe3cd32fdc 100644
--- a/aegisub/src/video_display.cpp
+++ b/aegisub/src/video_display.cpp
@@ -54,6 +54,7 @@
 
 #include "ass_file.h"
 #include "command/command.h"
+#include "compat.h"
 #include "include/aegisub/context.h"
 #include "include/aegisub/hotkey.h"
 #include "include/aegisub/menu.h"
@@ -75,7 +76,7 @@ int attribList[] = { WX_GL_RGBA , WX_GL_DOUBLEBUFFER, WX_GL_STENCIL_SIZE, 8, 0 }
 class OpenGlException : public agi::Exception {
 public:
 	OpenGlException(const char *func, int err)
-		: agi::Exception(from_wx(wxString::Format("%s failed with error code %d", func, err)))
+	: agi::Exception(from_wx(wxString::Format("%s failed with error code %d", func, err)))
 	{ }
 	const char * GetName() const { return "video/opengl"; }
 	Exception * Copy() const { return new OpenGlException(*this); }
@@ -432,5 +433,5 @@ void VideoDisplay::Unload() {
 }
 
 void VideoDisplay::OnSubtitlesSave() {
-	con->ass->SetScriptInfo("Video Zoom Percent", wxString::Format("%g", zoomValue));
+	con->ass->SetScriptInfo("Video Zoom Percent", std::to_string(zoomValue));
 }
diff --git a/aegisub/src/video_display.h b/aegisub/src/video_display.h
index e0a5fbca87a25b4821b5c4141aacfa47e92422f1..e65dee5c4738def45f95655efabe9306fa02b466 100644
--- a/aegisub/src/video_display.h
+++ b/aegisub/src/video_display.h
@@ -47,7 +47,7 @@
 
 // Prototypes
 class AegiVideoFrame;
-class FrameReadyEvent;
+struct FrameReadyEvent;
 class VideoContext;
 class VideoOutGL;
 class VisualToolBase;
diff --git a/aegisub/src/video_out_gl.h b/aegisub/src/video_out_gl.h
index 222c85ffd4b3ff693fe4398705aa4c37d581b6e6..a362bda6da065edecfff2671a55cef46b92578b5 100644
--- a/aegisub/src/video_out_gl.h
+++ b/aegisub/src/video_out_gl.h
@@ -36,8 +36,6 @@
 
 #include <vector>
 
-#include "compat.h"
-
 class AegiVideoFrame;
 
 /// @class VideoOutGL
@@ -104,22 +102,22 @@ DEFINE_BASE_EXCEPTION_NOINNER(VideoOutException, agi::Exception)
 
 /// @class VideoOutRenderException
 /// @extends VideoOutException
-/// @brief An OpenGL error occured while uploading or displaying a frame
+/// @brief An OpenGL error occurred while uploading or displaying a frame
 class VideoOutRenderException : public VideoOutException {
 public:
 	VideoOutRenderException(const char *func, int err)
-		: VideoOutException(from_wx(wxString::Format("%s failed with error code %d", func, err)))
+	: VideoOutException(std::string(func) + " failed with error code " + std::to_string(err))
 	{ }
 	const char * GetName() const { return "videoout/opengl/render"; }
 	Exception * Copy() const { return new VideoOutRenderException(*this); }
 };
 /// @class VideoOutOpenGLException
 /// @extends VideoOutException
-/// @brief An OpenGL error occured while setting up the video display
+/// @brief An OpenGL error occurred while setting up the video display
 class VideoOutInitException : public VideoOutException {
 public:
 	VideoOutInitException(const char *func, int err)
-		: VideoOutException(from_wx(wxString::Format("%s failed with error code %d", func, err)))
+	: VideoOutException(std::string(func) + " failed with error code " + std::to_string(err))
 	{ }
 	VideoOutInitException(const char *err) : VideoOutException(err) { }
 	const char * GetName() const { return "videoout/opengl/init"; }
diff --git a/aegisub/src/video_provider_avs.cpp b/aegisub/src/video_provider_avs.cpp
index 3fd4a0df12214a799e696a1be74ec8f5da519a9e..c551c20ace827f472bcc3551dcf85004abf0e863 100644
--- a/aegisub/src/video_provider_avs.cpp
+++ b/aegisub/src/video_provider_avs.cpp
@@ -38,43 +38,40 @@
 
 #include "video_provider_avs.h"
 
-#include <wx/filename.h>
-#include <wx/msw/registry.h>
+#include "options.h"
+#include "standard_paths.h"
+
+#include <libaegisub/access.h>
+#include <libaegisub/charset_conv.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <mutex>
 
 #ifdef _WIN32
 #include <vfw.h>
 #endif
 
-#include <libaegisub/log.h>
-
-#include "charset_conv.h"
-#include "compat.h"
-#include "options.h"
-#include "standard_paths.h"
-
-AvisynthVideoProvider::AvisynthVideoProvider(wxString filename)
+AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename)
 : last_fnum(-1)
 {
+	agi::acs::CheckFileRead(filename);
+
 	iframe.flipped = true;
 	iframe.invertChannels = true;
 
-	wxMutexLocker lock(avs.GetMutex());
-
-	wxFileName fname(filename);
-	if (!fname.FileExists())
-		throw agi::FileNotFoundError(from_wx(filename));
-
-	wxString extension = filename.Right(4).Lower();
+	std::lock_guard<std::mutex> lock(avs.GetMutex());
 
 #ifdef _WIN32
-	if (extension == ".avi") {
+	if (agi::fs::HasExtension(filename, "avi")) {
 		// Try to read the keyframes before actually opening the file as trying
 		// to open the file while it's already open can cause problems with
 		// badly written VFW decoders
 		AVIFileInit();
 
 		PAVIFILE pfile;
-		long hr = AVIFileOpen(&pfile, filename.wc_str(), OF_SHARE_DENY_WRITE, 0);
+		long hr = AVIFileOpen(&pfile, filename.c_str(), OF_SHARE_DENY_WRITE, 0);
 		if (hr) {
 			warning = "Unable to open AVI file for reading keyframes:\n";
 			switch (hr) {
@@ -123,16 +120,14 @@ AvisynthVideoProvider::AvisynthVideoProvider(wxString filename)
 			goto stream_release;
 		}
 
-		bool all_keyframes = true;
 		for (size_t i = 0; i < avis.dwLength; i++) {
 			if (AVIStreamIsKeyFrame(ppavi, i))
 				KeyFrames.push_back(i);
-			else
-				all_keyframes = false;
 		}
 
 		// If every frame is a keyframe then just discard the keyframe data as it's useless
-		if (all_keyframes) KeyFrames.clear();
+		if (KeyFrames.size() == (size_t)avis.dwLength)
+			KeyFrames.clear();
 
 		// Clean up
 stream_release:
@@ -145,7 +140,7 @@ file_exit:
 #endif
 
 	try {
-		AVSValue script = Open(fname, extension);
+		auto script = Open(filename);
 
 		// Check if video was loaded properly
 		if (!script.IsClip() || !script.AsClip()->GetVideoInfo().HasVideo())
@@ -154,7 +149,6 @@ file_exit:
 		vi = script.AsClip()->GetVideoInfo();
 		if (!vi.IsRGB()) {
 			/// @todo maybe read ColorMatrix hints for d2v files?
-
 			AVSValue args[2] = { script, "Rec601" };
 			if (!OPT_GET("Video/Force BT.601")->GetBool() && (vi.width > 1024 || vi.height >= 600)) {
 				args[1] = "Rec709";
@@ -181,19 +175,19 @@ AvisynthVideoProvider::~AvisynthVideoProvider() {
 	iframe.Clear();
 }
 
-AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& extension) {
+AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
 	IScriptEnvironment *env = avs.GetEnv();
-	char *videoFilename = env->SaveString(fname.GetShortPath().mb_str(csConvLocal));
+	char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str());
 
 	// Avisynth file, just import it
-	if (extension == ".avs") {
+	if (agi::fs::HasExtension(filename, "avs")) {
 		LOG_I("avisynth/video") << "Opening .avs file with Import";
 		decoderName = "Avisynth/Import";
 		return env->Invoke("Import", videoFilename);
 	}
 
 	// Open avi file with AviSource
-	if (extension == ".avi") {
+	if (agi::fs::HasExtension(filename, "avi")) {
 		LOG_I("avisynth/video") << "Opening .avi file with AviSource";
 		try {
 			const char *argnames[2] = { 0, "audio" };
@@ -209,9 +203,9 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 	}
 
 	// Open d2v with mpeg2dec3
-	if (extension == ".d2v" && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) {
+	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) {
 		LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source";
-		AVSValue script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename);
+		auto script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename);
 		decoderName = "Avisynth/Mpeg2Dec3_Mpeg2Source";
 
 		//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
@@ -223,7 +217,7 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 	}
 
 	// If that fails, try opening it with DGDecode
-	if (extension == ".d2v" && env->FunctionExists("DGDecode_Mpeg2Source")) {
+	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("DGDecode_Mpeg2Source")) {
 		LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source";
 		decoderName = "DGDecode_Mpeg2Source";
 		return env->Invoke("Avisynth/Mpeg2Source", videoFilename);
@@ -232,7 +226,7 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 		// ancient but no sane person would use that anyway
 	}
 
-	if (extension == ".d2v" && env->FunctionExists("Mpeg2Source")) {
+	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Source")) {
 		LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source";
 		AVSValue script = env->Invoke("Mpeg2Source", videoFilename);
 		decoderName = "Avisynth/Mpeg2Source";
@@ -246,10 +240,9 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 
 	// Try loading DirectShowSource2
 	if (!env->FunctionExists("dss2")) {
-		wxFileName dss2path(StandardPaths::DecodePath("?data/avss.dll"));
-		if (dss2path.FileExists()) {
-			env->Invoke("LoadPlugin", env->SaveString(dss2path.GetFullPath().mb_str(csConvLocal)));
-		}
+		auto dss2path(StandardPaths::DecodePath("?data/avss.dll"));
+		if (agi::fs::FileExists(dss2path))
+			env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dss2path).c_str()));
 	}
 
 	// If DSS2 loaded properly, try using it
@@ -261,10 +254,9 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 
 	// Try DirectShowSource
 	// Load DirectShowSource.dll from app dir if it exists
-	wxFileName dsspath(StandardPaths::DecodePath("?data/DirectShowSource.dll"));
-	if (dsspath.FileExists()) {
-		env->Invoke("LoadPlugin",env->SaveString(dsspath.GetFullPath().mb_str(csConvLocal)));
-	}
+	auto dsspath(StandardPaths::DecodePath("?data/DirectShowSource.dll"));
+	if (agi::fs::FileExists(dsspath))
+		env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dsspath).c_str()));
 
 	// Then try using DSS
 	if (env->FunctionExists("DirectShowSource")) {
@@ -284,9 +276,9 @@ AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& ex
 const AegiVideoFrame AvisynthVideoProvider::GetFrame(int n) {
 	if (n == last_fnum) return iframe;
 
-	wxMutexLocker lock(avs.GetMutex());
+	std::lock_guard<std::mutex> lock(avs.GetMutex());
 
-	PVideoFrame frame = RGB32Video->GetFrame(n, avs.GetEnv());
+	auto frame = RGB32Video->GetFrame(n, avs.GetEnv());
 	iframe.pitch = frame->GetPitch();
 	iframe.w = frame->GetRowSize() / (vi.BitsPerPixel() / 8);
 	iframe.h = frame->GetHeight();
diff --git a/aegisub/src/video_provider_avs.h b/aegisub/src/video_provider_avs.h
index f8630cd3217a2d627acc0e831518a9745c616cd9..abbc4929ab4313b9fcbba0a34e16c81c8f077991 100644
--- a/aegisub/src/video_provider_avs.h
+++ b/aegisub/src/video_provider_avs.h
@@ -33,29 +33,30 @@
 ///
 
 #ifdef WITH_AVISYNTH
+#include "include/aegisub/video_provider.h"
+
 #include "avisynth.h"
 #include "avisynth_wrap.h"
-#include "include/aegisub/video_provider.h"
 #include "video_frame.h"
 
 class AvisynthVideoProvider: public VideoProvider {
 	AviSynthWrapper avs;
 	AegiVideoFrame iframe;
-	wxString decoderName;
+	std::string decoderName;
 	agi::vfr::Framerate fps;
 	std::vector<int> KeyFrames;
-	wxString warning;
-	wxString colorspace;
+	std::string warning;
+	std::string colorspace;
 
 	PClip RGB32Video;
 	VideoInfo vi;
 
 	int last_fnum;
 
-	AVSValue Open(wxFileName const& fname, wxString const& extension);
+	AVSValue Open(agi::fs::path const& filename);
 
 public:
-	AvisynthVideoProvider(wxString filename);
+	AvisynthVideoProvider(agi::fs::path const& filename);
 	~AvisynthVideoProvider();
 
 	const AegiVideoFrame GetFrame(int n);
@@ -66,8 +67,8 @@ public:
 	int GetHeight() const { return vi.height; };
 	double GetDAR() const { return 0; }
 	std::vector<int> GetKeyFrames() const { return KeyFrames; };
-	wxString GetWarning() const { return warning; }
-	wxString GetDecoderName() const { return decoderName; }
-	wxString GetColorSpace() const { return colorspace; }
+	std::string GetWarning() const { return warning; }
+	std::string GetDecoderName() const { return decoderName; }
+	std::string GetColorSpace() const { return colorspace; }
 };
 #endif
diff --git a/aegisub/src/video_provider_cache.h b/aegisub/src/video_provider_cache.h
index d72eb38bfb6de5159bbab9100c2ba7da43101f09..a5324d9ded2eb5ebd0330d13e5010d8e6b4caec8 100644
--- a/aegisub/src/video_provider_cache.h
+++ b/aegisub/src/video_provider_cache.h
@@ -67,7 +67,7 @@ public:
 	double GetDAR() const                 { return master->GetDAR(); }
 	agi::vfr::Framerate GetFPS() const    { return master->GetFPS(); }
 	std::vector<int> GetKeyFrames() const { return master->GetKeyFrames(); }
-	wxString GetWarning() const           { return master->GetWarning(); }
-	wxString GetDecoderName() const       { return master->GetDecoderName(); }
-	wxString GetColorSpace() const        { return master->GetColorSpace(); }
+	std::string GetWarning() const        { return master->GetWarning(); }
+	std::string GetDecoderName() const    { return master->GetDecoderName(); }
+	std::string GetColorSpace() const     { return master->GetColorSpace(); }
 };
diff --git a/aegisub/src/video_provider_dummy.cpp b/aegisub/src/video_provider_dummy.cpp
index 7fc2498fde28ea96f047cb61711f3af49e3de8a2..8d6c28c094df82208d1314be339e0b36a5951cc8 100644
--- a/aegisub/src/video_provider_dummy.cpp
+++ b/aegisub/src/video_provider_dummy.cpp
@@ -36,10 +36,16 @@
 
 #include "video_provider_dummy.h"
 
-#include <wx/tokenzr.h>
-
 #include "colorspace.h"
+
 #include <libaegisub/color.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/util.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/format.hpp>
 
 void DummyVideoProvider::Create(double fps, int frames, int width, int height, unsigned char red, unsigned char green, unsigned char blue, bool pattern) {
 	this->framecount = frames;
@@ -77,34 +83,30 @@ void DummyVideoProvider::Create(double fps, int frames, int width, int height, u
 	}
 }
 
-static long get_long(wxStringTokenizer &t, const char *err) {
-	long ret;
-	if (!t.GetNextToken().ToLong(&ret))
-		throw VideoOpenError(err);
-	return ret;
-}
-
-DummyVideoProvider::DummyVideoProvider(wxString const& filename) {
-	wxString params;
-	if (!filename.StartsWith("?dummy:", &params))
-		throw agi::FileNotFoundError("Attempted creating dummy video provider with non-dummy filename");
+DummyVideoProvider::DummyVideoProvider(agi::fs::path const& filename) {
+	if (!boost::starts_with(filename.string(), "?dummy"))
+		throw agi::fs::FileNotFound(std::string("Attempted creating dummy video provider with non-dummy filename"));
 
-	wxStringTokenizer t(params, ":");
-	if (t.CountTokens() < 7)
+	std::vector<std::string> toks;
+	auto const& fields = filename.string().substr(7);
+	boost::split(toks, fields, boost::is_any_of(":"));
+	if (toks.size() != 8)
 		throw VideoOpenError("Too few fields in dummy video parameter list");
 
+	size_t i = 0;
 	double fps;
-	if (!t.GetNextToken().ToDouble(&fps))
-		throw VideoOpenError("Unable to parse fps field in dummy video parameter list");
+	int frames, width, height, red, green, blue;
 
-	long frames = get_long(t, "Unable to parse framecount field in dummy video parameter list");
-	long width  = get_long(t, "Unable to parse width field in dummy video parameter list");
-	long height = get_long(t, "Unable to parse height field in dummy video parameter list");
-	long red    = get_long(t, "Unable to parse red colour field in dummy video parameter list");
-	long green  = get_long(t, "Unable to parse green colour field in dummy video parameter list");
-	long blue   = get_long(t, "Unable to parse blue colour field in dummy video parameter list");
+	using agi::util::try_parse;
+	if (!try_parse(toks[i++], &fps))    throw VideoOpenError("Unable to parse fps field in dummy video parameter list");
+	if (!try_parse(toks[i++], &frames)) throw VideoOpenError("Unable to parse framecount field in dummy video parameter list");
+	if (!try_parse(toks[i++], &width))  throw VideoOpenError("Unable to parse width field in dummy video parameter list");
+	if (!try_parse(toks[i++], &height)) throw VideoOpenError("Unable to parse height field in dummy video parameter list");
+	if (!try_parse(toks[i++], &red))    throw VideoOpenError("Unable to parse red colour field in dummy video parameter list");
+	if (!try_parse(toks[i++], &green))  throw VideoOpenError("Unable to parse green colour field in dummy video parameter list");
+	if (!try_parse(toks[i++], &blue))   throw VideoOpenError("Unable to parse blue colour field in dummy video parameter list");
 
-	bool pattern = t.GetNextToken() == "c";
+	bool pattern = toks[i] == "c";
 
 	Create(fps, frames, width, height, red, green, blue, pattern);
 }
@@ -117,6 +119,6 @@ DummyVideoProvider::~DummyVideoProvider() {
 	frame.Clear();
 }
 
-wxString DummyVideoProvider::MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern) {
-	return wxString::Format("?dummy:%f:%d:%d:%d:%d:%d:%d:%s", fps, frames, width, height, colour.r, colour.g, colour.b, pattern ? "c" : "");
+std::string DummyVideoProvider::MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern) {
+	return str(boost::format("?dummy:%f:%d:%d:%d:%d:%d:%d:%s") % fps % frames % width % height % (int)colour.r % (int)colour.g % (int)colour.b % (pattern ? "c" : ""));
 }
diff --git a/aegisub/src/video_provider_dummy.h b/aegisub/src/video_provider_dummy.h
index bf7fdab717f112d2b61924dcc47d97dd214c82e9..bf6161ba0f9cc88efb0e91111dfae114f6d54ced 100644
--- a/aegisub/src/video_provider_dummy.h
+++ b/aegisub/src/video_provider_dummy.h
@@ -64,7 +64,7 @@ class DummyVideoProvider : public VideoProvider {
 
 public:
 	/// Create a dummy video from a string returned from MakeFilename
-	DummyVideoProvider(wxString const& filename);
+	DummyVideoProvider(agi::fs::path const& filename);
 
 	/// Create a dummy video from separate parameters
 	/// @param fps Frame rate of the dummy video
@@ -80,7 +80,7 @@ public:
 
 	/// Make a fake filename which when passed to the constructor taking a
 	/// string will result in a video with the given parameters
-	static wxString MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
+	static std::string MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
 
 	const AegiVideoFrame GetFrame(int n)  { return frame; }
 	int GetFrameCount()             const { return framecount; }
@@ -89,6 +89,6 @@ public:
 	double GetDAR()                 const { return 0; }
 	agi::vfr::Framerate GetFPS()    const { return fps; }
 	std::vector<int> GetKeyFrames() const { return std::vector<int>(); }
-	wxString GetColorSpace()        const { return "None"; }
-	wxString GetDecoderName()       const { return "Dummy Video Provider"; }
+	std::string GetColorSpace()     const { return "None"; }
+	std::string GetDecoderName()    const { return "Dummy Video Provider"; }
 };
diff --git a/aegisub/src/video_provider_ffmpegsource.cpp b/aegisub/src/video_provider_ffmpegsource.cpp
index cd9f810c486e6dccdbebddfd592ad1f440b68f79..889b27fb5c6a651b517a64950b649fbe19ba4eb9 100644
--- a/aegisub/src/video_provider_ffmpegsource.cpp
+++ b/aegisub/src/video_provider_ffmpegsource.cpp
@@ -37,21 +37,18 @@
 #ifdef WITH_FFMS2
 #include "video_provider_ffmpegsource.h"
 
-#include <map>
-
-#include <wx/choicdlg.h>
-#include <wx/msgdlg.h>
-#include <wx/utils.h>
-
 #include "aegisub_endian.h"
 #include "compat.h"
 #include "options.h"
 #include "utils.h"
 #include "video_context.h"
 
-/// @brief Constructor
-/// @param filename The filename to open
-FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(wxString filename) try
+#include <libaegisub/fs.h>
+
+#include <wx/choicdlg.h>
+#include <wx/msgdlg.h>
+
+FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(agi::fs::path const& filename) try
 : VideoSource(nullptr, FFMS_DestroyVideoSource)
 , VideoInfo(nullptr)
 , Width(-1)
@@ -65,30 +62,25 @@ FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(wxString filename) try
 
 	SetLogLevel();
 
-	// and here we go
 	LoadVideo(filename);
 }
-catch (wxString const& err) {
-	throw VideoOpenError(from_wx(err));
+catch (std::string const& err) {
+	throw VideoOpenError(err);
 }
 catch (const char * err) {
 	throw VideoOpenError(err);
 }
 
-/// @brief Opens video
-/// @param filename The filename to open
-void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
-	wxString FileNameShort = wxFileName(filename).GetShortPath();
-
-	FFMS_Indexer *Indexer = FFMS_CreateIndexer(FileNameShort.utf8_str(), &ErrInfo);
+void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename) {
+	FFMS_Indexer *Indexer = FFMS_CreateIndexer(filename.string().c_str(), &ErrInfo);
 	if (!Indexer) {
 		if (ErrInfo.SubType == FFMS_ERROR_FILE_READ)
-			throw agi::FileNotFoundError(ErrInfo.Buffer);
+			throw agi::fs::FileNotFound(std::string(ErrInfo.Buffer));
 		else
 			throw VideoNotSupported(ErrInfo.Buffer);
 	}
 
-	std::map<int,wxString> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_VIDEO);
+	std::map<int, std::string> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_VIDEO);
 	if (TrackList.size() <= 0)
 		throw VideoNotSupported("no video tracks found");
 
@@ -103,13 +95,13 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	}
 
 	// generate a name for the cache file
-	wxString CacheName = GetCacheFilename(filename);
+	auto CacheName = GetCacheFilename(filename);
 
 	// try to read index
 	agi::scoped_holder<FFMS_Index*, void (FFMS_CC*)(FFMS_Index*)>
-		Index(FFMS_ReadIndex(CacheName.utf8_str(), &ErrInfo), FFMS_DestroyIndex);
+		Index(FFMS_ReadIndex(CacheName.string().c_str(), &ErrInfo), FFMS_DestroyIndex);
 
-	if (Index && FFMS_IndexBelongsToFile(Index, FileNameShort.utf8_str(), &ErrInfo))
+	if (Index && FFMS_IndexBelongsToFile(Index, filename.string().c_str(), &ErrInfo))
 		Index = nullptr;
 
 	// time to examine the index and check if the track we want is indexed
@@ -133,7 +125,7 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	}
 
 	// update access time of index file so it won't get cleaned away
-	wxFileName(CacheName).Touch();
+	agi::fs::Touch(CacheName);
 
 	// we have now read the index and may proceed with cleaning the index cache
 	CleanCache();
@@ -159,7 +151,7 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	else
 		SeekMode = FFMS_SEEK_NORMAL;
 
-	VideoSource = FFMS_CreateVideoSource(FileNameShort.utf8_str(), TrackNumber, Index, Threads, SeekMode, &ErrInfo);
+	VideoSource = FFMS_CreateVideoSource(filename.string().c_str(), TrackNumber, Index, Threads, SeekMode, &ErrInfo);
 	if (!VideoSource)
 		throw VideoOpenError(std::string("Failed to open video track: ") + ErrInfo.Buffer);
 
@@ -178,7 +170,7 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 		DAR = double(Width) / Height;
 
 	// Assuming TV for unspecified
-	wxString ColorRange = TempFrame->ColorRange == FFMS_CR_JPEG ? "PC" : "TV";
+	ColorSpace = TempFrame->ColorRange == FFMS_CR_JPEG ? "PC" : "TV";
 
 	int CS = TempFrame->ColorSpace;
 #if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0)
@@ -195,20 +187,20 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 			ColorSpace = "None";
 			break;
 		case FFMS_CS_BT709:
-			ColorSpace = wxString::Format("%s.709", ColorRange);
+			ColorSpace += ".709";
 			break;
 		case FFMS_CS_UNSPECIFIED:
-			ColorSpace = wxString::Format("%s.%s", ColorRange, Width > 1024 || Height >= 600 ? "709" : "601");
+			ColorSpace += Width > 1024 || Height >= 600 ? "709" : "601";
 			break;
 		case FFMS_CS_FCC:
-			ColorSpace = wxString::Format("%s.FCC", ColorRange);
+			ColorSpace += ".FCC";
 			break;
 		case FFMS_CS_BT470BG:
 		case FFMS_CS_SMPTE170M:
-			ColorSpace = wxString::Format("%s.601", ColorRange);
+			ColorSpace += ".601";
 			break;
 		case FFMS_CS_SMPTE240M:
-			ColorSpace = wxString::Format("%s.240M", ColorRange);
+			ColorSpace += ".240M";
 			break;
 		default:
 			throw VideoOpenError("Unknown video color space");
@@ -228,15 +220,12 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	if (TimeBase == nullptr)
 		throw VideoOpenError("failed to get track time base");
 
-	const FFMS_FrameInfo *CurFrameData;
-
 	// build list of keyframes and timecodes
 	std::vector<int> TimecodesVector;
 	for (int CurFrameNum = 0; CurFrameNum < VideoInfo->NumFrames; CurFrameNum++) {
-		CurFrameData = FFMS_GetFrameInfo(FrameData, CurFrameNum);
-		if (CurFrameData == nullptr) {
-			throw VideoOpenError(from_wx(wxString::Format("Couldn't get info about frame %d", CurFrameNum)));
-		}
+		const FFMS_FrameInfo *CurFrameData = FFMS_GetFrameInfo(FrameData, CurFrameNum);
+		if (!CurFrameData)
+			throw VideoOpenError("Couldn't get info about frame " + std::to_string(CurFrameNum));
 
 		// keyframe?
 		if (CurFrameData->KeyFrame)
@@ -259,9 +248,8 @@ const AegiVideoFrame FFmpegSourceVideoProvider::GetFrame(int n) {
 
 	// decode frame
 	const FFMS_Frame *SrcFrame = FFMS_GetFrame(VideoSource, FrameNumber, &ErrInfo);
-	if (SrcFrame == nullptr) {
+	if (!SrcFrame)
 		throw VideoDecodeError(std::string("Failed to retrieve frame: ") +  ErrInfo.Buffer);
-	}
 
 	CurFrame.SetTo(SrcFrame->Data[0], Width, Height, SrcFrame->Linesize[0]);
 	return CurFrame;
diff --git a/aegisub/src/video_provider_ffmpegsource.h b/aegisub/src/video_provider_ffmpegsource.h
index d23559a1b83493ef4bd5f94cd460130434e93a8f..d99d716ba8306dab6ded840372afe1f89a9f5163 100644
--- a/aegisub/src/video_provider_ffmpegsource.h
+++ b/aegisub/src/video_provider_ffmpegsource.h
@@ -52,17 +52,17 @@ class FFmpegSourceVideoProvider : public VideoProvider, FFmpegSourceProvider {
 	int FrameNumber;                ///< current framenumber
 	std::vector<int> KeyFramesList; ///< list of keyframes
 	agi::vfr::Framerate Timecodes;  ///< vfr object
-	wxString ColorSpace;            ///< Colorspace name
+	std::string ColorSpace;         ///< Colorspace name
 
 	AegiVideoFrame CurFrame;        ///< current video frame
 
 	char FFMSErrMsg[1024];          ///< FFMS error message
 	FFMS_ErrorInfo ErrInfo;         ///< FFMS error codes/messages
 
-	void LoadVideo(wxString filename);
+	void LoadVideo(agi::fs::path const& filename);
 
 public:
-	FFmpegSourceVideoProvider(wxString filename);
+	FFmpegSourceVideoProvider(agi::fs::path const& filename);
 
 	const AegiVideoFrame GetFrame(int n);
 
@@ -72,12 +72,12 @@ public:
 	double GetDAR() const { return DAR; }
 	agi::vfr::Framerate GetFPS() const { return Timecodes; }
 
-	wxString GetColorSpace() const { return ColorSpace; }
+	std::string GetColorSpace() const { return ColorSpace; }
 
 	/// @brief Gets a list of keyframes
 	/// @return	Returns a wxArrayInt of keyframes.
 	std::vector<int> GetKeyFrames() const { return KeyFramesList; };
-	wxString GetDecoderName() const { return "FFmpegSource"; }
+	std::string GetDecoderName() const { return "FFmpegSource"; }
 	/// @brief Gets the desired cache behavior.
 	/// @return Returns true.
 	bool WantsCaching() const { return true; }
diff --git a/aegisub/src/video_provider_manager.cpp b/aegisub/src/video_provider_manager.cpp
index 7f630f6c69e8b1078992afd3d19021dd16103259..994d50975fb7319855411c5b42e531252d03d356 100644
--- a/aegisub/src/video_provider_manager.cpp
+++ b/aegisub/src/video_provider_manager.cpp
@@ -34,79 +34,66 @@
 
 #include "config.h"
 
-#include <libaegisub/log.h>
+#include "video_provider_manager.h"
 
-#include "compat.h"
 #include "options.h"
-
-#ifdef WITH_AVISYNTH
 #include "video_provider_avs.h"
-#endif
 #include "video_provider_cache.h"
 #include "video_provider_dummy.h"
-#ifdef WITH_FFMS2
 #include "video_provider_ffmpegsource.h"
-#endif
-#include "video_provider_manager.h"
 #include "video_provider_yuv4mpeg.h"
 
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
 
-/// @brief Get provider
-/// @param video
-/// @return
-///
-VideoProvider *VideoProviderFactory::GetProvider(wxString video) {
-	std::vector<std::string> list = GetClasses(OPT_GET("Video/Provider")->GetString());
-	if (video.StartsWith("?dummy")) list.insert(list.begin(), "Dummy");
-	list.insert(list.begin(), "YUV4MPEG");
+VideoProvider *VideoProviderFactory::GetProvider(agi::fs::path const& video_file) {
+	std::vector<std::string> factories = GetClasses(OPT_GET("Video/Provider")->GetString());
+	factories.insert(factories.begin(), "YUV4MPEG");
+	factories.insert(factories.begin(), "Dummy");
 
-	bool fileFound = false;
-	bool fileSupported = false;
+	bool found = false;
+	bool supported = false;
 	std::string errors;
 	errors.reserve(1024);
-	for (auto const& factory : list) {
+
+	for (auto const& factory : factories) {
 		std::string err;
 		try {
-			VideoProvider *provider = Create(factory, video);
-			LOG_I("manager/video/provider") << factory << ": opened " << from_wx(video);
-			if (provider->WantsCaching()) {
-				return new VideoProviderCache(provider);
-			}
-			return provider;
+			VideoProvider *provider = Create(factory, video_file);
+			LOG_I("manager/video/provider") << factory << ": opened " << video_file;
+			return provider->WantsCaching() ? new VideoProviderCache(provider) : provider;
 		}
-		catch (agi::FileNotFoundError const&) {
-			err = factory + ": file not found.";
+		catch (agi::fs::FileNotFound const&) {
+			err = "file not found.";
 			// Keep trying other providers as this one may just not be able to
 			// open a valid path
 		}
 		catch (VideoNotSupported const&) {
-			fileFound = true;
-			err = factory + ": video is not in a supported format.";
+			found = true;
+			err = "video is not in a supported format.";
 		}
 		catch (VideoOpenError const& ex) {
-			fileSupported = true;
-			err = factory + ": " + ex.GetMessage();
+			supported = true;
+			err = ex.GetMessage();
 		}
 		catch (agi::vfr::Error const& ex) {
-			fileSupported = true;
-			err = factory + ": " + ex.GetMessage();
+			supported = true;
+			err = ex.GetMessage();
 		}
-		errors += err;
-		errors += "\n";
-		LOG_D("manager/video/provider") << err;
+
+		errors += factory + ": " + err + "\n";
+		LOG_D("manager/video/provider") << factory << ": " << err;
 	}
 
 	// No provider could open the file
-	LOG_E("manager/video/provider") << "Could not open " << from_wx(video);
-	std::string msg = "Could not open " + from_wx(video) + ":\n" + errors;
+	LOG_E("manager/video/provider") << "Could not open " << video_file;
+	std::string msg = "Could not open " + video_file.string() + ":\n" + errors;
 
-	if (!fileFound) throw agi::FileNotFoundError(from_wx(video));
-	if (!fileSupported) throw VideoNotSupported(msg);
+	if (!found) throw agi::fs::FileNotFound(video_file.string());
+	if (!supported) throw VideoNotSupported(msg);
 	throw VideoOpenError(msg);
 }
 
-/// @brief Register all providers
-///
 void VideoProviderFactory::RegisterProviders() {
 #ifdef WITH_AVISYNTH
 	Register<AvisynthVideoProvider>("Avisynth");
@@ -118,4 +105,4 @@ void VideoProviderFactory::RegisterProviders() {
 	Register<YUV4MPEGVideoProvider>("YUV4MPEG", true);
 }
 
-template<> VideoProviderFactory::map *FactoryBase<VideoProvider *(*)(wxString)>::classes = nullptr;
+template<> VideoProviderFactory::map *FactoryBase<VideoProvider *(*)(agi::fs::path)>::classes = nullptr;
diff --git a/aegisub/src/video_provider_manager.h b/aegisub/src/video_provider_manager.h
index 8a5893d8d5d9b1064d4fdb6820c20957a255cb22..739960746a29988c888be886432c82b6fba4ab79 100644
--- a/aegisub/src/video_provider_manager.h
+++ b/aegisub/src/video_provider_manager.h
@@ -1,42 +1,26 @@
-// Copyright (c) 2006-2008, Rodrigo Braz Monteiro, Fredrik Mellbin
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
 //
-//   * Redistributions of source code must retain the above copyright notice,
-//     this list of conditions and the following disclaimer.
-//   * Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//   * Neither the name of the Aegisub Group nor the names of its contributors
-//     may be used to endorse or promote products derived from this software
-//     without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 // Aegisub Project http://www.aegisub.org/
 
-/// @file video_provider_manager.h
-/// @see video_provider_manager.cpp
-/// @ingroup video_input
-///
-
 #include "factory_manager.h"
 #include "include/aegisub/video_provider.h"
 
-class VideoProviderFactory : public Factory1<VideoProvider, wxString> {
+#include <libaegisub/fs_fwd.h>
+
+class VideoProviderFactory : public Factory1<VideoProvider, agi::fs::path> {
 public:
-	static VideoProvider *GetProvider(wxString video);
+	static VideoProvider *GetProvider(agi::fs::path const& video_file);
 	static void RegisterProviders();
 };
diff --git a/aegisub/src/video_provider_yuv4mpeg.cpp b/aegisub/src/video_provider_yuv4mpeg.cpp
index 00afd65b59ac888728a7a16530f5e07b28820be3..9b633a1fc20303143c4a62f0734327d75ce841fd 100644
--- a/aegisub/src/video_provider_yuv4mpeg.cpp
+++ b/aegisub/src/video_provider_yuv4mpeg.cpp
@@ -34,14 +34,19 @@
 
 #include "config.h"
 
-#include <libaegisub/log.h>
-
 #include "video_provider_yuv4mpeg.h"
 
 #include "compat.h"
 #include "utils.h"
 #include "video_frame.h"
 
+#include <libaegisub/fs.h>
+#include <libaegisub/log.h>
+#include <libaegisub/util.h>
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/filesystem/path.hpp>
+
 // All of this cstdio bogus is because of one reason and one reason only:
 // MICROSOFT'S IMPLEMENTATION OF STD::FSTREAM DOES NOT SUPPORT FILES LARGER THAN 2 GB.
 // (yes, really)
@@ -53,7 +58,7 @@
 
 /// @brief Constructor
 /// @param filename The filename to open
-YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(wxString fname)
+YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename)
 : sf(nullptr)
 , inited(false)
 , w (0)
@@ -67,15 +72,13 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(wxString fname)
 	fps_rat.den = 1;
 
 	try {
-		wxString filename = wxFileName(fname).GetShortPath();
-
 #ifdef WIN32
-		sf = _wfopen(filename.wc_str(), L"rb");
+		sf = _wfopen(filename.c_str(), L"rb");
 #else
-		sf = fopen(filename.utf8_str(), "rb");
+		sf = fopen(filename.c_str(), "rb");
 #endif
 
-		if (sf == nullptr) throw agi::FileNotFoundError(from_wx(fname));
+		if (!sf) throw agi::fs::FileNotFound(filename);
 
 		CheckFileFormat();
 
@@ -120,10 +123,9 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(wxString fname)
 	}
 }
 
-
 /// @brief Destructor
 YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() {
-	if (sf) fclose(sf);
+	fclose(sf);
 }
 
 /// @brief Checks if the file is an YUV4MPEG file or not
@@ -143,14 +145,14 @@ void YUV4MPEGVideoProvider::CheckFileFormat() {
 /// @param startpos		The byte offset at where to start reading
 /// @param reset_pos	If true, the function will reset the file position to what it was before the function call before returning
 /// @return				A list of parameters
-std::vector<wxString> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) {
-	std::vector<wxString> tags;
-	wxString curtag;
+std::vector<std::string> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) {
+	std::vector<std::string> tags;
+	std::string curtag;
 	int bytesread = 0;
 	int buf;
 
 	if (fseeko(sf, startpos, SEEK_SET))
-		throw VideoOpenError(from_wx(wxString::Format("YUV4MPEG video provider: ReadHeader: failed seeking to position %d", startpos)));
+		throw VideoOpenError("YUV4MPEG video provider: ReadHeader: failed seeking to position " + std::to_string(startpos));
 
 	// read header until terminating newline (0x0A) is found
 	while ((buf = fgetc(sf)) != 0x0A) {
@@ -166,29 +168,27 @@ std::vector<wxString> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos) {
 			throw VideoOpenError("ReadHeader: Malformed header (no terminating newline found)");
 
 		// found a new tag
-		if (buf == 0x20) {
+		if (buf == 0x20 && !curtag.empty()) {
 			tags.push_back(curtag);
-			curtag.Clear();
+			curtag.clear();
 		}
 		else
-			curtag.Append(static_cast<wxChar>(buf));
+			curtag.push_back(buf);
 	}
 	// if only one tag with no trailing space was found (possible in the
 	// FRAME header case), make sure we get it
-	if (!curtag.IsEmpty()) {
+	if (!curtag.empty())
 		tags.push_back(curtag);
-		curtag.Clear();
-	}
 
 	return tags;
 }
 
 /// @brief Parses a list of parameters and sets reader state accordingly
 /// @param tags	The list of parameters to parse
-void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
+void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<std::string>& tags) {
 	if (tags.size() <= 1)
 		throw VideoOpenError("ParseFileHeader: contentless header");
-	if (tags.front().Cmp("YUV4MPEG2"))
+	if (tags.front() != "YUV4MPEG2")
 		throw VideoOpenError("ParseFileHeader: malformed header (bad magic)");
 
 	// temporary stuff
@@ -200,30 +200,30 @@ void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
 	Y4M_PixelFormat t_pixfmt	= Y4M_PIXFMT_NONE;
 
 	for (unsigned i = 1; i < tags.size(); i++) {
-		wxString tag;
-		long tmp_long1 = 0;
-		long tmp_long2 = 0;
+		char type = tags[i][0];
+		std::string tag = tags[i].substr(1);
 
-		if (tags[i].StartsWith("W", &tag)) {
-			if (!tag.ToLong(&tmp_long1))
+		if (type == 'W') {
+			if (!agi::util::try_parse(tag, &t_w))
 				throw VideoOpenError("ParseFileHeader: invalid width");
-			t_w = (int)tmp_long1;
 		}
-		else if (tags[i].StartsWith("H", &tag)) {
-			if (!tag.ToLong(&tmp_long1))
+		else if (type == 'H') {
+			if (!agi::util::try_parse(tag, &t_h))
 				throw VideoOpenError("ParseFileHeader: invalid height");
-			t_h = (int)tmp_long1;
 		}
-		else if (tags[i].StartsWith("F", &tag)) {
-			if (!(tag.BeforeFirst(':').ToLong(&tmp_long1) && tag.AfterFirst(':').ToLong(&tmp_long2)))
+		else if (type == 'F') {
+			size_t pos = tag.find(':');
+			if (pos == tag.npos)
+				throw VideoOpenError("ParseFileHeader: invalid framerate");
+
+			if (!agi::util::try_parse(tag.substr(0, pos), &t_fps_num) ||
+				!agi::util::try_parse(tag.substr(pos + 1), &t_fps_den))
 				throw VideoOpenError("ParseFileHeader: invalid framerate");
-			t_fps_num = (int)tmp_long1;
-			t_fps_den = (int)tmp_long2;
 		}
-		else if (tags[i].StartsWith("C", &tag)) {
+		else if (type == 'C') {
 			// technically this should probably be case sensitive,
 			// but being liberal in what you accept doesn't hurt
-			tag.MakeLower();
+			boost::to_lower(tag);
 			if (tag == "420")			t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct?
 			else if (tag == "420jpeg")	t_pixfmt = Y4M_PIXFMT_420JPEG;
 			else if (tag == "420mpeg2")	t_pixfmt = Y4M_PIXFMT_420MPEG2;
@@ -236,8 +236,8 @@ void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
 			else
 				throw VideoOpenError("ParseFileHeader: invalid or unknown colorspace");
 		}
-		else if (tags[i].StartsWith("I", &tag)) {
-			tag.MakeLower();
+		else if (type == 'I') {
+			boost::to_lower(tag);
 			if (tag == "p")			t_imode = Y4M_ILACE_PROGRESSIVE;
 			else if (tag == "t")	t_imode = Y4M_ILACE_TFF;
 			else if (tag == "b")	t_imode = Y4M_ILACE_BFF;
@@ -247,7 +247,7 @@ void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
 				throw VideoOpenError("ParseFileHeader: invalid or unknown interlacing mode");
 		}
 		else
-			LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << from_wx(tags[i]);
+			LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << tags[i];
 	}
 
 	// The point of all this is to allow multiple YUV4MPEG2 headers in a single file
@@ -282,8 +282,8 @@ void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
 /// @param tags	The list of parameters to parse
 /// @return	The flags set, as a binary mask
 ///	This function is currently unimplemented (it will always return Y4M_FFLAG_NONE).
-YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(const std::vector<wxString>& tags) {
-	if (tags.front().Cmp("FRAME"))
+YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(const std::vector<std::string>& tags) {
+	if (tags.front() != "FRAME")
 		throw VideoOpenError("ParseFrameHeader: malformed frame header (bad magic)");
 
 	/// @todo implement parsing of frame flags
@@ -307,7 +307,7 @@ int YUV4MPEGVideoProvider::IndexFile() {
 			throw VideoOpenError("IndexFile: ftello failed");
 
 		// continue reading headers until no more are found
-		std::vector<wxString> tags = ReadHeader(curpos);
+		std::vector<std::string> tags = ReadHeader(curpos);
 		curpos = ftello(sf);
 		if (curpos < 0)
 			throw VideoOpenError("IndexFile: ftello failed");
@@ -316,11 +316,11 @@ int YUV4MPEGVideoProvider::IndexFile() {
 			break; // no more headers
 
 		Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET;
-		if (!tags.front().Cmp("YUV4MPEG2")) {
+		if (tags.front() == "YUV4MPEG2") {
 			ParseFileHeader(tags);
 			continue;
 		}
-		else if (!tags.front().Cmp("FRAME"))
+		else if (tags.front() == "FRAME")
 			flags = ParseFrameHeader(tags);
 
 		if (flags == Y4M_FFLAG_NONE) {
@@ -328,7 +328,7 @@ int YUV4MPEGVideoProvider::IndexFile() {
 			seek_table.push_back(curpos);
 			// seek to next frame header start position
 			if (fseeko(sf, frame_sz, SEEK_CUR))
-				throw VideoOpenError(from_wx(wxString::Format("IndexFile: failed seeking to position %d", curpos + frame_sz)));
+				throw VideoOpenError("IndexFile: failed seeking to position " + std::to_string(curpos + frame_sz));
 		}
 		else {
 			/// @todo implement rff flags etc
diff --git a/aegisub/src/video_provider_yuv4mpeg.h b/aegisub/src/video_provider_yuv4mpeg.h
index dae7415fc2aa92140c3a00bb190aa93b4ceec3c4..4f92634673deb9fc49b64ac72048ec1a4ede7a41 100644
--- a/aegisub/src/video_provider_yuv4mpeg.h
+++ b/aegisub/src/video_provider_yuv4mpeg.h
@@ -33,13 +33,10 @@
 ///
 
 #include "include/aegisub/video_provider.h"
-#include <cstdio>
 
+#include <cstdio>
 #include <vector>
 
-#include <wx/filename.h>
-#include <wx/log.h>
-
 /// the maximum allowed header length, in bytes
 #define YUV4MPEG_HEADER_MAXLEN 128
 
@@ -103,7 +100,6 @@ class YUV4MPEGVideoProvider : public VideoProvider {
 		Y4M_FFLAG_C_UNKNOWN = 0x0800	/// unknown (only allowed for non-4:2:0 sampling)
 	};
 
-
 	FILE *sf;		/// source file
 	bool inited;	/// initialization state
 
@@ -128,13 +124,13 @@ class YUV4MPEGVideoProvider : public VideoProvider {
 	std::vector<int64_t> seek_table;
 
 	void CheckFileFormat();
-	void ParseFileHeader(const std::vector<wxString>& tags);
-	Y4M_FrameFlags ParseFrameHeader(const std::vector<wxString>& tags);
-	std::vector<wxString> ReadHeader(int64_t startpos);
+	void ParseFileHeader(const std::vector<std::string>& tags);
+	Y4M_FrameFlags ParseFrameHeader(const std::vector<std::string>& tags);
+	std::vector<std::string> ReadHeader(int64_t startpos);
 	int IndexFile();
 
 public:
-	YUV4MPEGVideoProvider(wxString filename);
+	YUV4MPEGVideoProvider(agi::fs::path const& filename);
 	~YUV4MPEGVideoProvider();
 
 	const AegiVideoFrame GetFrame(int n);
@@ -145,7 +141,7 @@ public:
 	double GetDAR() const                 { return 0; }
 	agi::vfr::Framerate GetFPS() const    { return fps; }
 	std::vector<int> GetKeyFrames() const { return std::vector<int>(); }
-	wxString GetColorSpace() const        { return "TV.601"; }
-	wxString GetDecoderName() const       { return "YU4MPEG"; }
+	std::string GetColorSpace() const     { return "TV.601"; }
+	std::string GetDecoderName() const    { return "YU4MPEG"; }
 	bool WantsCaching() const             { return true; }
 };
diff --git a/aegisub/src/visual_tool.cpp b/aegisub/src/visual_tool.cpp
index ad6d37b0a92abebb0f81b3bca5a130ab7529b6da..ff87dfd7ffbf00757bc855c2707374cd5ac6f06e 100644
--- a/aegisub/src/visual_tool.cpp
+++ b/aegisub/src/visual_tool.cpp
@@ -20,21 +20,17 @@
 
 #include "config.h"
 
-#include <algorithm>
-
 #include "visual_tool.h"
 
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
 #include "ass_time.h"
-#include "compat.h"
 #include "include/aegisub/context.h"
 #include "options.h"
 #include "utils.h"
 #include "video_context.h"
 #include "video_display.h"
-#include "video_provider_manager.h"
 #include "visual_feature.h"
 #include "visual_tool_clip.h"
 #include "visual_tool_drag.h"
@@ -42,6 +38,9 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/format.hpp>
+
 using std::placeholders::_1;
 
 const wxColour VisualToolBase::colour[4] = {wxColour(106,32,19), wxColour(255,169,40), wxColour(255,253,185), wxColour(187,0,0)};
@@ -388,7 +387,7 @@ Vector2D VisualToolBase::GetLinePosition(AssDialogue *diag) {
 	memcpy(margin, diag->Margin, sizeof margin);
 	int align = 2;
 
-	if (AssStyle *style = c->ass->GetStyle(from_wx(diag->Style))) {
+	if (AssStyle *style = c->ass->GetStyle(diag->Style)) {
 		align = style->alignment;
 		for (int i = 0; i < 3; i++) {
 			if (margin[i] == 0)
@@ -453,7 +452,7 @@ bool VisualToolBase::GetLineMove(AssDialogue *diag, Vector2D &p1, Vector2D &p2,
 void VisualToolBase::GetLineRotation(AssDialogue *diag, float &rx, float &ry, float &rz) {
 	rx = ry = rz = 0.f;
 
-	if (AssStyle *style = c->ass->GetStyle(from_wx(diag->Style)))
+	if (AssStyle *style = c->ass->GetStyle(diag->Style))
 		rz = style->angle;
 
 	boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
@@ -471,7 +470,7 @@ void VisualToolBase::GetLineRotation(AssDialogue *diag, float &rx, float &ry, fl
 void VisualToolBase::GetLineScale(AssDialogue *diag, Vector2D &scale) {
 	float x = 100.f, y = 100.f;
 
-	if (AssStyle *style = c->ass->GetStyle(from_wx(diag->Style))) {
+	if (AssStyle *style = c->ass->GetStyle(diag->Style)) {
 		x = style->scalex;
 		y = style->scaley;
 	}
@@ -506,7 +505,7 @@ void VisualToolBase::GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2,
 	}
 }
 
-wxString VisualToolBase::GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse) {
+std::string VisualToolBase::GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse) {
 	boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
 
 	scale = 1;
@@ -519,26 +518,26 @@ wxString VisualToolBase::GetLineVectorClip(AssDialogue *diag, int &scale, bool &
 		tag = find_tag(blocks, "\\clip");
 
 	if (tag && tag->size() == 4) {
-		return wxString::Format("m %d %d l %d %d %d %d %d %d",
-			(*tag)[0].Get<int>(), (*tag)[1].Get<int>(),
-			(*tag)[2].Get<int>(), (*tag)[1].Get<int>(),
-			(*tag)[2].Get<int>(), (*tag)[3].Get<int>(),
-			(*tag)[0].Get<int>(), (*tag)[3].Get<int>());
+		return str(boost::format("m %d %d l %d %d %d %d %d %d")
+			% (*tag)[0].Get<int>() % (*tag)[1].Get<int>()
+			% (*tag)[2].Get<int>() % (*tag)[1].Get<int>()
+			% (*tag)[2].Get<int>() % (*tag)[3].Get<int>()
+			% (*tag)[0].Get<int>() % (*tag)[3].Get<int>());
 	}
 	if (tag) {
 		scale = std::max((*tag)[0].Get(scale), 1);
-		return to_wx((*tag)[1].Get<std::string>(""));
+		return (*tag)[1].Get<std::string>("");
 	}
 
 	return "";
 }
 
-void VisualToolBase::SetSelectedOverride(std::string const& tag, wxString const& value) {
+void VisualToolBase::SetSelectedOverride(std::string const& tag, std::string const& value) {
 	for (auto line : c->selectionController->GetSelectedSet())
 		SetOverride(line, tag, value);
 }
 
-void VisualToolBase::SetOverride(AssDialogue* line, std::string const& tag, wxString const& value) {
+void VisualToolBase::SetOverride(AssDialogue* line, std::string const& tag, std::string const& value) {
 	if (!line) return;
 
 	std::string removeTag;
@@ -562,12 +561,12 @@ void VisualToolBase::SetOverride(AssDialogue* line, std::string const& tag, wxSt
 				i--;
 			}
 		}
-		ovr->AddTag(tag + from_wx(value));
+		ovr->AddTag(tag + value);
 
 		line->UpdateText(blocks);
 	}
 	else
-		line->Text = "{" + to_wx(tag) + value + "}" + line->Text;
+		line->Text = "{" + tag + value + "}" + line->Text.get();
 }
 
 // If only export worked
diff --git a/aegisub/src/visual_tool.h b/aegisub/src/visual_tool.h
index efe3c5cd3d01e299896e1965e3a6e07d5e6555ee..18d2a7ea74fb31edec6c809628363b5e1267d405 100644
--- a/aegisub/src/visual_tool.h
+++ b/aegisub/src/visual_tool.h
@@ -28,9 +28,7 @@
 
 #include <boost/container/list.hpp>
 
-#include <wx/log.h>
 #include <wx/event.h>
-#include <wx/button.h>
 
 #include <libaegisub/signal.h>
 
@@ -133,10 +131,10 @@ protected:
 	void GetLineRotation(AssDialogue *diag, float &rx, float &ry, float &rz);
 	void GetLineScale(AssDialogue *diag, Vector2D &scale);
 	void GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2, bool &inverse);
-	wxString GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse);
+	std::string GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse);
 
-	void SetOverride(AssDialogue* line, std::string const& tag, wxString const& value);
-	void SetSelectedOverride(std::string const& tag, wxString const& value);
+	void SetOverride(AssDialogue* line, std::string const& tag, std::string const& value);
+	void SetSelectedOverride(std::string const& tag, std::string const& value);
 
 	VisualToolBase(VideoDisplay *parent, agi::Context *context);
 
diff --git a/aegisub/src/visual_tool_clip.cpp b/aegisub/src/visual_tool_clip.cpp
index aebab905c648015136f5eaafc482e991f84acd30..a5dd5552befbec7164cba991546351ed11f0ca9e 100644
--- a/aegisub/src/visual_tool_clip.cpp
+++ b/aegisub/src/visual_tool_clip.cpp
@@ -20,8 +20,6 @@
 
 #include "config.h"
 
-#include <utility>
-
 #include "visual_tool_clip.h"
 
 #include "ass_dialogue.h"
@@ -29,6 +27,9 @@
 #include "selection_controller.h"
 #include "utils.h"
 
+#include <boost/format.hpp>
+#include <utility>
+
 VisualToolClip::VisualToolClip(VideoDisplay *parent, agi::Context *context)
 : VisualTool<ClipCorner>(parent, context)
 , cur_1(0, 0)
@@ -112,12 +113,12 @@ void VisualToolClip::UpdateHold() {
 }
 
 void VisualToolClip::CommitHold() {
-	wxString value = wxString::Format("(%s,%s)", ToScriptCoords(cur_1.Min(cur_2)).Str(), ToScriptCoords(cur_1.Max(cur_2)).Str());
+	std::string value = str(boost::format("(%s,%s)") % ToScriptCoords(cur_1.Min(cur_2)).Str() % ToScriptCoords(cur_1.Max(cur_2)).Str());
 
 	for (auto line : c->selectionController->GetSelectedSet()) {
 		// This check is technically not correct as it could be outside of an
 		// override block... but that's rather unlikely
-		bool has_iclip = line->Text.get().find("\\iclip") != wxString::npos;
+		bool has_iclip = line->Text.get().find("\\iclip") != std::string::npos;
 		SetOverride(line, has_iclip ? "\\iclip" : "\\clip", value);
 	}
 }
diff --git a/aegisub/src/visual_tool_cross.cpp b/aegisub/src/visual_tool_cross.cpp
index c3984e80c63a2719a1874b354e9197cb55610ab1..64b4a89514b52af1264840103ae11e2b9cd975c1 100644
--- a/aegisub/src/visual_tool_cross.cpp
+++ b/aegisub/src/visual_tool_cross.cpp
@@ -26,6 +26,10 @@
 #include "include/aegisub/context.h"
 #include "video_display.h"
 
+#include <libaegisub/color.h>
+
+#include <boost/format.hpp>
+
 VisualToolCross::VisualToolCross(VideoDisplay *parent, agi::Context *context)
 : VisualTool<VisualDraggableFeature>(parent, context)
 , gl_text(new OpenGLText)
@@ -45,9 +49,9 @@ void VisualToolCross::OnDoubleClick() {
 		int t1, t2;
 		if (GetLineMove(line, p1, p2, t1, t2)) {
 			if (t1 > 0 || t2 > 0)
-				SetOverride(line, "\\move", wxString::Format("(%s,%s,%d,%d)", Text(p1 + d), Text(p2 + d), t1, t2));
+				SetOverride(line, "\\move", str(boost::format("(%s,%s,%d,%d)") % Text(p1 + d) % Text(p2 + d) % t1 % t2));
 			else
-				SetOverride(line, "\\move", wxString::Format("(%s,%s)", Text(p1 + d), Text(p2 + d)));
+				SetOverride(line, "\\move", str(boost::format("(%s,%s)") % Text(p1 + d) % Text(p2 + d)));
 		}
 		else
 			SetOverride(line, "\\pos", "(" + Text(GetLinePosition(line) + d) + ")");
@@ -74,11 +78,11 @@ void VisualToolCross::Draw() {
 	gl.DrawLines(2, lines, 4);
 	gl.ClearInvert();
 
-	wxString mouse_text = Text(ToScriptCoords(shift_down ? video_res - mouse_pos : mouse_pos));
+	std::string mouse_text = Text(ToScriptCoords(shift_down ? video_res - mouse_pos : mouse_pos));
 
 	int tw, th;
 	gl_text->SetFont("Verdana", 12, true, false);
-	gl_text->SetColour(*wxWHITE, 1.f);
+	gl_text->SetColour(agi::Color(255, 255, 255, 255));
 	gl_text->GetExtent(mouse_text, tw, th);
 
 	// Place the text in the corner of the cross closest to the center of the video
@@ -97,6 +101,6 @@ void VisualToolCross::Draw() {
 	gl_text->Print(mouse_text, dx, dy);
 }
 
-wxString VisualToolCross::Text(Vector2D v) {
+std::string VisualToolCross::Text(Vector2D v) {
 	return video_res.X() > script_res.X() ? v.Str() : v.DStr();
 }
diff --git a/aegisub/src/visual_tool_cross.h b/aegisub/src/visual_tool_cross.h
index 75c7e2259b63afadb80b7442c89f9832122f295c..9579dd03d5b16369ad4bdb0df976f860793283d4 100644
--- a/aegisub/src/visual_tool_cross.h
+++ b/aegisub/src/visual_tool_cross.h
@@ -34,7 +34,7 @@ class VisualToolCross : public VisualTool<VisualDraggableFeature> {
 
 	void OnDoubleClick();
 	void Draw();
-	wxString Text(Vector2D v);
+	std::string Text(Vector2D v);
 public:
 	VisualToolCross(VideoDisplay *parent, agi::Context *context);
 	~VisualToolCross();
diff --git a/aegisub/src/visual_tool_drag.cpp b/aegisub/src/visual_tool_drag.cpp
index 29c3b97c459eded2ef5b066d03e41fcef55877f0..9b2378f2d06f0c3408a7d18a93be722b313b019a 100644
--- a/aegisub/src/visual_tool_drag.cpp
+++ b/aegisub/src/visual_tool_drag.cpp
@@ -22,12 +22,6 @@
 
 #include "visual_tool_drag.h"
 
-#include <algorithm>
-#include <functional>
-
-#include <wx/bmpbuttn.h>
-#include <wx/toolbar.h>
-
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "include/aegisub/context.h"
@@ -39,6 +33,12 @@
 
 #include <libaegisub/of_type_adaptor.h>
 
+#include <algorithm>
+#include <boost/format.hpp>
+#include <functional>
+
+#include <wx/toolbar.h>
+
 static const DraggableFeatureType DRAG_ORIGIN = DRAG_BIG_TRIANGLE;
 static const DraggableFeatureType DRAG_START = DRAG_BIG_SQUARE;
 static const DraggableFeatureType DRAG_END = DRAG_BIG_CIRCLE;
@@ -94,7 +94,7 @@ void VisualToolDrag::OnSubTool(wxCommandEvent &) {
 			// Round the start and end times to exact frames
 			int start = vc->TimeAtFrame(vc->FrameAtTime(line->Start, agi::vfr::START)) - line->Start;
 			int end = vc->TimeAtFrame(vc->FrameAtTime(line->Start, agi::vfr::END)) - line->Start;
-			SetOverride(line, "\\move", wxString::Format("(%s,%s,%d,%d)", p1.Str(), p1.Str(), start, end));
+			SetOverride(line, "\\move", str(boost::format("(%s,%s,%d,%d)") % p1.Str() % p1.Str() % start % end));
 		}
 	}
 
@@ -293,10 +293,10 @@ void VisualToolDrag::UpdateDrag(feature_iterator feature) {
 		SetOverride(feature->line, "\\pos", ToScriptCoords(feature->pos).PStr());
 	else
 		SetOverride(feature->line, "\\move",
-			wxString::Format("(%s,%s,%d,%d)",
-				ToScriptCoords(feature->pos).Str(),
-				ToScriptCoords(end_feature->pos).Str(),
-				feature->time, end_feature->time));
+			str(boost::format("(%s,%s,%d,%d)")
+				% ToScriptCoords(feature->pos).Str()
+				% ToScriptCoords(end_feature->pos).Str()
+				% feature->time % end_feature->time));
 }
 
 void VisualToolDrag::OnDoubleClick() {
@@ -307,9 +307,9 @@ void VisualToolDrag::OnDoubleClick() {
 		int t1, t2;
 		if (GetLineMove(line, p1, p2, t1, t2)) {
 			if (t1 > 0 || t2 > 0)
-				SetOverride(line, "\\move", wxString::Format("(%s,%s,%d,%d)", (p1 + d).Str(), (p2 + d).Str(), t1, t2));
+				SetOverride(line, "\\move", str(boost::format("(%s,%s,%d,%d)") % (p1 + d).Str() % (p2 + d).Str() % t1 % t2));
 			else
-				SetOverride(line, "\\move", wxString::Format("(%s,%s)", (p1 + d).Str(), (p2 + d).Str()));
+				SetOverride(line, "\\move", str(boost::format("(%s,%s)") % (p1 + d).Str() % (p2 + d).Str()));
 		}
 		else
 			SetOverride(line, "\\pos", (GetLinePosition(line) + d).PStr());
diff --git a/aegisub/src/visual_tool_rotatexy.cpp b/aegisub/src/visual_tool_rotatexy.cpp
index 703977f46b8f8872a3241ff86e1a4c240b11e505..353d4a6e6016d75f0c897da32582465b9e626101 100644
--- a/aegisub/src/visual_tool_rotatexy.cpp
+++ b/aegisub/src/visual_tool_rotatexy.cpp
@@ -20,10 +20,11 @@
 
 #include "config.h"
 
-#include <cmath>
-
 #include "visual_tool_rotatexy.h"
 
+#include <boost/format.hpp>
+#include <cmath>
+
 VisualToolRotateXY::VisualToolRotateXY(VideoDisplay *parent, agi::Context *context)
 : VisualTool<VisualDraggableFeature>(parent, context)
 , angle_x(0)
@@ -156,8 +157,8 @@ void VisualToolRotateXY::UpdateHold() {
 	angle_x = fmodf(angle_x + 360.f, 360.f);
 	angle_y = fmodf(angle_y + 360.f, 360.f);
 
-	SetSelectedOverride("\\frx", wxString::Format("%.4g", angle_x));
-	SetSelectedOverride("\\fry", wxString::Format("%.4g", angle_y));
+	SetSelectedOverride("\\frx", str(boost::format("%.4g") % angle_x));
+	SetSelectedOverride("\\fry", str(boost::format("%.4g") % angle_y));
 }
 
 void VisualToolRotateXY::UpdateDrag(feature_iterator feature) {
diff --git a/aegisub/src/visual_tool_rotatez.cpp b/aegisub/src/visual_tool_rotatez.cpp
index 2473e91174403536ca982705240ccd65f3e332e1..8ff4909535f43a1deceee1230881d9b79ce57fb3 100644
--- a/aegisub/src/visual_tool_rotatez.cpp
+++ b/aegisub/src/visual_tool_rotatez.cpp
@@ -20,12 +20,13 @@
 
 #include "config.h"
 
-#include <cmath>
-
 #include "visual_tool_rotatez.h"
 
 #include "utils.h"
 
+#include <boost/format.hpp>
+#include <cmath>
+
 static const float deg2rad = 3.1415926536f / 180.f;
 static const float rad2deg = 180.f / 3.1415926536f;
 
@@ -114,7 +115,7 @@ void VisualToolRotateZ::UpdateHold() {
 
 	angle = fmodf(angle + 360.f, 360.f);
 
-	SetSelectedOverride("\\frz", wxString::Format("%.4g", angle));
+	SetSelectedOverride("\\frz", str(boost::format("%.4g") % angle));
 }
 
 void VisualToolRotateZ::UpdateDrag(feature_iterator feature) {
diff --git a/aegisub/src/visual_tool_scale.cpp b/aegisub/src/visual_tool_scale.cpp
index bb68b6c9fbbe62cced436e850bbd0999c04bd320..3fd2395cdb6043d33ec60433aebef462292a8cee 100644
--- a/aegisub/src/visual_tool_scale.cpp
+++ b/aegisub/src/visual_tool_scale.cpp
@@ -103,8 +103,8 @@ void VisualToolScale::UpdateHold() {
 	if (ctrl_down)
 		scale = scale.Round(25.f);
 
-	SetSelectedOverride("\\fscx", wxString::Format("%d", (int)scale.X()));
-	SetSelectedOverride("\\fscy", wxString::Format("%d", (int)scale.Y()));
+	SetSelectedOverride("\\fscx", std::to_string((int)scale.X()));
+	SetSelectedOverride("\\fscy", std::to_string((int)scale.Y()));
 }
 
 void VisualToolScale::DoRefresh() {
diff --git a/aegisub/src/visual_tool_vector_clip.cpp b/aegisub/src/visual_tool_vector_clip.cpp
index 5039396c702d2bf851a9664821a76f134a0cb6a3..600f337a9305599bca7fb5323e1cb9c62cb9bf57 100644
--- a/aegisub/src/visual_tool_vector_clip.cpp
+++ b/aegisub/src/visual_tool_vector_clip.cpp
@@ -18,14 +18,10 @@
 /// @brief Vector clipping visual typesetting tool
 /// @ingroup visual_ts
 
-#include "visual_tool_vector_clip.h"
-
-#include <wx/toolbar.h>
-
-#include <algorithm>
-
 #include "config.h"
 
+#include "visual_tool_vector_clip.h"
+
 #include "ass_dialogue.h"
 #include "include/aegisub/context.h"
 #include "libresrc/libresrc.h"
@@ -33,6 +29,9 @@
 #include "selection_controller.h"
 #include "utils.h"
 
+#include <algorithm>
+#include <wx/toolbar.h>
+
 /// Button IDs
 enum {
 	BUTTON_DRAG = 1300,
@@ -190,15 +189,15 @@ void VisualToolVectorClip::MakeFeatures() {
 }
 
 void VisualToolVectorClip::Save() {
-	wxString value = "(";
+	std::string value = "(";
 	if (spline.GetScale() != 1)
-		value += wxString::Format("%d,", spline.GetScale());
+		value += std::to_string(spline.GetScale()) + ",";
 	value += spline.EncodeToAss() + ")";
 
 	for (auto line : c->selectionController->GetSelectedSet()) {
 		// This check is technically not correct as it could be outside of an
 		// override block... but that's rather unlikely
-		bool has_iclip = line->Text.get().find("\\iclip") != wxString::npos;
+		bool has_iclip = line->Text.get().find("\\iclip") != std::string::npos;
 		SetOverride(line, has_iclip ? "\\iclip" : "\\clip", value);
 	}
 }
@@ -388,9 +387,8 @@ void VisualToolVectorClip::UpdateHold() {
 void VisualToolVectorClip::DoRefresh() {
 	if (!active_line) return;
 
-	wxString vect;
 	int scale;
-	vect = GetLineVectorClip(active_line, scale, inverse);
+	std::string vect = GetLineVectorClip(active_line, scale, inverse);
 	spline.SetScale(scale);
 	spline.DecodeFromAss(vect);
 
diff --git a/aegisub/tests/Makefile b/aegisub/tests/Makefile
index b2b6359f063ba9a90ee59d6a1033d357ae22769e..06041676a85c89376e5502aa0970058c295a965b 100644
--- a/aegisub/tests/Makefile
+++ b/aegisub/tests/Makefile
@@ -10,7 +10,7 @@ CPPFLAGS += -I../src/include -I../libaegisub/include $(CFLAGS_ICONV) -I${GTEST_R
 CXXFLAGS += -Wno-unused-value
 
 ifeq (yes, $(BUILD_DARWIN))
-LDFLAGS += -framework ApplicationServices
+LDFLAGS += -framework ApplicationServices -framework Foundation
 endif
 
 SRC = \
@@ -28,6 +28,7 @@ SRC = \
 		libaegisub_line_wrap.cpp \
 		libaegisub_mru.cpp \
 		libaegisub_option.cpp \
+		libaegisub_path.cpp \
 		libaegisub_signals.cpp \
 		libaegisub_syntax_highlight.cpp \
 		libaegisub_thesaurus.cpp \
diff --git a/aegisub/tests/libaegisub_access.cpp b/aegisub/tests/libaegisub_access.cpp
index dd58350c708ef074266c771b98afea7256e134ad..82a42b77eb8ae2370fec6c1a9b75e8d31b814197 100644
--- a/aegisub/tests/libaegisub_access.cpp
+++ b/aegisub/tests/libaegisub_access.cpp
@@ -11,92 +11,69 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
 
-/// @file libaegisub_access.cpp
-/// @brief agi::acs tests.
-/// @ingroup acs
+#include "main.h"
 
 #include <libaegisub/access.h>
-
-#include "main.h"
+#include <libaegisub/fs.h>
 
 using namespace agi;
 using namespace agi::acs;
-
-class lagi_acs : public libagi {
-
-protected:
-    // place holder for future code placement
-};
-
+using namespace agi::fs;
 
 // Yes, this is a horrifying use of macros, since these are all void static
 // methods I couldn't think of a better way to test these without massive code
 // duplication.
-#define EX_FileNotFoundError(func, pass) \
-	TEST_F(lagi_acs, func##ExFileNotFoundError) { \
-		EXPECT_THROW(func("data/nonexistent"), FileNotFoundError); \
+#define DO_TEST(func, name, fail, fail_ex, pass) \
+	TEST(lagi_acs, name) { \
+		EXPECT_THROW(func(fail), fail_ex); \
 		EXPECT_NO_THROW(func(pass)); \
 	}
 
+#define EX_FileNotFound(func, pass) \
+	DO_TEST(func, func##ExFileNotFound, "data/nonexistent", FileNotFound, pass)
+
 #define EX_Fatal(func, fail, pass) \
-	TEST_F(lagi_acs, func##ExFatal) { \
-		EXPECT_THROW(func(fail), Fatal); \
-		EXPECT_NO_THROW(func(pass)); \
-	}
+	DO_TEST(func, func##ExFatal, fail, Fatal, pass)
 
 #define EX_NotAFile(func, fail, pass) \
-	TEST_F(lagi_acs, func##ExNotAFile) { \
-		EXPECT_THROW(func(fail), NotAFile); \
-		EXPECT_NO_THROW(func(pass)); \
-	}
+	DO_TEST(func, func##ExNotAFile, fail, NotAFile, pass)
 
 #define EX_NotADirectory(func, fail, pass) \
-	TEST_F(lagi_acs, func##ExNotADirectory) { \
-		EXPECT_THROW(func(fail), NotADirectory); \
-		EXPECT_NO_THROW(func(pass)); \
-	}
+	DO_TEST(func, func##ExNotADirectory, fail, NotADirectory, pass)
 
 #define EX_Read(func, fail, pass) \
-	TEST_F(lagi_acs, func##ExRead) { \
-		EXPECT_THROW(func(fail), Read); \
-		EXPECT_NO_THROW(func(pass)); \
-	}
+	DO_TEST(func, func##ExReadDenied, fail, ReadDenied, pass)
 
 #define EX_Write(func, fail, pass) \
-	TEST_F(lagi_acs, func##ExWrite) { \
-		EXPECT_THROW(func(fail), Write); \
-		EXPECT_NO_THROW(func(pass)); \
-	}
+	DO_TEST(func, func##ExWriteDenied, fail, WriteDenied, pass)
 
-EX_FileNotFoundError(CheckFileRead, "data/file")
+EX_FileNotFound(CheckFileRead, "data/file")
 EX_Read(CheckFileRead, "data/file_access_denied", "data/file")
 EX_NotAFile(CheckFileRead, "data/dir", "data/file")
-TEST_F(lagi_acs, CheckFileRead) {
+TEST(lagi_acs, CheckFileRead) {
 	EXPECT_NO_THROW(CheckFileRead("data/file"));
 }
 
-EX_FileNotFoundError(CheckFileWrite, "data/file")
+EX_FileNotFound(CheckFileWrite, "data/file")
 EX_Read(CheckFileWrite, "data/file_access_denied", "data/file")
 EX_NotAFile(CheckFileWrite, "data/dir", "data/file")
 EX_Write(CheckFileWrite, "data/file_read_only", "data/file")
-TEST_F(lagi_acs, CheckFileWrite) {
+TEST(lagi_acs, CheckFileWrite) {
 	EXPECT_NO_THROW(CheckFileRead("data/file"));
 }
 
-EX_FileNotFoundError(CheckDirRead, "data/dir")
+EX_FileNotFound(CheckDirRead, "data/dir")
 EX_Read(CheckDirRead, "data/dir_access_denied", "data/dir")
 EX_NotADirectory(CheckDirRead, "data/file", "data/dir")
-TEST_F(lagi_acs, CheckDirRead) {
+TEST(lagi_acs, CheckDirRead) {
 	EXPECT_NO_THROW(CheckDirRead("data/dir"));
 }
 
-EX_FileNotFoundError(CheckDirWrite, "data/dir")
+EX_FileNotFound(CheckDirWrite, "data/dir")
 EX_Read(CheckDirWrite, "data/dir_access_denied", "data/dir")
 EX_NotADirectory(CheckDirWrite, "data/file", "data/dir")
 EX_Write(CheckDirWrite, "data/dir_read_only", "data/dir")
-TEST_F(lagi_acs, CheckDirWrite) {
+TEST(lagi_acs, CheckDirWrite) {
 	EXPECT_NO_THROW(CheckDirWrite("data/dir"));
 }
diff --git a/aegisub/tests/libaegisub_hotkey.cpp b/aegisub/tests/libaegisub_hotkey.cpp
index 0466b4b91774938d70856294321c8f207db3892e..ae9388d56c6839157b8d78df7fd19c509d0ad744 100644
--- a/aegisub/tests/libaegisub_hotkey.cpp
+++ b/aegisub/tests/libaegisub_hotkey.cpp
@@ -11,14 +11,14 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
 
 #include "main.h"
-#include "util.h"
 
+#include <libaegisub/fs.h>
 #include <libaegisub/hotkey.h>
 
+#include <cstdarg>
+
 using namespace agi::hotkey;
 
 static const char simple_valid[] = "{"
@@ -147,9 +147,8 @@ static void insert_combo(Hotkey::HotkeyMap &hm, const char *ctx, const char *cmd
 
 	va_list argp;
 	va_start(argp, N);
-	for (int i = 0; i < N; i++) {
+	for (int i = 0; i < N; ++i)
 		keys[i] = va_arg(argp, const char *);
-	}
 	va_end(argp);
 
 	hm.insert(make_pair(std::string(cmd), Combo(ctx, cmd, keys)));
@@ -158,7 +157,7 @@ static void insert_combo(Hotkey::HotkeyMap &hm, const char *ctx, const char *cmd
 static void set_var(bool *b) { *b = true; }
 
 TEST(lagi_hotkey, set_hotkey_map) {
-	util::remove("data/hotkey_tmp");
+	agi::fs::Remove("data/hotkey_tmp");
 	{
 		Hotkey h("data/hotkey_tmp", "{}");
 
diff --git a/aegisub/tests/libaegisub_iconv.cpp b/aegisub/tests/libaegisub_iconv.cpp
index 070d68afe9e074733438a7c287cb888278aee6b7..5c30b4267a7706ec7a6ec5e2da00a5809832de79 100644
--- a/aegisub/tests/libaegisub_iconv.cpp
+++ b/aegisub/tests/libaegisub_iconv.cpp
@@ -11,19 +11,11 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_iconv.cpp
-/// @brief agi::charset
-/// @ingroup iconv
 
 #include <stdint.h>
 #include <libaegisub/charset_conv.h>
 
-
 #include "main.h"
-#include "util.h"
 
 using namespace agi::charset;
 
diff --git a/aegisub/tests/libaegisub_keyframe.cpp b/aegisub/tests/libaegisub_keyframe.cpp
index 266ec026708d4ba2419d5d2513826effff0d344a..f11cf2e07995aadf12932b3330cd19f17c8837f3 100644
--- a/aegisub/tests/libaegisub_keyframe.cpp
+++ b/aegisub/tests/libaegisub_keyframe.cpp
@@ -11,13 +11,12 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
 
 /// @file libaegisub_keyframe.cpp
 /// @brief agi::keyframe tests
 /// @ingroup video_input
 
+#include <libaegisub/fs.h>
 #include <libaegisub/keyframe.h>
 
 #include <fstream>
@@ -41,7 +40,7 @@ TEST(lagi_keyframe, save) {
 }
 
 TEST(lagi_keyframe, bad_files) {
-	EXPECT_THROW(Load(""), agi::FileSystemError);
+	EXPECT_THROW(Load(""), agi::fs::FileSystemError);
 	EXPECT_THROW(Load("data/keyframe/empty.txt"), Error);
 	EXPECT_THROW(Load("data/keyframe/garbage.txt"), Error);
 }
diff --git a/aegisub/tests/libaegisub_line_iterator.cpp b/aegisub/tests/libaegisub_line_iterator.cpp
index 900ae95df8257949791ef1744f32704848f7e224..83d0df366537d84c1468e495868a605599b036c5 100644
--- a/aegisub/tests/libaegisub_line_iterator.cpp
+++ b/aegisub/tests/libaegisub_line_iterator.cpp
@@ -11,12 +11,6 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_line_iterator.cpp
-/// @brief agi::line_iterator tests
-/// @ingroup 
 
 #include <libaegisub/line_iterator.h>
 
diff --git a/aegisub/tests/libaegisub_line_wrap.cpp b/aegisub/tests/libaegisub_line_wrap.cpp
index cd522fb3329e40dfd0e4fd11f1dfd68c5ab3657f..edee7bd09c1e84bd209633db081911c9d27f1f5d 100644
--- a/aegisub/tests/libaegisub_line_wrap.cpp
+++ b/aegisub/tests/libaegisub_line_wrap.cpp
@@ -11,11 +11,6 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_line_wrap.cpp
-/// @brief agi::get_wrap_points tests
 
 #include <libaegisub/line_wrap.h>
 
diff --git a/aegisub/tests/libaegisub_mru.cpp b/aegisub/tests/libaegisub_mru.cpp
index 123aedbf38f0f52e14466282814e24cf3d178a76..803d2ffcbd8dd6023f8e4b0ce02c5531a5ae3a69 100644
--- a/aegisub/tests/libaegisub_mru.cpp
+++ b/aegisub/tests/libaegisub_mru.cpp
@@ -11,16 +11,11 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_mru.cpp
-/// @brief agi::mru (Most Recently Used)
-/// @ingroup mru
 
+#include <libaegisub/fs.h>
 #include <libaegisub/mru.h>
+
 #include "main.h"
-#include "util.h"
 
 class lagi_mru : public libagi {
 protected:
@@ -34,56 +29,58 @@ protected:
 };
 
 
-TEST_F(lagi_mru, MRUConstructFromFile) {
+TEST_F(lagi_mru, load_from_file) {
 	ASSERT_NO_THROW(agi::MRUManager mru(conf_ok, default_mru));
 	agi::MRUManager mru(conf_ok, default_mru);
-	agi::MRUManager::MRUListMap::const_iterator entry = mru.Get("Valid")->begin();
-	EXPECT_STREQ("Entry One", (*entry++).c_str());
-	EXPECT_STREQ("Entry Two", (*entry++).c_str());
+	ASSERT_NO_THROW(mru.Get("Valid"));
+	ASSERT_EQ(2u, mru.Get("Valid")->size());
+	auto entry = mru.Get("Valid")->begin();
+	EXPECT_STREQ("Entry One", (*entry++).string().c_str());
+	EXPECT_STREQ("Entry Two", (*entry++).string().c_str());
 	EXPECT_TRUE(mru.Get("Valid")->end() == entry);
 }
 
-TEST_F(lagi_mru, MRUConstructFromString) {
-	util::remove("data/mru_tmp");
+TEST_F(lagi_mru, load_from_default_string) {
+	agi::fs::Remove("data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 }
 
-TEST_F(lagi_mru, MRUConstructInvalid) {
-	util::copy("data/mru_invalid.json", "data/mru_tmp");
+TEST_F(lagi_mru, load_from_invalid_file) {
+	agi::fs::Copy("data/mru_invalid.json", "data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 	EXPECT_TRUE(mru.Get("Invalid")->empty());
 }
 
-TEST_F(lagi_mru, MRUEntryAdd) {
-	util::copy("data/mru_ok.json", "data/mru_tmp");
+TEST_F(lagi_mru, add_entry) {
+	agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 	EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file"));
-	EXPECT_STREQ("/path/to/file", mru.Get("Valid")->front().c_str());
+	EXPECT_STREQ("/path/to/file", mru.Get("Valid")->front().string().c_str());
 }
 
-TEST_F(lagi_mru, MRUEntryRemove) {
-	util::copy("data/mru_ok.json", "data/mru_tmp");
+TEST_F(lagi_mru, remove_entry) {
+	agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 	EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file"));
 	EXPECT_NO_THROW(mru.Remove("Valid", "/path/to/file"));
-	EXPECT_STRNE("/path/to/file", mru.Get("Valid")->front().c_str());
+	EXPECT_STRNE("/path/to/file", mru.Get("Valid")->front().string().c_str());
 }
 
-TEST_F(lagi_mru, MRUKeyInvalid) {
-	util::copy("data/mru_ok.json", "data/mru_tmp");
+TEST_F(lagi_mru, invalid_mru_key_throws) {
+	agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 	EXPECT_THROW(mru.Add("Invalid", "/path/to/file"), agi::MRUErrorInvalidKey);
 	EXPECT_THROW(mru.Get("Invalid"), agi::MRUErrorInvalidKey);
 }
 
-TEST_F(lagi_mru, MRUKeyValid) {
-	util::copy("data/mru_ok.json", "data/mru_tmp");
+TEST_F(lagi_mru, valid_mru_key_doesnt_throw) {
+	agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 	EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file"));
 }
 
-TEST_F(lagi_mru, MRUAddSeveral) {
-	util::remove("data/mru_tmp");
+TEST_F(lagi_mru, adding_existing_moves_to_front) {
+	agi::fs::Remove("data/mru_tmp");
 	agi::MRUManager mru("data/mru_tmp", default_mru);
 
 	EXPECT_NO_THROW(mru.Add("Valid", "/file/1"));
@@ -93,9 +90,9 @@ TEST_F(lagi_mru, MRUAddSeveral) {
 	EXPECT_NO_THROW(mru.Add("Valid", "/file/1"));
 	EXPECT_NO_THROW(mru.Add("Valid", "/file/3"));
 
-	EXPECT_STREQ("/file/3", mru.GetEntry("Valid", 0).c_str());
-	EXPECT_STREQ("/file/1", mru.GetEntry("Valid", 1).c_str());
-	EXPECT_STREQ("/file/2", mru.GetEntry("Valid", 2).c_str());
+	EXPECT_STREQ("/file/3", mru.GetEntry("Valid", 0).string().c_str());
+	EXPECT_STREQ("/file/1", mru.GetEntry("Valid", 1).string().c_str());
+	EXPECT_STREQ("/file/2", mru.GetEntry("Valid", 2).string().c_str());
 	EXPECT_THROW(mru.GetEntry("Valid", 3), agi::MRUErrorIndexOutOfRange);
 }
 
diff --git a/aegisub/tests/libaegisub_option.cpp b/aegisub/tests/libaegisub_option.cpp
index 9210986e9aea6e5579d7c0b755b32b0aa59401a6..3c093730d0682faae06259b99c5b572266972a2d 100644
--- a/aegisub/tests/libaegisub_option.cpp
+++ b/aegisub/tests/libaegisub_option.cpp
@@ -11,13 +11,8 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_option.cpp
-/// @brief Option class
-/// @ingroup option
 
+#include <libaegisub/fs.h>
 #include <libaegisub/option.h>
 #include <libaegisub/option_value.h>
 
@@ -56,7 +51,7 @@ TEST_F(lagi_option, get_nonexistant_option) {
 }
 
 TEST_F(lagi_option, flush_skip) {
-	util::copy("data/options/string.json", "data/options/tmp");
+	agi::fs::Copy("data/options/string.json", "data/options/tmp");
 	{
 		agi::Options opt("data/options/tmp", default_opt, agi::Options::FLUSH_SKIP);
 		ASSERT_NO_THROW(opt.Get("Valid")->SetString(""));
@@ -65,7 +60,7 @@ TEST_F(lagi_option, flush_skip) {
 }
 
 TEST_F(lagi_option, flush_no_skip) {
-	util::copy("data/options/string.json", "data/options/tmp");
+	agi::fs::Copy("data/options/string.json", "data/options/tmp");
 	{
 		agi::Options opt("data/options/tmp", default_opt);
 		ASSERT_NO_THROW(opt.Get("Valid")->SetString(""));
@@ -124,7 +119,7 @@ TEST_F(lagi_option, heterogeneous_arrays_rejected) {
 }
 
 TEST_F(lagi_option, flush_roundtrip) {
-	util::remove("data/options/tmp");
+	agi::fs::Remove("data/options/tmp");
 
 	{
 		agi::Options opt("data/options/tmp", "{}");
diff --git a/aegisub/tests/libaegisub_path.cpp b/aegisub/tests/libaegisub_path.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b20a9e66d073d3a8dfb2d7fb40332aa63275f52e
--- /dev/null
+++ b/aegisub/tests/libaegisub_path.cpp
@@ -0,0 +1,132 @@
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#include <libaegisub/exception.h>
+#include <libaegisub/fs.h>
+#include <libaegisub/path.h>
+
+#include "main.h"
+
+#include <boost/filesystem.hpp>
+
+using agi::Path;
+
+#ifdef _WIN32
+#define DS "\\"
+#else
+#define DS "/"
+#endif
+
+TEST(lagi_path, invalid_token_name_throws_internal_error) {
+	Path p;
+
+	// These are InternalError because the tokens are currently always hardcoded
+	EXPECT_THROW(p.SetToken("no ?", "path"), agi::InternalError);
+	EXPECT_THROW(p.SetToken("?bad", "path"), agi::InternalError);
+}
+
+TEST(lagi_path, relative_path_clears_token) {
+	Path p;
+
+	EXPECT_NO_THROW(p.SetToken("?video", "relative/path"));
+	EXPECT_STREQ("?video", p.Decode("?video").string().c_str());
+
+	EXPECT_NO_THROW(p.SetToken("?video", boost::filesystem::current_path()));
+	EXPECT_STRNE("?video", p.Decode("?video").string().c_str());
+
+	EXPECT_NO_THROW(p.SetToken("?video", "relative/path"));
+	EXPECT_STREQ("?video", p.Decode("?video").string().c_str());
+}
+
+TEST(lagi_path, empty_path_clears_token) {
+	Path p;
+
+	EXPECT_NO_THROW(p.SetToken("?video", boost::filesystem::current_path()));
+	EXPECT_STRNE("?video", p.Decode("?video").string().c_str());
+
+	EXPECT_NO_THROW(p.SetToken("?video", ""));
+	EXPECT_STREQ("?video", p.Decode("?video").string().c_str());
+}
+
+TEST(lagi_path, decode_sets_uses_right_slashes) {
+	Path p;
+
+	agi::fs::path expected = boost::filesystem::current_path()/"foo/bar.txt";
+	expected.make_preferred();
+
+	EXPECT_NO_THROW(p.SetToken("?video", boost::filesystem::current_path()));
+
+	agi::fs::path decoded;
+	ASSERT_NO_THROW(decoded = p.Decode("?video/foo/bar.txt"));
+	EXPECT_STREQ(expected.string().c_str(), decoded.string().c_str());
+}
+
+TEST(lagi_path, trailing_slash_on_token_is_optional) {
+	Path p;
+
+	agi::fs::path expected = boost::filesystem::current_path()/"foo.txt";
+	expected.make_preferred();
+
+	EXPECT_NO_THROW(p.SetToken("?audio", boost::filesystem::current_path()));
+
+	agi::fs::path decoded;
+	ASSERT_NO_THROW(decoded = p.Decode("?audiofoo.txt"));
+	EXPECT_STREQ(expected.string().c_str(), decoded.string().c_str());
+
+	ASSERT_NO_THROW(decoded = p.Decode("?audio/foo.txt"));
+	EXPECT_STREQ(expected.string().c_str(), decoded.string().c_str());
+}
+
+TEST(lagi_path, setting_token_to_file_sets_to_parent_directory_instead) {
+	Path p;
+
+	agi::fs::path file = boost::filesystem::system_complete("data/file");
+	ASSERT_NO_THROW(p.SetToken("?script", file));
+	EXPECT_STREQ(file.parent_path().string().c_str(), p.Decode("?script").string().c_str());
+
+	file = boost::filesystem::system_complete("data/dir");
+	ASSERT_NO_THROW(p.SetToken("?script", file));
+	EXPECT_STREQ(file.string().c_str(), p.Decode("?script").string().c_str());
+}
+
+TEST(lagi_path, valid_token_names) {
+	Path p;
+
+	EXPECT_NO_THROW(p.SetToken("?user", ""));
+	EXPECT_NO_THROW(p.SetToken("?local", ""));
+	EXPECT_NO_THROW(p.SetToken("?data", ""));
+	EXPECT_NO_THROW(p.SetToken("?temp", ""));
+	EXPECT_NO_THROW(p.SetToken("?dictionary", ""));
+	EXPECT_NO_THROW(p.SetToken("?docs", ""));
+	EXPECT_NO_THROW(p.SetToken("?audio", ""));
+	EXPECT_NO_THROW(p.SetToken("?script", ""));
+	EXPECT_NO_THROW(p.SetToken("?video", ""));
+}
+
+#define TEST_PLATFORM_PATH_TOKEN(tok) \
+	do { \
+		agi::fs::path d; \
+		ASSERT_NO_THROW(d = p.Decode(tok)); \
+		ASSERT_FALSE(d.empty()); \
+		ASSERT_STRNE(tok, d.string().c_str()); \
+		EXPECT_TRUE(agi::fs::DirectoryExists(d)); \
+	} while (false)
+
+TEST(lagi_path, platform_paths_have_values_and_exist) {
+	Path p;
+	TEST_PLATFORM_PATH_TOKEN("?data");
+	TEST_PLATFORM_PATH_TOKEN("?user");
+	TEST_PLATFORM_PATH_TOKEN("?local");
+	TEST_PLATFORM_PATH_TOKEN("?temp");
+}
diff --git a/aegisub/tests/libaegisub_signals.cpp b/aegisub/tests/libaegisub_signals.cpp
index a56710ff8fda52cc3d0a1b157d6a032442aaea47..1e79863235fac008f345d741e547f0739976b605 100644
--- a/aegisub/tests/libaegisub_signals.cpp
+++ b/aegisub/tests/libaegisub_signals.cpp
@@ -11,12 +11,6 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file libaegisub_signals.cpp
-/// @brief agi::signals tests
-/// @ingroup 
 
 #include <libaegisub/signal.h>
 
diff --git a/aegisub/tests/libaegisub_thesaurus.cpp b/aegisub/tests/libaegisub_thesaurus.cpp
index 6239613f99f6b20ab9135856e159cc4e83ea63e2..9988c1e2133e7c71f553317c7204f5757a1eef6e 100644
--- a/aegisub/tests/libaegisub_thesaurus.cpp
+++ b/aegisub/tests/libaegisub_thesaurus.cpp
@@ -11,9 +11,8 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
 
+#include <libaegisub/fs.h>
 #include <libaegisub/thesaurus.h>
 
 #include "main.h"
diff --git a/aegisub/tests/libaegisub_util.cpp b/aegisub/tests/libaegisub_util.cpp
index 0dcb08c5e732ae8b4d6058809620bcfc8b8f79dd..82af8e0d47c0ffdcbde87b95ab8aa59435ced8fc 100644
--- a/aegisub/tests/libaegisub_util.cpp
+++ b/aegisub/tests/libaegisub_util.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,125 +12,15 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
-// $Id$
+// Aegisub Project http://www.aegisub.org/
 
-/// @file libaegisub_util.cpp
-/// @brief agi::util (Utilities)
-/// @ingroup util
-
-#include <fstream>
-#include <libaegisub/access.h>
 #include <libaegisub/util.h>
+
 #include "main.h"
 
 class lagi_util : public libagi { };
 
 namespace agi {
-
-TEST(lagi_util, UtilDirnameEmpty) {
-	EXPECT_STREQ(".", util::DirName("").c_str());
-}
-
-TEST(lagi_util, UtilDirnameNoTrailingSlash) {
-	EXPECT_STREQ(".", util::DirName("dot").c_str());
-}
-
-TEST(lagi_util, UtilDirnameRoot) {
-	EXPECT_STREQ("/", util::DirName("/").c_str());
-}
-
-TEST(lagi_util, UtilDirnameHeir) {
-	EXPECT_STREQ("/last/part/not_stripped/", util::DirName("/last/part/not_stripped/").c_str());
-}
-
-TEST(lagi_util, UtilDirnameHeirNoTrailingSlash) {
-	EXPECT_STREQ("/last/part/", util::DirName("/last/part/stripped").c_str());
-}
-
-TEST(lagi_util, UtilRenameOverwrite) {
-	util::Rename("./data/rename_me_overwrite", "./data/rename_me_overwrite_renamed");
-	util::Rename("./data/rename_me_overwrite_renamed", "./data/rename_me_overwrite");
-	std::ofstream fp_touch("./data/rename_me_overwrite_renamed");
-}
-
-TEST(lagi_util, UtilRenameNew) {
-	util::Rename("./data/rename_me", "./data/rename_me_renamed");
-	util::Rename("./data/rename_me_renamed", "./data/rename_me");
-}
-
-TEST(lagi_util, UtilRenameExNotFound) {
-	EXPECT_THROW(util::Rename("./data/nonexistent", ""), FileNotFoundError);
-}
-
-TEST(lagi_util, RemoveExisting) {
-	std::ofstream("./data/file_to_remove");
-	EXPECT_NO_THROW(util::Remove("./data/file_to_remove"));
-	std::ifstream check("./data/file_to_remove");
-	EXPECT_FALSE(check.good());
-}
-
-TEST(lagi_util, RemoveNonExisting) {
-	EXPECT_NO_THROW(util::Remove("./data/nonexistent"));
-}
-
-TEST(lagi_util, RemoveReadOnly) {
-	EXPECT_THROW(util::Remove("./data/file_read_only"), FileNotAccessibleError);
-}
-
-TEST(lagi_util, Utilstr_lower) {
-	std::string str("-!ABCDEFGHIJKLMNOPQRSTUVWXYZ123");
-	util::str_lower(str);
-	EXPECT_STREQ("-!abcdefghijklmnopqrstuvwxyz123", str.c_str());
-}
-
-TEST(lagi_util, UtilstrtoiInvalidRange) {
-	std::string str("2147483650");
-	EXPECT_EQ(0, util::strtoi(str));
-
-	str.assign("-2147483650");
-	EXPECT_EQ(0, util::strtoi(str));
-}
-
-TEST(lagi_util, UtilstrtoiInvalidString) {
-	std::string str("bottles of beer on the wall");
-	EXPECT_EQ(0, util::strtoi(str));
-}
-
-TEST(lagi_util, UtilstrtoiNumberWithString) {
-	std::string str("24 bottles of beer on the wall");
-	EXPECT_EQ(24, util::strtoi(str));
-}
-
-TEST(lagi_util, UtilstrtoiValidString) {
-	std::string str("24");
-	int i;
-
-	EXPECT_NO_THROW(i = util::strtoi(str));
-	EXPECT_EQ(24, i);
-}
-
-TEST(lagi_util, UtilfreespaceFile) {
-	std::string path("./data/somefile");
-	EXPECT_NO_THROW(util::freespace(path, util::TypeFile));
-	EXPECT_ANY_THROW(util::freespace(path));
-
-}
-
-TEST(lagi_util, UtilfreespaceDir) {
-	std::string path("./data");
-	EXPECT_NO_THROW(util::freespace(path));
-}
-
-TEST(lagi_util, UtilfreespaceNoAccess) {
-	std::string path("./data/dir_access_denied");
-	EXPECT_THROW(util::freespace(path), acs::Read);
-}
-
-TEST(lagi_util, UtilfreespaceInvalid) {
-	std::string path("/nonexistent");
-	EXPECT_ANY_THROW(util::freespace(path));
-}
-
 TEST(lagi_util, try_parse_double) {
 	double d = 0.0;
 	EXPECT_TRUE(util::try_parse("1.0", &d));
@@ -152,4 +42,4 @@ TEST(lagi_util, try_parse_int) {
 	EXPECT_EQ(1.0, i);
 }
 
-} // namespace agi
+}
diff --git a/aegisub/tests/libaegisub_vfr.cpp b/aegisub/tests/libaegisub_vfr.cpp
index 212db9be6f6e7bce880f005182f9b9521d3e0455..78321c6cbf3bec4d5e5d58afd63e43b98fc668bb 100644
--- a/aegisub/tests/libaegisub_vfr.cpp
+++ b/aegisub/tests/libaegisub_vfr.cpp
@@ -12,12 +12,9 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
-// $Id$
-
-/// @file libaegisub_vfr.cpp
-/// @brief agi::vfr::Framerate tests
-/// @ingroup video_input
+// Aegisub Project http://www.aegisub.org/
 
+#include <libaegisub/fs.h>
 #include <libaegisub/vfr.h>
 
 #include <climits>
diff --git a/aegisub/tests/main.cpp b/aegisub/tests/main.cpp
index b96aeb15bed317c4167bbed61d57cbcc0822068d..7344b24de6f4038aa38d53ecef2946a46c47a8f1 100644
--- a/aegisub/tests/main.cpp
+++ b/aegisub/tests/main.cpp
@@ -11,18 +11,16 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file main.cpp
-/// @brief Main
-/// @ingroup main
 
 #include <gtest/gtest.h>
 
+#include <libaegisub/dispatch.h>
+#include <libaegisub/fs.h>
 #include <libaegisub/log.h>
 
 int main(int argc, char **argv) {
+	agi::dispatch::Init([](agi::dispatch::Thunk f) { });
+
 	int retval;
 	agi::log::log = new agi::log::LogSink;
 	agi::log::log->Subscribe(new agi::log::JsonEmitter("./", agi::log::log));
diff --git a/aegisub/tests/main.h b/aegisub/tests/main.h
index 078e231c94d5c5b9e8632fb8ef33f7cf88870445..847c43898efbc2f03a83dfa14d58d54d43b0d916 100644
--- a/aegisub/tests/main.h
+++ b/aegisub/tests/main.h
@@ -11,24 +11,9 @@
 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// $Id$
-
-/// @file main.h
-/// @brief Main header
-/// @ingroup main
 
 #include <gtest/gtest.h>
 
-/// A small macro to silence "unused variable" warnings.
-#define unused(x) x = x
-
 namespace {
-
-class libagi : public ::testing::Test {
-protected:
-    // place holder for future code placement
-};
-
-}  // namespace
-
+class libagi : public ::testing::Test { };
+}
diff --git a/aegisub/tests/util.cpp b/aegisub/tests/util.cpp
index 5f6f12f0f00f60b4ea6a707d23b023601e9fe69a..6061081d7acbd41e523753f5642db33040c887da 100644
--- a/aegisub/tests/util.cpp
+++ b/aegisub/tests/util.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,25 +12,14 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
-// $Id$
+// Aegisub Project http://www.aegisub.org/
 
-/// @file util.cpp
-/// @brief Common utilities used in tests.
-/// @ingroup util common
+#include "util.h"
 
 #include <fstream>
 #include <sstream>
-#include "util.h"
 
 namespace util {
-
-void copy(const std::string &from, const std::string &to) {
-	std::ifstream ifs(from.c_str(), std::ios::binary);
-	std::ofstream ofs(to.c_str(), std::ios::binary);
-
-	ofs << ifs.rdbuf();
-}
-
 bool compare(const std::string &file1, const std::string &file2) {
 	std::stringstream ss1, ss2;
 	std::ifstream if1(file1.c_str(), std::ios::binary), if2(file2.c_str(), std::ios::binary);
@@ -39,6 +28,4 @@ bool compare(const std::string &file1, const std::string &file2) {
 	return ss1.str() == ss2.str();
 }
 
-} // namespace util
-
-
+}
diff --git a/aegisub/tests/util.h b/aegisub/tests/util.h
index 411d4a38af33291ad9aa5a25d228dd9fedd77066..738b12ecba04f536f685b000cdaf4c5cdc5c2030 100644
--- a/aegisub/tests/util.h
+++ b/aegisub/tests/util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
+// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,11 +12,7 @@
 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
-// $Id$
-
-/// @file util.cpp
-/// @brief Common utilities used in tests.
-/// @ingroup util
+// Aegisub Project http://www.aegisub.org/
 
 #include <string>
 #include <vector>
@@ -24,10 +20,7 @@
 #include <stdarg.h>
 
 namespace util {
-
-void copy(const std::string &from, const std::string &to);
-bool compare(const std::string &file1, const std::string &file2);
-void remove(const std::string& file);
+bool compare(std::string const& file1, std::string const& file2);
 
 template<typename T>
 static std::vector<T> make_vector(int len, ...) {
@@ -42,6 +35,4 @@ static std::vector<T> make_vector(int len, ...) {
 	return vec;
 }
 
-} // namespace util
-
-
+}