From 40e12403d564bd4e15a832b1b3fe461676aa88a2 Mon Sep 17 00:00:00 2001
From: Amar Takhar <verm@aegisub.org>
Date: Fri, 21 May 2010 01:13:36 +0000
Subject: [PATCH] Merge all changes from the libaegisub branch into trunk, the
 effective range is r4175:4330.  All options have been re-done and now use
 Cajun to support a json-backed format.

Initial support for low-level access and file I/O methods are included as well.

Originally committed to SVN as r4331.
---
 aegisub/Makefile.am                           |    1 +
 .../aegisub_vs2008/aegisub_vs2008.vcproj      |  108 +-
 .../libaegisub_include_dir.vsprops            |   11 +
 .../compiler_options.vsprops                  |   26 +
 .../compiler_options_debug.vsprops            |   26 +
 .../libaegisub_vs2008.vcproj                  |  226 +++
 .../libraries_outdirs.vsprops                 |   27 +
 .../libaegisub_vs2008/precomp_header.vsprops  |   13 +
 .../src_msvc_include_dir.vsprops              |   11 +
 .../libaegisub_vs2008/wxlib_include.vsprops   |   15 +
 aegisub/configure.in                          |    2 +
 aegisub/docs/doxygen/doxyfile_libaegisub      |   12 +
 aegisub/docs/doxygen/gen.sh                   |    3 +
 .../docs/doxygen/pages_libaegisub/groups.dox  |    7 +
 .../docs/doxygen/pages_libaegisub/index.dox   |   11 +
 .../docs/doxygen/pages_libaegisub/license.dox |    5 +
 aegisub/libaegisub/Makefile.am                |   32 +
 aegisub/libaegisub/common/mru.cpp             |  203 +++
 aegisub/libaegisub/common/option.cpp          |  237 ++++
 aegisub/libaegisub/common/option_visit.cpp    |  242 ++++
 aegisub/libaegisub/common/option_visit.h      |   83 ++
 aegisub/libaegisub/common/validator.cpp       |   73 +
 .../libaegisub/include/libaegisub/access.h    |   58 +
 .../include/libaegisub/cajun/elements.h       |  272 ++++
 .../include/libaegisub/cajun/elements.inl     |  412 ++++++
 .../include/libaegisub/cajun/reader.h         |  125 ++
 .../include/libaegisub/cajun/reader.inl       |  519 +++++++
 .../include/libaegisub/cajun/visitor.h        |   44 +
 .../include/libaegisub/cajun/writer.h         |   57 +
 .../include/libaegisub/cajun/writer.inl       |  153 ++
 .../libaegisub/include/libaegisub/colour.h    |   31 +
 .../include/libaegisub}/exception.h           |   47 +-
 aegisub/libaegisub/include/libaegisub/io.h    |   57 +
 aegisub/libaegisub/include/libaegisub/mru.h   |  104 ++
 .../libaegisub/include/libaegisub/option.h    |  120 ++
 .../include/libaegisub/option_value.h         |  199 +++
 aegisub/libaegisub/include/libaegisub/util.h  |   36 +
 .../libaegisub/include/libaegisub/validator.h |   83 ++
 aegisub/libaegisub/lagi_pre.h                 |   23 +
 aegisub/libaegisub/unix/access.cpp            |  109 ++
 aegisub/libaegisub/unix/io.cpp                |   87 ++
 aegisub/libaegisub/unix/util.cpp              |   59 +
 aegisub/libaegisub/windows/access.cpp         |  118 ++
 aegisub/libaegisub/windows/io.cpp             |   89 ++
 aegisub/libaegisub/windows/lagi_pre.cpp       |    0
 aegisub/libaegisub/windows/util.cpp           |   59 +
 aegisub/src/Makefile.am                       |    7 +-
 aegisub/src/agi_pre.h                         |    4 -
 aegisub/src/ass_entry.h                       |    9 +-
 aegisub/src/ass_file.cpp                      |    6 +-
 aegisub/src/ass_file.h                        |    2 +-
 aegisub/src/audio_box.cpp                     |   34 +-
 aegisub/src/audio_display.cpp                 |  120 +-
 aegisub/src/audio_player.cpp                  |    4 +-
 aegisub/src/audio_player_alsa.cpp             |    2 +-
 aegisub/src/audio_player_dsound2.cpp          |    4 +-
 aegisub/src/audio_player_oss.cpp              |    4 +-
 aegisub/src/audio_player_portaudio.cpp        |    3 +-
 aegisub/src/audio_provider.cpp                |    8 +-
 aegisub/src/audio_provider_avs.cpp            |    6 +-
 aegisub/src/audio_provider_ffmpegsource.cpp   |    3 +-
 aegisub/src/audio_provider_hd.cpp             |    5 +-
 aegisub/src/audio_renderer_spectrum.cpp       |    7 +-
 aegisub/src/auto4_base.cpp                    |    6 +-
 aegisub/src/auto4_lua.cpp                     |    5 +-
 aegisub/src/auto4_lua.h                       |    1 +
 aegisub/src/avisynth_wrap.cpp                 |    5 +-
 aegisub/src/base_grid.cpp                     |   56 +-
 aegisub/src/compat.cpp                        |   14 +
 aegisub/src/compat.h                          |   15 +
 aegisub/src/dialog_attachments.cpp            |    8 +-
 aegisub/src/dialog_automation.cpp             |    5 +-
 aegisub/src/dialog_colorpicker.cpp            |   13 +-
 aegisub/src/dialog_detached_video.cpp         |   23 +-
 aegisub/src/dialog_dummy_video.cpp            |   32 +-
 aegisub/src/dialog_fonts_collector.cpp        |   11 +-
 aegisub/src/dialog_kara_timing_copy.cpp       |    6 +-
 aegisub/src/dialog_options.cpp                | 1229 -----------------
 aegisub/src/dialog_options.h                  |  186 ---
 aegisub/src/dialog_paste_over.cpp             |   16 +-
 aegisub/src/dialog_search_replace.cpp         |   36 +-
 aegisub/src/dialog_selection.cpp              |   34 +-
 aegisub/src/dialog_shift_times.cpp            |   24 +-
 aegisub/src/dialog_spellchecker.cpp           |    7 +-
 aegisub/src/dialog_style_editor.cpp           |   17 +-
 aegisub/src/dialog_style_manager.cpp          |    7 +-
 aegisub/src/dialog_text_import.cpp            |   11 +-
 aegisub/src/dialog_timing_processor.cpp       |   55 +-
 aegisub/src/dialog_tip.cpp                    |   10 +-
 aegisub/src/dialog_version_check.cpp          |   30 +-
 aegisub/src/ffmpegsource_common.cpp           |    9 +-
 aegisub/src/frame_main.cpp                    |   38 +-
 aegisub/src/frame_main.h                      |    2 +-
 aegisub/src/frame_main_events.cpp             |  110 +-
 aegisub/src/hilimod_textctrl.cpp              |    4 +-
 aegisub/src/keyframe.cpp                      |    6 +-
 aegisub/src/libresrc/Makefile.am              |    6 +-
 .../src/libresrc/OLD_TO_NEW_OPTION_MAP.txt    |  191 +++
 aegisub/src/libresrc/default_config.json      |  415 ++++++
 aegisub/src/libresrc/default_mru.json         |    8 +-
 aegisub/src/main.cpp                          |   73 +-
 aegisub/src/main.h                            |   10 +-
 aegisub/src/options.cpp                       |  815 -----------
 aegisub/src/options.h                         |  110 --
 aegisub/src/preferences.cpp                   |  566 ++++++++
 aegisub/src/preferences.h                     |   67 +
 aegisub/src/spellchecker.cpp                  |    4 +-
 aegisub/src/spellchecker_hunspell.cpp         |    8 +-
 aegisub/src/subs_edit_box.cpp                 |   19 +-
 aegisub/src/subs_edit_ctrl.cpp                |   38 +-
 aegisub/src/subs_grid.cpp                     |   21 +-
 aegisub/src/subtitle_format_ttxt.cpp          |    4 +-
 aegisub/src/subtitle_format_txt.cpp           |    8 +-
 aegisub/src/subtitles_provider.cpp            |    6 +-
 aegisub/src/text_file_writer.cpp              |    4 +-
 aegisub/src/thesaurus_myspell.cpp             |    8 +-
 aegisub/src/timeedit_ctrl.cpp                 |   14 +-
 aegisub/src/vfr.cpp                           |    4 +-
 aegisub/src/video_box.cpp                     |   11 +-
 aegisub/src/video_context.cpp                 |   14 +-
 aegisub/src/video_display.cpp                 |   15 +-
 aegisub/src/video_out_gl.cpp                  |    8 +-
 aegisub/src/video_out_gl.h                    |   20 +-
 aegisub/src/video_provider_cache.cpp          |    3 +-
 aegisub/src/video_provider_ffmpegsource.cpp   |    7 +-
 aegisub/src/video_provider_manager.cpp        |    4 +-
 aegisub/src/video_slider.cpp                  |    5 +-
 aegisub/src/visual_tool.cpp                   |    3 +-
 aegisub/tests/Makefile.am                     |   18 +
 aegisub/tests/libaegisub_access.cpp           |  109 ++
 aegisub/tests/libaegisub_cajun.cpp            |  159 +++
 aegisub/tests/libaegisub_mru.cpp              |   84 ++
 aegisub/tests/libaegisub_option.cpp           |   41 +
 aegisub/tests/libaegisub_util.cpp             |   70 +
 aegisub/tests/main.cpp                        |   27 +
 aegisub/tests/main.h                          |   34 +
 aegisub/tests/setup.sh                        |   40 +
 aegisub/tests/util.cpp                        |   39 +
 aegisub/tests/util.h                          |   28 +
 aegisub/tinderbox/www/doxygen.sh              |    3 +
 .../common_respack_packresources.vsprops      |    2 +-
 141 files changed, 6679 insertions(+), 2958 deletions(-)
 create mode 100644 aegisub/build/aegisub_vs2008/libaegisub_include_dir.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/compiler_options.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/compiler_options_debug.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj
 create mode 100644 aegisub/build/libaegisub_vs2008/libraries_outdirs.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/precomp_header.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/src_msvc_include_dir.vsprops
 create mode 100644 aegisub/build/libaegisub_vs2008/wxlib_include.vsprops
 create mode 100644 aegisub/docs/doxygen/doxyfile_libaegisub
 create mode 100644 aegisub/docs/doxygen/pages_libaegisub/groups.dox
 create mode 100644 aegisub/docs/doxygen/pages_libaegisub/index.dox
 create mode 100644 aegisub/docs/doxygen/pages_libaegisub/license.dox
 create mode 100644 aegisub/libaegisub/Makefile.am
 create mode 100644 aegisub/libaegisub/common/mru.cpp
 create mode 100644 aegisub/libaegisub/common/option.cpp
 create mode 100644 aegisub/libaegisub/common/option_visit.cpp
 create mode 100644 aegisub/libaegisub/common/option_visit.h
 create mode 100644 aegisub/libaegisub/common/validator.cpp
 create mode 100644 aegisub/libaegisub/include/libaegisub/access.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/elements.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/elements.inl
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/reader.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/reader.inl
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/visitor.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/writer.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/cajun/writer.inl
 create mode 100644 aegisub/libaegisub/include/libaegisub/colour.h
 rename aegisub/{src/include/aegisub => libaegisub/include/libaegisub}/exception.h (88%)
 create mode 100644 aegisub/libaegisub/include/libaegisub/io.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/mru.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/option.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/option_value.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/util.h
 create mode 100644 aegisub/libaegisub/include/libaegisub/validator.h
 create mode 100644 aegisub/libaegisub/lagi_pre.h
 create mode 100644 aegisub/libaegisub/unix/access.cpp
 create mode 100644 aegisub/libaegisub/unix/io.cpp
 create mode 100644 aegisub/libaegisub/unix/util.cpp
 create mode 100644 aegisub/libaegisub/windows/access.cpp
 create mode 100644 aegisub/libaegisub/windows/io.cpp
 create mode 100644 aegisub/libaegisub/windows/lagi_pre.cpp
 create mode 100644 aegisub/libaegisub/windows/util.cpp
 create mode 100644 aegisub/src/compat.cpp
 create mode 100644 aegisub/src/compat.h
 delete mode 100644 aegisub/src/dialog_options.cpp
 delete mode 100644 aegisub/src/dialog_options.h
 create mode 100644 aegisub/src/libresrc/OLD_TO_NEW_OPTION_MAP.txt
 create mode 100644 aegisub/src/libresrc/default_config.json
 create mode 100644 aegisub/src/preferences.cpp
 create mode 100644 aegisub/src/preferences.h
 create mode 100644 aegisub/tests/Makefile.am
 create mode 100644 aegisub/tests/libaegisub_access.cpp
 create mode 100644 aegisub/tests/libaegisub_cajun.cpp
 create mode 100644 aegisub/tests/libaegisub_mru.cpp
 create mode 100644 aegisub/tests/libaegisub_option.cpp
 create mode 100644 aegisub/tests/libaegisub_util.cpp
 create mode 100644 aegisub/tests/main.cpp
 create mode 100644 aegisub/tests/main.h
 create mode 100755 aegisub/tests/setup.sh
 create mode 100644 aegisub/tests/util.cpp
 create mode 100644 aegisub/tests/util.h

diff --git a/aegisub/Makefile.am b/aegisub/Makefile.am
index 5c400f5a9..59c9ca837 100644
--- a/aegisub/Makefile.am
+++ b/aegisub/Makefile.am
@@ -14,6 +14,7 @@ ffmpegsource = libffms
 endif
 
 SUBDIRS = \
+	libaegisub \
 	$(ffmpegsource) \
     $(libass) \
 	$(univchardet) \
diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
index ec670775a..b610ce8c7 100644
--- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
+++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
@@ -22,7 +22,7 @@
 		<Configuration
 			Name="Debug|Win32"
 			ConfigurationType="1"
-			InheritedPropertySheets=".\suffix_debug32.vsprops;.\wxlib_include.vsprops;.\wxlib_lib32.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\compiler_options_debug.vsprops;.\svn_rev_header_update.vsprops;.\delayload_portaudio_32.vsprops;.\delayload_openal_32.vsprops"
+			InheritedPropertySheets=".\suffix_debug32.vsprops;.\wxlib_include.vsprops;.\wxlib_lib32.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\compiler_options_debug.vsprops;.\svn_rev_header_update.vsprops;.\delayload_portaudio_32.vsprops;.\delayload_openal_32.vsprops;.\libaegisub_include_dir.vsprops"
 			CharacterSet="1"
 			>
 			<Tool
@@ -88,7 +88,7 @@
 		<Configuration
 			Name="Debug|x64"
 			ConfigurationType="1"
-			InheritedPropertySheets=".\suffix_debug64.vsprops;.\wxlib_include.vsprops;.\wxlib_lib64.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\compiler_options_debug.vsprops;.\svn_rev_header_update.vsprops"
+			InheritedPropertySheets=".\suffix_debug64.vsprops;.\wxlib_include.vsprops;.\wxlib_lib64.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\compiler_options_debug.vsprops;.\svn_rev_header_update.vsprops;.\libaegisub_include_dir.vsprops"
 			CharacterSet="1"
 			>
 			<Tool
@@ -154,7 +154,7 @@
 		<Configuration
 			Name="Release|Win32"
 			ConfigurationType="1"
-			InheritedPropertySheets=".\suffix_release32.vsprops;.\wxlib_include.vsprops;.\wxlib_lib32.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\svn_rev_header_update.vsprops;.\delayload_portaudio_32.vsprops;.\delayload_openal_32.vsprops"
+			InheritedPropertySheets=".\suffix_release32.vsprops;.\wxlib_include.vsprops;.\wxlib_lib32.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\svn_rev_header_update.vsprops;.\delayload_portaudio_32.vsprops;.\delayload_openal_32.vsprops;.\libaegisub_include_dir.vsprops"
 			CharacterSet="1"
 			WholeProgramOptimization="1"
 			>
@@ -223,7 +223,7 @@
 		<Configuration
 			Name="Release|x64"
 			ConfigurationType="1"
-			InheritedPropertySheets=".\suffix_release64.vsprops;.\wxlib_include.vsprops;.\wxlib_lib64.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\svn_rev_header_update.vsprops"
+			InheritedPropertySheets=".\suffix_release64.vsprops;.\wxlib_include.vsprops;.\wxlib_lib64.vsprops;.\libraries_outdirs.vsprops;.\contrib_includedirs.vsprops;.\aegisub_exe_filename.vsprops;.\ffms2_lib_include_dir.vsprops;.\compiler_options.vsprops;.\svn_rev_header_update.vsprops;.\libaegisub_include_dir.vsprops"
 			CharacterSet="1"
 			WholeProgramOptimization="1"
 			>
@@ -1103,14 +1103,6 @@
 				RelativePath="..\..\src\dialog_kara_timing_copy.h"
 				>
 			</File>
-			<File
-				RelativePath="..\..\src\dialog_options.cpp"
-				>
-			</File>
-			<File
-				RelativePath="..\..\src\dialog_options.h"
-				>
-			</File>
 			<File
 				RelativePath="..\..\src\dialog_paste_over.cpp"
 				>
@@ -1255,10 +1247,62 @@
 				RelativePath="..\..\src\dialog_video_details.h"
 				>
 			</File>
+			<File
+				RelativePath="..\..\src\preferences.cpp"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="Core"
 			>
+			<File
+				RelativePath="..\..\src\agi_pre.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\..\src\agi_pre.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\src\compat.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\src\compat.h"
+				>
+			</File>
 			<File
 				RelativePath="..\..\src\frame_main.cpp"
 				>
@@ -1307,46 +1351,6 @@
 				RelativePath="..\..\src\setup.cpp"
 				>
 			</File>
-			<File
-				RelativePath="..\..\src\agi_pre.cpp"
-				>
-				<FileConfiguration
-					Name="Debug|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Debug|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|Win32"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-				<FileConfiguration
-					Name="Release|x64"
-					>
-					<Tool
-						Name="VCCLCompilerTool"
-						UsePrecompiledHeader="1"
-					/>
-				</FileConfiguration>
-			</File>
-			<File
-				RelativePath="..\..\src\agi_pre.h"
-				>
-			</File>
 			<File
 				RelativePath="..\..\src\subs_edit_box.cpp"
 				>
diff --git a/aegisub/build/aegisub_vs2008/libaegisub_include_dir.vsprops b/aegisub/build/aegisub_vs2008/libaegisub_include_dir.vsprops
new file mode 100644
index 000000000..04bbc734c
--- /dev/null
+++ b/aegisub/build/aegisub_vs2008/libaegisub_include_dir.vsprops
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="libaegisub_include_dir"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		AdditionalIncludeDirectories="&quot;$(ProjectDir)\..\..\libaegisub\include&quot;"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/compiler_options.vsprops b/aegisub/build/libaegisub_vs2008/compiler_options.vsprops
new file mode 100644
index 000000000..da57a9c7a
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/compiler_options.vsprops
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="compiler_options"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		EnableIntrinsicFunctions="true"
+		RuntimeLibrary="2"
+		OpenMP="true"
+		WarningLevel="3"
+		WarnAsError="true"
+        DebugInformationFormat="3"
+        PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS"
+	/>
+	<Tool
+		Name="VCLinkerTool"
+		LinkIncremental="1"
+		GenerateManifest="true"
+		GenerateDebugInformation="true"
+		SubSystem="2"
+		LargeAddressAware="2"
+		DataExecutionPrevention="2"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/compiler_options_debug.vsprops b/aegisub/build/libaegisub_vs2008/compiler_options_debug.vsprops
new file mode 100644
index 000000000..fe1663f67
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/compiler_options_debug.vsprops
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="compiler_options_debug"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		Optimization="0"
+		InlineFunctionExpansion="0"
+		EnableIntrinsicFunctions="false"
+		WholeProgramOptimization="false"
+		PreprocessorDefinitions="DEBUG;_DEBUG;_CRT_SECURE_NO_WARNINGS"
+		BasicRuntimeChecks="3"
+		RuntimeLibrary="3"
+		BufferSecurityCheck="true"
+		EnableFunctionLevelLinking="true"
+		DebugInformationFormat="4"
+        EnablePREfast="true"
+	/>
+	<Tool
+		Name="VCLinkerTool"
+		LinkIncremental="2"
+		RandomizedBaseAddress="1"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj b/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj
new file mode 100644
index 000000000..083430e21
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="libaegisub_vs2008"
+	ProjectGUID="{8732F2B6-B3C6-4AE8-986A-2A334E33334B}"
+	RootNamespace="libaegisub_vs2008"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+		<Platform
+			Name="x64"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			ConfigurationType="4"
+			InheritedPropertySheets=".\wxlib_include.vsprops;.\libraries_outdirs.vsprops;.\compiler_options_debug.vsprops;..\aegisub_vs2008\wxlib_lib32.vsprops;..\aegisub_vs2008\suffix_debug32.vsprops;.\precomp_header.vsprops;.\src_msvc_include_dir.vsprops"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			ConfigurationType="4"
+			InheritedPropertySheets=".\wxlib_include.vsprops;.\libraries_outdirs.vsprops;.\compiler_options.vsprops;..\aegisub_vs2008\wxlib_lib32.vsprops;..\aegisub_vs2008\suffix_release32.vsprops;.\precomp_header.vsprops"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|x64"
+			ConfigurationType="4"
+			InheritedPropertySheets=".\wxlib_include.vsprops;.\libraries_outdirs.vsprops;.\compiler_options_debug.vsprops;..\aegisub_vs2008\wxlib_lib64.vsprops;..\aegisub_vs2008\suffix_debug64.vsprops;.\precomp_header.vsprops"
+			CharacterSet="1"
+			>
+			<Tool
+				Name="VCMIDLTool"
+				TargetEnvironment="3"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|x64"
+			ConfigurationType="4"
+			InheritedPropertySheets=".\wxlib_include.vsprops;.\libraries_outdirs.vsprops;.\compiler_options.vsprops;..\aegisub_vs2008\wxlib_lib64.vsprops;..\aegisub_vs2008\suffix_release64.vsprops;.\precomp_header.vsprops"
+			CharacterSet="1"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCMIDLTool"
+				TargetEnvironment="3"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Common"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath="..\..\libaegisub\common\mru.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\common\option.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\common\option_visit.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\common\option_visit.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\common\validator.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Windows"
+			Filter="h;hpp;hxx;hm;inl;inc;xsd"
+			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+			>
+			<File
+				RelativePath="..\..\libaegisub\windows\access.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\windows\io.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\windows\lagi_pre.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\windows\util.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="Include"
+			>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\access.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\colour.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\exception.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\io.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\mru.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\option.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\option_value.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\util.h"
+				>
+			</File>
+			<File
+				RelativePath="..\..\libaegisub\include\libaegisub\validator.h"
+				>
+			</File>
+		</Filter>
+		<File
+			RelativePath="..\..\libaegisub\lagi_pre.h"
+			>
+		</File>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>
diff --git a/aegisub/build/libaegisub_vs2008/libraries_outdirs.vsprops b/aegisub/build/libaegisub_vs2008/libraries_outdirs.vsprops
new file mode 100644
index 000000000..f012a6c1f
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/libraries_outdirs.vsprops
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="standard_outdirs"
+	OutputDirectory="$(SolutionDir)build/$(ProjectName)/$(PlatformName)/$(ConfigurationName)/"
+	IntermediateDirectory="$(OutDir)"
+	>
+	<Tool
+		Name="VCLibrarianTool"
+		OutputFile="$(LibraryOutDir)/$(ProjectName).lib"
+	/>
+	<Tool
+		Name="VCLinkerTool"
+		OutputFile=""
+		AdditionalLibraryDirectories="$(LibraryOutDir)"
+		ImportLibrary="$(LibraryOutDir)$(TargetName).lib"
+	/>
+	<UserMacro
+		Name="LibraryOutDir"
+		Value="$(SolutionDir)lib/$(PlatformName)/$(ConfigurationName)/"
+	/>
+	<UserMacro
+		Name="ExecutableOutDir"
+		Value="$(SolutionDir)bin/"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/precomp_header.vsprops b/aegisub/build/libaegisub_vs2008/precomp_header.vsprops
new file mode 100644
index 000000000..5da6833a5
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/precomp_header.vsprops
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="precomp_header"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		UsePrecompiledHeader="2"
+		PrecompiledHeaderThrough="../../libaegisub/lagi_pre.h"
+        ForcedIncludeFiles="../../libaegisub/lagi_pre.h"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/src_msvc_include_dir.vsprops b/aegisub/build/libaegisub_vs2008/src_msvc_include_dir.vsprops
new file mode 100644
index 000000000..adc9c6cd6
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/src_msvc_include_dir.vsprops
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="src_msvc_include_dir"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		AdditionalIncludeDirectories="&quot;$(ProjectDir)\..\..\src\msvc&quot;"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/build/libaegisub_vs2008/wxlib_include.vsprops b/aegisub/build/libaegisub_vs2008/wxlib_include.vsprops
new file mode 100644
index 000000000..5029d0eae
--- /dev/null
+++ b/aegisub/build/libaegisub_vs2008/wxlib_include.vsprops
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="wxlib_include"
+	>
+	<Tool
+		Name="VCCLCompilerTool"
+		AdditionalIncludeDirectories="&quot;$(SolutionDir)/wxlib/include/&quot;;&quot;$(ProjectDir)../../libaegisub/include/&quot;"
+	/>
+	<Tool
+		Name="VCResourceCompilerTool"
+		AdditionalIncludeDirectories="&quot;$(SolutionDir)/wxlib/include/&quot;"
+	/>
+</VisualStudioPropertySheet>
diff --git a/aegisub/configure.in b/aegisub/configure.in
index a164b575c..0036a4808 100644
--- a/aegisub/configure.in
+++ b/aegisub/configure.in
@@ -1204,6 +1204,7 @@ AC_CONFIG_FILES([
 Makefile
 automation/Makefile
 desktop/Makefile
+libaegisub/Makefile
 libass/Makefile
 libffms/Makefile
 po/Makefile.in
@@ -1214,6 +1215,7 @@ src/libosxutil/Makefile
 src/libresrc/Makefile
 tools/Makefile
 universalchardet/Makefile
+tests/Makefile
 ])
 
 # Files that need substitution.
diff --git a/aegisub/docs/doxygen/doxyfile_libaegisub b/aegisub/docs/doxygen/doxyfile_libaegisub
new file mode 100644
index 000000000..cc97d8124
--- /dev/null
+++ b/aegisub/docs/doxygen/doxyfile_libaegisub
@@ -0,0 +1,12 @@
+@INCLUDE = doxyfile_base
+
+PROJECT_NAME           = "libaegisub"
+
+INPUT                  = ../../libaegisub/ ./pages_libaegisub
+
+EXCLUDE_PATTERNS       = */.svn* */.deps* */.libs*
+
+PREDEFINED             +=	\
+							__WINDOWS__ \
+							__UNIX__ \
+							__OSX__
diff --git a/aegisub/docs/doxygen/gen.sh b/aegisub/docs/doxygen/gen.sh
index dc84baadb..a980b4e09 100755
--- a/aegisub/docs/doxygen/gen.sh
+++ b/aegisub/docs/doxygen/gen.sh
@@ -17,6 +17,9 @@ case "$1" in
 	"reporter")
 		TRIM="${SRC_PWD}/reporter/"
 	;;
+	"libaegisub")
+		TRIM="${SRC_PWD}/libaegisub/"
+	;;
 esac
 
 export OUTPUT_DIR="$2"
diff --git a/aegisub/docs/doxygen/pages_libaegisub/groups.dox b/aegisub/docs/doxygen/pages_libaegisub/groups.dox
new file mode 100644
index 000000000..13172d4be
--- /dev/null
+++ b/aegisub/docs/doxygen/pages_libaegisub/groups.dox
@@ -0,0 +1,7 @@
+/**
+@defgroup base		Base
+@defgroup unix		Unix
+@defgroup windows	Windows
+@defgroup osx		OS X
+@defgroup io		File I/O
+*/
diff --git a/aegisub/docs/doxygen/pages_libaegisub/index.dox b/aegisub/docs/doxygen/pages_libaegisub/index.dox
new file mode 100644
index 000000000..c47304e82
--- /dev/null
+++ b/aegisub/docs/doxygen/pages_libaegisub/index.dox
@@ -0,0 +1,11 @@
+/** @mainpage
+
+Main
+ - @ref base
+
+Library
+ - @ref unix
+ - @ref windows
+ - @ref osx
+ - @ref io
+*/
diff --git a/aegisub/docs/doxygen/pages_libaegisub/license.dox b/aegisub/docs/doxygen/pages_libaegisub/license.dox
new file mode 100644
index 000000000..903d32cb6
--- /dev/null
+++ b/aegisub/docs/doxygen/pages_libaegisub/license.dox
@@ -0,0 +1,5 @@
+/** @page License libaegisub License
+
+@verbinclude "LICENCE"
+
+*/
diff --git a/aegisub/libaegisub/Makefile.am b/aegisub/libaegisub/Makefile.am
new file mode 100644
index 000000000..af8c1cb15
--- /dev/null
+++ b/aegisub/libaegisub/Makefile.am
@@ -0,0 +1,32 @@
+# $Id$
+AUTOMAKE_OPTIONS = subdir-objects
+AM_CXXFLAGS=
+DISTCLEANFILES=
+
+lib_LTLIBRARIES = libaegisub-2.2.la
+libaegisub_2_2_la_CPPFLAGS = -I../src/include -Iinclude -I. @WX_CPPFLAGS@
+
+if PRECOMPILED_HEADER
+BUILT_SOURCES = lagi_pre.h.gch
+AM_CXXFLAGS += -include lagi_pre.h -Winvalid-pch -fpch-deps -fpch-preprocess
+nodist_libaegisub_2_2_la_SOURCES = lagi_pre.h.gch
+endif
+
+if PRECOMPILED_HEADER
+# This doesn't depend on Makefile on purpose, you should already know what you're doing when using this.
+lagi_pre.h.gch: lagi_pre.h
+	@CXX@ $(libaegisub_2_2_la_CPPFLAGS) @CXXFLAGS@ @DEBUG_FLAGS@ -fPIC -DPIC lagi_pre.h
+DISTCLEANFILES += lagi_pre.h.gch
+endif
+
+libaegisub_2_2_la_SOURCES = \
+	common/mru.cpp \
+	common/option.cpp \
+	common/option_visit.cpp \
+	common/validator.cpp \
+	unix/util.cpp \
+	unix/io.cpp \
+	unix/access.cpp
+
+noinst_HEADERS = *.h
+
diff --git a/aegisub/libaegisub/common/mru.cpp b/aegisub/libaegisub/common/mru.cpp
new file mode 100644
index 000000000..6cbe9e739
--- /dev/null
+++ b/aegisub/libaegisub/common/mru.cpp
@@ -0,0 +1,203 @@
+// 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.
+//
+// $Id$
+
+/// @file mru.cpp
+/// @brief Most Recently Used (MRU) Lists
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <fstream>
+#include <time.h>
+#endif
+
+#include "libaegisub/access.h"
+#include "libaegisub/mru.h"
+#include "libaegisub/io.h"
+
+namespace agi {
+
+MRUManager::MRUManager(const std::string &config, const std::string &default_config): config_name(config) {
+
+	json::UnknownElement root;
+	std::istream *stream;
+
+	try {
+		stream = io::Open(config);
+	} catch (const acs::AcsNotFound&) {
+		stream = new std::istringstream(default_config);
+	}
+
+	try {
+		json::Reader::Read(root, *stream);
+	} catch (const json::Exception&) {
+		/// @todo Do something better here, maybe print the exact error
+//		std::cout << "json::Exception: " << e.what() << std::endl;
+
+		stream = new std::istringstream(default_config);
+		json::Reader::Read(root, *stream);
+	}
+
+	const json::Object& root_new = (json::Object)root;
+
+	json::Object::const_iterator index_object(root_new.Begin()), index_objectEnd(root_new.End());
+
+	for (; index_object != index_objectEnd; ++index_object) {
+		const json::Object::Member& member = *index_object;
+		const std::string &member_name = member.name;
+		const json::UnknownElement& element = member.element;
+
+		Load(member_name, (json::Array)element);
+	}
+
+	delete stream;
+}
+
+
+MRUManager::~MRUManager() {
+	Flush();
+}
+
+
+void MRUManager::Add(const std::string &key, const std::string &entry) {
+
+	MRUMap::iterator index;
+
+	if ((index = mru.find(key)) != mru.end()) {
+		MRUListMap &map = *index->second;
+
+		// Remove the file before adding it.
+		Remove(key, entry);
+
+		map.insert(std::pair<time_t, std::string>(time(NULL), entry));
+
+		Prune(map);
+
+	} else {
+		throw MRUErrorInvalidKey("Invalid key value");
+	}
+}
+
+
+void MRUManager::Remove(const std::string &key, const std::string &entry) {
+
+	MRUMap::iterator index;
+
+	if ((index = mru.find(key)) != mru.end()) {
+		MRUListMap map = *index->second;
+		for (MRUListMap::iterator map_idx = map.begin(); map_idx != map.end();) {
+			if (map_idx->second == entry)
+				map.erase(map_idx++);
+			else
+				++map_idx;
+		}
+	} else {
+		throw MRUErrorInvalidKey("Invalid key value");
+	}
+
+}
+
+
+const MRUManager::MRUListMap* MRUManager::Get(const std::string &key) {
+
+	MRUMap::iterator index;
+
+	if ((index = mru.find(key)) != mru.end()) {
+		return index->second;
+	} else {
+		throw MRUErrorInvalidKey("Invalid key value");
+	}
+}
+
+
+const std::string MRUManager::GetEntry(const std::string &key, const int entry) {
+
+	const MRUManager::MRUListMap *map = Get(key);
+
+	MRUListMap::const_iterator index = map->begin();;
+
+	if ((unsigned int)entry > map->size())
+		throw MRUErrorIndexOutOfRange("Requested element index is out of range.");
+
+	std::advance(index, entry);
+
+	return index->second;
+}
+
+
+void MRUManager::Flush() {
+
+	json::Object out;
+
+	for (MRUMap::const_iterator i = mru.begin(); i != mru.end(); ++i) {
+		json::Array array;
+		MRUListMap *map_list = i->second;
+
+		for (MRUListMap::const_iterator i_lst = map_list->begin(); i_lst != map_list->end(); ++i_lst) {
+			json::Object obj;
+			obj["time"] = json::Number((double)i_lst->first);
+			obj["entry"] = json::String(i_lst->second);
+			array.Insert(obj);
+		}
+
+		out[i->first] = array;
+	}
+
+	io::Save file(config_name);
+	std::ofstream& ofp = file.Get();
+	json::Writer::Write(out, ofp);
+
+}
+
+
+/// @brief Prune MRUListMap to the desired length.
+/// This uses the user-set values for MRU list length.
+inline void MRUManager::Prune(MRUListMap& map) {
+	unsigned int size = 16;
+
+	MRUListMap::iterator index = map.begin();;
+
+	if (map.size() >= size) {
+		std::advance(index, size);
+
+		// Use a range incase the storage number shrinks.
+		map.erase(index, map.end());
+	}
+}
+
+/// @brief Load MRU Lists.
+/// @param key List name.
+/// @param array json::Array of values.
+void MRUManager::Load(const std::string &key, const json::Array& array) {
+
+	json::Array::const_iterator index(array.Begin()), indexEnd(array.End());
+
+	MRUListMap *map = new MRUListMap();
+
+	for (; index != indexEnd; ++index) {
+		const json::Object& obj = *index;
+
+		time_t time = (time_t)(json::Number)obj["time"];
+		std::string entry = (json::String)obj["entry"];
+
+		map->insert(std::pair<time_t, std::string>(time, entry));
+	}
+
+	mru.insert(std::pair<std::string, MRUListMap*>(key, map));
+	Prune(*map);
+}
+
+
+}
diff --git a/aegisub/libaegisub/common/option.cpp b/aegisub/libaegisub/common/option.cpp
new file mode 100644
index 000000000..e61e1e6cb
--- /dev/null
+++ b/aegisub/libaegisub/common/option.cpp
@@ -0,0 +1,237 @@
+// 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.
+//
+// $Id$
+
+/// @file option.cpp
+/// @brief Option interface.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <fstream>
+#include <sstream>
+#include <map>
+#endif
+
+#include "libaegisub/io.h"
+#include "option_visit.h"
+
+namespace agi {
+
+Options::Options(const std::string &file, const std::string& default_config):
+							config_file(file), config_default(default_config), config_loaded(false) {
+	std::istringstream stream(default_config);
+	LoadConfig(stream);
+}
+
+Options::~Options() {
+	Flush();
+}
+
+void Options::ConfigNext(std::istream& stream) {
+	LoadConfig(stream);
+}
+
+void Options::ConfigUser() {
+	std::istream *stream = agi::io::Open(config_file);
+	LoadConfig(*stream);
+	config_loaded = true;
+	delete stream;
+}
+
+
+void Options::LoadConfig(std::istream& stream) {
+	/// @todo Store all previously loaded configs in an array for bug report purposes,
+	///       this is just a temp stub.
+	json::UnknownElement config_root;
+
+	try {
+		json::Reader::Read(config_root, stream);
+	} catch (json::Reader::ParseException& e) {
+		std::cout << "json::ParseException: " << e.what() << ", Line/offset: " << e.m_locTokenBegin.m_nLine + 1 << '/' << e.m_locTokenBegin.m_nLineOffset + 1 << std::endl << std::endl;
+    } catch (json::Exception& e) {
+        /// @todo Do something better here, maybe print the exact error
+        std::cout << "json::Exception: " << e.what() << std::endl;
+	}
+
+	ConfigVisitor config_visitor(values, std::string(""));
+	config_root.Accept(config_visitor);
+}
+
+
+
+
+OptionValue* Options::Get(const std::string &name) {
+
+	OptionValueMap::iterator index;
+
+    if ((index = values.find(name)) != values.end())
+		return index->second;
+
+	std::cout << "agi::Options::Get Option not found: (" << name << ")" << std::endl;
+	throw OptionErrorNotFound("Option value not found");
+}
+
+
+
+void Options::Flush() {
+
+	json::Object obj_out;
+
+	bool ok;
+
+	for (OptionValueMap::const_iterator i = values.begin(); i != values.end(); ++i) {
+
+		std::string key = i->first.substr(i->first.rfind("/")+1, i->first.size());
+
+		int type = i->second->GetType();
+
+		switch (type) {
+			case OptionValue::Type_String: {
+				ok = PutOption(obj_out, i->first, (json::String)i->second->GetString());
+			}
+			break;
+
+			case OptionValue::Type_Int:
+				ok = PutOption(obj_out, i->first, (json::Number)(const double)i->second->GetInt());
+			break;
+
+			case OptionValue::Type_Double:
+				ok = PutOption(obj_out, i->first, (json::Number)i->second->GetDouble());
+			break;
+
+			case OptionValue::Type_Colour: {
+				std::string str = std::string(i->second->GetColour());
+				ok = PutOption(obj_out, i->first, (json::String)str);
+			}
+			break;
+
+			case OptionValue::Type_Bool:
+				ok = PutOption(obj_out, i->first, (json::Boolean)i->second->GetBool());
+			break;
+
+			case OptionValue::Type_List_String: {
+				std::vector<std::string> array_string;
+				i->second->GetListString(array_string);
+
+				json::Array array;
+
+				for (std::vector<std::string>::const_iterator i_str = array_string.begin(); i_str != array_string.end(); ++i_str) {
+					json::Object obj;
+					obj["string"] = json::String(*i_str);
+					array.Insert(obj);
+				}
+
+				ok = PutOption(obj_out, i->first, (json::Array)array);
+			}
+			break;
+
+			case OptionValue::Type_List_Int: {
+				std::vector<int64_t> array_int;
+				i->second->GetListInt(array_int);
+
+				json::Array array;
+
+				for (std::vector<int64_t>::const_iterator i_int = array_int.begin(); i_int != array_int.end(); ++i_int) {
+					json::Object obj;
+					obj["int"] = json::Number((const double)*i_int);
+					array.Insert(obj);
+				}
+				ok = PutOption(obj_out, i->first, (json::Array)array);
+			}
+			break;
+
+			case OptionValue::Type_List_Double: {
+				std::vector<double> array_double;
+				i->second->GetListDouble(array_double);
+
+				json::Array array;
+
+				for (std::vector<double>::const_iterator i_double = array_double.begin(); i_double != array_double.end(); ++i_double) {
+					json::Object obj;
+					obj["double"] = json::Number(*i_double);
+					array.Insert(obj);
+				}
+				ok = PutOption(obj_out, i->first, (json::Array)array);
+			}
+			break;
+
+			case OptionValue::Type_List_Colour: {
+				std::vector<Colour> array_colour;
+				i->second->GetListColour(array_colour);
+
+				json::Array array;
+				for (std::vector<Colour>::const_iterator i_colour = array_colour.begin(); i_colour != array_colour.end(); ++i_colour) {
+					json::Object obj;
+
+					Colour col = *i_colour;
+					std::string str = std::string(col);
+
+					obj["colour"] = json::String(str);
+
+					array.Insert(obj);
+				}
+				ok = PutOption(obj_out, i->first, (json::Array)array);
+			}
+			break;
+
+			case OptionValue::Type_List_Bool: {
+				std::vector<bool> array_bool;
+
+				json::Array array;
+
+				i->second->GetListBool(array_bool);
+				for (std::vector<bool>::const_iterator i_bool = array_bool.begin(); i_bool != array_bool.end(); ++i_bool) {
+					json::Object obj;
+					obj["bool"] = json::Boolean(*i_bool);
+					array.Insert(obj);
+				}
+				ok = PutOption(obj_out, i->first, (json::Array)array);
+			}
+			break;
+		}
+	}
+
+	io::Save file(config_file);
+	json::Writer::Write(obj_out, file.Get());
+}
+
+
+bool Options::PutOption(json::Object &obj, const std::string &path, const json::UnknownElement &value) {
+	// Having a '/' denotes it is a leaf.
+	if (path.find('/') == std::string::npos) {
+		json::Object::iterator pos = obj.Find(path);
+
+		// Fail if a key of the same name already exists.
+		if (pos != obj.End())
+			throw OptionErrorDuplicateKey("Key already exists");
+
+		obj.Insert(json::Object::Member(path, value));
+		return true;
+	} else {
+		std::string thispart = path.substr(0, path.find("/"));
+		std::string restpart = path.substr(path.find("/")+1, path.size());
+		json::Object::iterator pos = obj.Find(thispart);
+
+		// New key, make object.
+		if (pos == obj.End())
+			pos = obj.Insert(json::Object::Member(thispart, json::Object()));
+
+		PutOptionVisitor visitor(restpart, value);
+		pos->element.Accept(visitor);
+		return visitor.result;
+	}
+}
+
+} // namespace agi
diff --git a/aegisub/libaegisub/common/option_visit.cpp b/aegisub/libaegisub/common/option_visit.cpp
new file mode 100644
index 000000000..1609a9b7d
--- /dev/null
+++ b/aegisub/libaegisub/common/option_visit.cpp
@@ -0,0 +1,242 @@
+// 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.
+//
+// $Id$
+
+/// @file option_visit.cpp
+/// @brief Cajun JSON visitor to load config values.
+/// @see option_visit.h
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <math.h>
+
+#include <wx/colour.h>
+#include <wx/wxcrtvararg.h>
+#endif
+
+#include <libaegisub/colour.h>
+#include "option_visit.h"
+
+namespace agi {
+
+ConfigVisitor::ConfigVisitor(OptionValueMap &val, const std::string &member_name): values(val) {
+		// Corropsonding code is in AddOptionValue()
+		name = member_name +  "/";
+}
+
+ConfigVisitor::~ConfigVisitor() {
+}
+
+
+void ConfigVisitor::Visit(const json::Object& object) {
+
+	json::Object::const_iterator index(object.Begin()), index_end(object.End());
+
+	for (; index != index_end; ++index) {
+		const json::Object::Member& member = *index;
+		const std::string &member_name = member.name;
+		const json::UnknownElement& element = member.element;
+
+		ConfigVisitor config_visitor(values, name + member_name);
+
+		element.Accept(config_visitor);
+	}
+}
+
+
+void ConfigVisitor::Visit(const json::Array& array) {
+	int init = 0;
+
+	OptionValueList *array_list;
+
+	json::Array::const_iterator index(array.Begin()), indexEnd(array.End());
+
+	for (; index != indexEnd; ++index) {
+
+		const json::Object& index_array = *index;
+
+		json::Object::const_iterator index_object(index_array.Begin()), index_objectEnd(index_array.End());
+
+		for (; index_object != index_objectEnd; ++index_object) {
+			const json::Object::Member& member = *index_object;
+			const std::string& member_name = member.name;
+
+
+			// This can only happen once since a list must always be of the same
+			// type, if we try inserting another type into it we want it to fail.
+			if (!init) {
+				if (member_name == "string") {
+					array_list = new OptionValueListString(name);
+				} else if (member_name == "int") {
+					array_list = new OptionValueListInt(name);
+				} else if (member_name == "double") {
+					array_list = new OptionValueListDouble(name);
+				} else if (member_name == "bool") {
+					array_list = new OptionValueListBool(name);
+				} else if (member_name == "colour") {
+					array_list = new OptionValueListColour(name);
+				} else {
+					throw OptionJsonValueArray("Array type not handled");
+				}
+				init = 1;
+			}
+
+			try {
+				if (member_name == "string") {
+					std::string val = (json::String)member.element;
+					array_list->InsertString(val);
+
+				} else if (member_name == "int") {
+					int64_t val = (int64_t)(json::Number)member.element;
+					array_list->InsertInt(val);
+
+				} else if (member_name == "double") {
+					double val = (json::Number)member.element;
+					array_list->InsertDouble(val);
+
+				} else if (member_name == "bool") {
+					bool val = (json::Boolean)member.element;
+					array_list->InsertBool(val);
+
+				} else if (member_name == "colour") {
+					std::string val = (json::String)member.element;
+					Colour col(val);
+					array_list->InsertColour(col);
+				}
+
+				AddOptionValue(array_list);
+
+			} catch (agi::Exception&) {
+				throw OptionJsonValueArray("Attempt to insert value into array of wrong type");
+			}
+
+		} // for index_object
+
+	} // for index
+}
+
+
+void ConfigVisitor::Visit(const json::Number& number) {
+	double val = number.Value();
+
+	if (int64_t(val) == ceil(val)) {
+		OptionValue *opt = new OptionValueInt(name, int64_t(val));
+		AddOptionValue(opt);
+	} else {
+		OptionValue *opt = new OptionValueDouble(name, val);
+		AddOptionValue(opt);
+	}
+
+}
+
+
+void ConfigVisitor::Visit(const json::String& string) {
+	OptionValue *opt;
+	if (string.Value().find("rgb(") == 0) {
+		opt = new OptionValueColour(name, string.Value());
+	} else {
+		opt = new OptionValueString(name, string.Value());
+	}
+	AddOptionValue(opt);
+}
+
+
+void ConfigVisitor::Visit(const json::Boolean& boolean) {
+	OptionValue *opt = new OptionValueBool(name, boolean.Value());
+	AddOptionValue(opt);
+}
+
+
+void ConfigVisitor::Visit(const json::Null& null) {
+	throw OptionJsonValueNull("Attempt to read null value");
+}
+
+
+void ConfigVisitor::AddOptionValue(OptionValue* opt) {
+	// Corrosponding code is in the constuctor.
+	std::string stripped = name.substr(1, name.rfind("/")-1);
+	OptionValue *opt_cur;
+
+	OptionValueMap::iterator index;
+
+	if ((index = values.find(stripped)) != values.end()) {
+		opt_cur = index->second;
+	} else {
+		values.insert(OptionValuePair(stripped, opt));
+		return;
+	}
+
+	int type = opt_cur->GetType();
+	switch (type) {
+		case OptionValue::Type_String:
+			opt_cur->SetString(opt->GetString());
+			break;
+
+		case OptionValue::Type_Int:
+			opt_cur->SetInt(opt->GetInt());
+			break;
+
+		case OptionValue::Type_Double:
+			opt_cur->SetDouble(opt->GetDouble());
+			break;
+
+		case OptionValue::Type_Colour:
+			opt_cur->SetColour(opt->GetColour());
+			break;
+
+		case OptionValue::Type_Bool:
+			opt_cur->SetBool(opt->GetBool());
+			break;
+
+		case OptionValue::Type_List_String: {
+			std::vector<std::string> array;
+ 			opt->GetListString(array);
+			opt_cur->SetListString(array);
+			break;
+		}
+
+		case OptionValue::Type_List_Int: {
+			std::vector<int64_t> array;
+ 			opt->GetListInt(array);
+			opt_cur->SetListInt(array);
+			break;
+		}
+
+		case OptionValue::Type_List_Double: {
+			std::vector<double> array;
+ 			opt->GetListDouble(array);
+			opt_cur->SetListDouble(array);
+			break;
+		}
+
+		case OptionValue::Type_List_Colour: {
+			std::vector<Colour> array;
+ 			opt->GetListColour(array);
+			opt_cur->SetListColour(array);
+			break;
+		}
+
+
+		case OptionValue::Type_List_Bool: {
+			std::vector<bool> array;
+ 			opt->GetListBool(array);
+			opt_cur->SetListBool(array);
+			break;
+		}
+	}
+
+}
+
+} // namespace agi
diff --git a/aegisub/libaegisub/common/option_visit.h b/aegisub/libaegisub/common/option_visit.h
new file mode 100644
index 000000000..537c2427b
--- /dev/null
+++ b/aegisub/libaegisub/common/option_visit.h
@@ -0,0 +1,83 @@
+// 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.
+//
+// $Id$
+
+/// @file option_visit.h
+/// @brief Cajun JSON visitor to load config values.
+/// @see option_visit.cpp
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include "libaegisub/cajun/elements.h"
+#include "libaegisub/cajun/visitor.h"
+#endif
+
+#pragma once
+
+#include "libaegisub/option.h"
+
+namespace agi {
+
+DEFINE_BASE_EXCEPTION_NOINNER(OptionJsonValueError, Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionJsonValueArray, OptionJsonValueError, "options/value/array")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionJsonValueSingle, OptionJsonValueError, "options/value")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionJsonValueNull, OptionJsonValueError, "options/value")
+
+class ConfigVisitor : public json::ConstVisitor {
+
+	OptionValueMap &values;
+	std::string name;
+	typedef std::pair<std::string, OptionValue*> OptionValuePair;
+
+	void AddOptionValue(OptionValue* opt);
+
+public:
+
+	ConfigVisitor(OptionValueMap &val, const std::string &member_name);
+	~ConfigVisitor();
+
+	void Visit(const json::Array& array);
+	void Visit(const json::Object& object);
+	void Visit(const json::Number& number);
+	void Visit(const json::String& string);
+	void Visit(const json::Boolean& boolean);
+	void Visit(const json::Null& null);
+};
+
+
+class PutOptionVisitor : public json::Visitor {
+public:
+	bool result;
+	const std::string &path;
+	const json::UnknownElement &value;
+
+	PutOptionVisitor(const std::string &path, const json::UnknownElement &value)
+	: result(false), path(path), value(value)
+	{}
+
+	// all of these are a fail
+	virtual void Visit(json::Array& array) { }
+	virtual void Visit(json::Number& number) { }
+	virtual void Visit(json::String& string) { }
+	virtual void Visit(json::Boolean& boolean) { }
+	virtual void Visit(json::Null& null) { }
+
+	// this one is the win
+	virtual void Visit(json::Object& object) {
+		result = Options::PutOption(object, path, value);
+	}
+};
+
+} // namespace agi
diff --git a/aegisub/libaegisub/common/validator.cpp b/aegisub/libaegisub/common/validator.cpp
new file mode 100644
index 000000000..24e8568e7
--- /dev/null
+++ b/aegisub/libaegisub/common/validator.cpp
@@ -0,0 +1,73 @@
+// 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.
+//
+// $Id$
+
+/// @file validator.cpp
+/// @brief Input validation.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#endif
+
+#include <libaegisub/validator.h>
+
+namespace agi {
+
+bool ValidAny::CheckType(std::string &value) {
+	return true;
+}
+
+bool ValidAny::Check(std::string &value) {
+	return true;
+}
+
+
+bool ValidString::CheckType(std::string &value) {
+	return true;
+}
+
+bool ValidString::Check(std::string &value) {
+	return CheckType(value);
+}
+
+
+bool ValidInt::CheckType(std::string &value) {
+	return true;
+}
+
+bool ValidInt::Check(std::string &value) {
+	return CheckType(value);
+}
+
+bool ValidBool::CheckType(std::string &value) {
+	return true;
+}
+
+bool ValidBool::Check(std::string &value) {
+	return CheckType(value);
+}
+
+bool ValidColour::Check(std::string &value) {
+
+	if (ValidString::CheckType(value)) {
+		// check if it's a valid colour
+		return 1;
+	}
+	return 0;
+}
+
+
+} // namespace agi
+
diff --git a/aegisub/libaegisub/include/libaegisub/access.h b/aegisub/libaegisub/include/libaegisub/access.h
new file mode 100644
index 000000000..13f87b57d
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/access.h
@@ -0,0 +1,58 @@
+// 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.
+//
+// $Id$
+
+/// @file access.h
+/// @brief Public interface for access methods.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#endif
+
+#include <libaegisub/exception.h>
+
+namespace agi {
+	namespace acs {
+
+DEFINE_BASE_EXCEPTION_NOINNER(AcsError, Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsFatal, AcsError, "acs/fatal")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsNotFound, AcsError, "acs/notfound")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsNotAFile, AcsError, "acs/file")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsNotADirectory, AcsError, "acs/directory")
+
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsAccess, AcsError, "acs/access")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsRead, AcsAccess, "acs/access/read")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsWrite, AcsAccess, "acs/access/write")
+
+
+enum Type {
+	FileRead,
+	DirRead,
+	FileWrite,
+	DirWrite
+};
+
+
+void Check(const std::string &file, acs::Type);
+
+void CheckFileRead(const std::string &file);
+void CheckDirRead(const std::string &dir);
+
+void CheckFileWrite(const std::string &file);
+void CheckDirWrite(const std::string &dir);
+
+
+	} // namespace axs
+} // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/elements.h b/aegisub/libaegisub/include/libaegisub/cajun/elements.h
new file mode 100644
index 000000000..dfe619943
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/elements.h
@@ -0,0 +1,272 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#pragma once
+
+#include <deque>
+#include <list>
+#include <string>
+#include <stdexcept>
+
+/*  
+
+TODO:
+* better documentation (doxygen?)
+* Unicode support
+* parent element accessors
+
+*/
+
+namespace json
+{
+
+
+/////////////////////////////////////////////////
+// forward declarations (more info further below)
+
+
+class Visitor;
+class ConstVisitor;
+
+template <typename ValueTypeT>
+class TrivialType_T;
+
+typedef TrivialType_T<double> Number;
+typedef TrivialType_T<bool> Boolean;
+typedef TrivialType_T<std::string> String;
+
+class Object;
+class Array;
+class Null;
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// Exception - base class for all JSON-related runtime errors
+
+class Exception : public std::runtime_error
+{
+public:
+   Exception(const std::string& sMessage);
+};
+
+
+
+
+/////////////////////////////////////////////////////////////////////////
+// UnknownElement - provides a typesafe surrogate for any of the JSON-
+//  sanctioned element types. This class allows the Array and Object
+//  class to effectively contain a heterogeneous set of child elements.
+// The cast operators provide convenient implicit downcasting, while
+//  preserving dynamic type safety by throwing an exception during a
+//  a bad cast. 
+// The object & array element index operators (operators [std::string]
+//  and [size_t]) provide convenient, quick access to child elements.
+//  They are a logical extension of the cast operators. These child
+//  element accesses can be chained together, allowing the following
+//  (when document structure is well-known):
+//  String str = objInvoices[1]["Customer"]["Company"];
+
+
+class UnknownElement
+{
+public:
+   UnknownElement();
+   UnknownElement(const UnknownElement& unknown);
+   UnknownElement(const Object& object);
+   UnknownElement(const Array& array);
+   UnknownElement(const Number& number);
+   UnknownElement(const Boolean& boolean);
+   UnknownElement(const String& string);
+   UnknownElement(const Null& null);
+
+   ~UnknownElement();
+
+   UnknownElement& operator = (const UnknownElement& unknown);
+
+   // implicit cast to actual element type. throws on failure
+   operator const Object& () const;
+   operator const Array& () const;
+   operator const Number& () const;
+   operator const Boolean& () const;
+   operator const String& () const;
+   operator const Null& () const;
+
+   // implicit cast to actual element type. *converts* on failure, and always returns success
+   operator Object& ();
+   operator Array& ();
+   operator Number& ();
+   operator Boolean& ();
+   operator String& ();
+   operator Null& ();
+
+   // provides quick access to children when real element type is object
+   UnknownElement& operator[] (const std::string& key);
+   const UnknownElement& operator[] (const std::string& key) const;
+
+   // provides quick access to children when real element type is array
+   UnknownElement& operator[] (size_t index);
+   const UnknownElement& operator[] (size_t index) const;
+
+   // implements visitor pattern
+   void Accept(ConstVisitor& visitor) const;
+   void Accept(Visitor& visitor);
+
+   // tests equality. first checks type, then value if possible
+   bool operator == (const UnknownElement& element) const;
+
+private:
+   class Imp;
+
+   template <typename ElementTypeT>
+   class Imp_T;
+
+   class CastVisitor;
+   class ConstCastVisitor;
+   
+   template <typename ElementTypeT>
+   class CastVisitor_T;
+
+   template <typename ElementTypeT>
+   class ConstCastVisitor_T;
+
+   template <typename ElementTypeT>
+   const ElementTypeT& CastTo() const;
+
+   template <typename ElementTypeT>
+   ElementTypeT& ConvertTo();
+
+   Imp* m_pImp;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////
+// Array - mimics std::deque<UnknownElement>. The array contents are effectively 
+//  heterogeneous thanks to the ElementUnknown class. push_back has been replaced 
+//  by more generic insert functions.
+
+class Array
+{
+public:
+   typedef std::deque<UnknownElement> Elements;
+   typedef Elements::iterator iterator;
+   typedef Elements::const_iterator const_iterator;
+
+   iterator Begin();
+   iterator End();
+   const_iterator Begin() const;
+   const_iterator End() const;
+   
+   iterator Insert(const UnknownElement& element, iterator itWhere);
+   iterator Insert(const UnknownElement& element);
+   iterator Erase(iterator itWhere);
+   void Resize(size_t newSize);
+   void Clear();
+
+   size_t Size() const;
+   bool Empty() const;
+
+   UnknownElement& operator[] (size_t index);
+   const UnknownElement& operator[] (size_t index) const;
+
+   bool operator == (const Array& array) const;
+
+private:
+   Elements m_Elements;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////
+// Object - mimics std::map<std::string, UnknownElement>. The member value 
+//  contents are effectively heterogeneous thanks to the UnknownElement class
+
+class Object
+{
+public:
+   struct Member {
+      Member(const std::string& nameIn = std::string(), const UnknownElement& elementIn = UnknownElement());
+
+      bool operator == (const Member& member) const;
+
+      std::string name;
+      UnknownElement element;
+   };
+
+   typedef std::list<Member> Members; // map faster, but does not preserve order
+   typedef Members::iterator iterator;
+   typedef Members::const_iterator const_iterator;
+
+   bool operator == (const Object& object) const;
+
+   iterator Begin();
+   iterator End();
+   const_iterator Begin() const;
+   const_iterator End() const;
+
+   size_t Size() const;
+   bool Empty() const;
+
+   iterator Find(const std::string& name);
+   const_iterator Find(const std::string& name) const;
+
+   iterator Insert(const Member& member);
+   iterator Insert(const Member& member, iterator itWhere);
+   iterator Erase(iterator itWhere);
+   void Clear();
+
+   UnknownElement& operator [](const std::string& name);
+   const UnknownElement& operator [](const std::string& name) const;
+
+private:
+   class Finder;
+
+   Members m_Members;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////
+// TrivialType_T - class template for encapsulates a simple data type, such as
+//  a string, number, or boolean. Provides implicit const & noncost cast operators
+//  for that type, allowing "DataTypeT type = trivialType;"
+
+
+template <typename DataTypeT>
+class TrivialType_T
+{
+public:
+   TrivialType_T(const DataTypeT& t = DataTypeT());
+
+   operator DataTypeT&();
+   operator const DataTypeT&() const;
+
+   DataTypeT& Value();
+   const DataTypeT& Value() const;
+
+   bool operator == (const TrivialType_T<DataTypeT>& trivial) const;
+
+private:
+   DataTypeT m_tValue;
+};
+
+
+
+/////////////////////////////////////////////////////////////////////////////////
+// Null - doesn't do much of anything but satisfy the JSON spec. It is the default
+//  element type of UnknownElement
+
+class Null
+{
+public:
+   bool operator == (const Null& trivial) const;
+};
+
+
+} // End namespace
+
+
+#include "elements.inl"
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/elements.inl b/aegisub/libaegisub/include/libaegisub/cajun/elements.inl
new file mode 100644
index 000000000..db9df1859
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/elements.inl
@@ -0,0 +1,412 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#include "visitor.h"
+#include "reader.h"
+#include <cassert>
+#include <algorithm>
+#include <map>
+
+/*  
+
+TODO:
+* better documentation
+
+*/
+
+namespace json
+{
+
+
+inline Exception::Exception(const std::string& sMessage) :
+   std::runtime_error(sMessage) {}
+
+
+/////////////////////////
+// UnknownElement members
+
+class UnknownElement::Imp
+{
+public:
+   virtual ~Imp() {}
+   virtual Imp* Clone() const = 0;
+
+   virtual bool Compare(const Imp& imp) const = 0;
+
+   virtual void Accept(ConstVisitor& visitor) const = 0;
+   virtual void Accept(Visitor& visitor) = 0;
+};
+
+
+template <typename ElementTypeT>
+class UnknownElement::Imp_T : public UnknownElement::Imp
+{
+public:
+   Imp_T(const ElementTypeT& element) : m_Element(element) {}
+   virtual Imp* Clone() const { return new Imp_T<ElementTypeT>(*this); }
+
+   virtual void Accept(ConstVisitor& visitor) const { visitor.Visit(m_Element); }
+   virtual void Accept(Visitor& visitor) { visitor.Visit(m_Element); }
+
+   virtual bool Compare(const Imp& imp) const
+   {
+      ConstCastVisitor_T<ElementTypeT> castVisitor;
+      imp.Accept(castVisitor);
+      return castVisitor.m_pElement &&
+             m_Element == *castVisitor.m_pElement;
+   }
+
+private:
+   ElementTypeT m_Element;
+};
+
+
+class UnknownElement::ConstCastVisitor : public ConstVisitor
+{
+   virtual void Visit(const Array& array) {}
+   virtual void Visit(const Object& object) {}
+   virtual void Visit(const Number& number) {}
+   virtual void Visit(const String& string) {}
+   virtual void Visit(const Boolean& boolean) {}
+   virtual void Visit(const Null& null) {}
+};
+
+template <typename ElementTypeT>
+class UnknownElement::ConstCastVisitor_T : public ConstCastVisitor
+{
+public:
+   ConstCastVisitor_T() : m_pElement(0) {}
+   virtual void Visit(const ElementTypeT& element) { m_pElement = &element; } // we don't know what this is, but it overrides one of the base's no-op functions
+   const ElementTypeT* m_pElement;
+};
+
+
+class UnknownElement::CastVisitor : public Visitor
+{
+   virtual void Visit(Array& array) {}
+   virtual void Visit(Object& object) {}
+   virtual void Visit(Number& number) {}
+   virtual void Visit(String& string) {}
+   virtual void Visit(Boolean& boolean) {}
+   virtual void Visit(Null& null) {}
+};
+
+template <typename ElementTypeT>
+class UnknownElement::CastVisitor_T : public CastVisitor
+{
+public:
+   CastVisitor_T() : m_pElement(0) {}
+   virtual void Visit(ElementTypeT& element) { m_pElement = &element; } // we don't know what this is, but it overrides one of the base's no-op functions
+   ElementTypeT* m_pElement;
+};
+
+
+
+
+inline UnknownElement::UnknownElement() :                               m_pImp( new Imp_T<Null>( Null() ) ) {}
+inline UnknownElement::UnknownElement(const UnknownElement& unknown) :  m_pImp( unknown.m_pImp->Clone()) {}
+inline UnknownElement::UnknownElement(const Object& object) :           m_pImp( new Imp_T<Object>(object) ) {}
+inline UnknownElement::UnknownElement(const Array& array) :             m_pImp( new Imp_T<Array>(array) ) {}
+inline UnknownElement::UnknownElement(const Number& number) :           m_pImp( new Imp_T<Number>(number) ) {}
+inline UnknownElement::UnknownElement(const Boolean& boolean) :         m_pImp( new Imp_T<Boolean>(boolean) ) {}
+inline UnknownElement::UnknownElement(const String& string) :           m_pImp( new Imp_T<String>(string) ) {}
+inline UnknownElement::UnknownElement(const Null& null) :               m_pImp( new Imp_T<Null>(null) ) {}
+
+inline UnknownElement::~UnknownElement()   { delete m_pImp; }
+
+inline UnknownElement::operator const Object& () const    { return CastTo<Object>(); }
+inline UnknownElement::operator const Array& () const     { return CastTo<Array>(); }
+inline UnknownElement::operator const Number& () const    { return CastTo<Number>(); }
+inline UnknownElement::operator const Boolean& () const   { return CastTo<Boolean>(); }
+inline UnknownElement::operator const String& () const    { return CastTo<String>(); }
+inline UnknownElement::operator const Null& () const      { return CastTo<Null>(); }
+
+inline UnknownElement::operator Object& ()    { return ConvertTo<Object>(); }
+inline UnknownElement::operator Array& ()     { return ConvertTo<Array>(); }
+inline UnknownElement::operator Number& ()    { return ConvertTo<Number>(); }
+inline UnknownElement::operator Boolean& ()   { return ConvertTo<Boolean>(); }
+inline UnknownElement::operator String& ()    { return ConvertTo<String>(); }
+inline UnknownElement::operator Null& ()      { return ConvertTo<Null>(); }
+
+inline UnknownElement& UnknownElement::operator = (const UnknownElement& unknown) 
+{
+   delete m_pImp;
+   m_pImp = unknown.m_pImp->Clone();
+   return *this;
+}
+
+inline UnknownElement& UnknownElement::operator[] (const std::string& key)
+{
+   // the people want an object. make us one if we aren't already
+   Object& object = ConvertTo<Object>();
+   return object[key];
+}
+
+inline const UnknownElement& UnknownElement::operator[] (const std::string& key) const
+{
+   // throws if we aren't an object
+   const Object& object = CastTo<Object>();
+   return object[key];
+}
+
+inline UnknownElement& UnknownElement::operator[] (size_t index)
+{
+   // the people want an array. make us one if we aren't already
+   Array& array = ConvertTo<Array>();
+   return array[index];
+}
+
+inline const UnknownElement& UnknownElement::operator[] (size_t index) const
+{
+   // throws if we aren't an array
+   const Array& array = CastTo<Array>();
+   return array[index];
+}
+
+
+template <typename ElementTypeT>
+const ElementTypeT& UnknownElement::CastTo() const
+{
+   ConstCastVisitor_T<ElementTypeT> castVisitor;
+   m_pImp->Accept(castVisitor);
+   if (castVisitor.m_pElement == 0)
+      throw Exception("Bad cast");
+   return *castVisitor.m_pElement;
+}
+
+
+
+template <typename ElementTypeT>
+ElementTypeT& UnknownElement::ConvertTo() 
+{
+   CastVisitor_T<ElementTypeT> castVisitor;
+   m_pImp->Accept(castVisitor);
+   if (castVisitor.m_pElement == 0)
+   {
+      // we're not the right type. fix it & try again
+      *this = ElementTypeT();
+      m_pImp->Accept(castVisitor);
+   }
+
+   return *castVisitor.m_pElement;
+}
+
+
+inline void UnknownElement::Accept(ConstVisitor& visitor) const { m_pImp->Accept(visitor); }
+inline void UnknownElement::Accept(Visitor& visitor)            { m_pImp->Accept(visitor); }
+
+
+inline bool UnknownElement::operator == (const UnknownElement& element) const
+{
+   return m_pImp->Compare(*element.m_pImp);
+}
+
+
+
+//////////////////
+// Object members
+
+
+inline Object::Member::Member(const std::string& nameIn, const UnknownElement& elementIn) :
+   name(nameIn), element(elementIn) {}
+
+inline bool Object::Member::operator == (const Member& member) const 
+{
+   return name == member.name &&
+          element == member.element;
+}
+
+class Object::Finder : public std::unary_function<Object::Member, bool>
+{
+public:
+   Finder(const std::string& name) : m_name(name) {}
+   bool operator () (const Object::Member& member) {
+      return member.name == m_name;
+   }
+
+private:
+   std::string m_name;
+};
+
+
+
+inline Object::iterator Object::Begin() { return m_Members.begin(); }
+inline Object::iterator Object::End() { return m_Members.end(); }
+inline Object::const_iterator Object::Begin() const { return m_Members.begin(); }
+inline Object::const_iterator Object::End() const { return m_Members.end(); }
+
+inline size_t Object::Size() const { return m_Members.size(); }
+inline bool Object::Empty() const { return m_Members.empty(); }
+
+inline Object::iterator Object::Find(const std::string& name) 
+{
+   return std::find_if(m_Members.begin(), m_Members.end(), Finder(name));
+}
+
+inline Object::const_iterator Object::Find(const std::string& name) const 
+{
+   return std::find_if(m_Members.begin(), m_Members.end(), Finder(name));
+}
+
+inline Object::iterator Object::Insert(const Member& member)
+{
+   return Insert(member, End());
+}
+
+inline Object::iterator Object::Insert(const Member& member, iterator itWhere)
+{
+   iterator it = Find(member.name);
+   if (it != m_Members.end())
+      throw Exception("Object member already exists: " + member.name);
+
+   it = m_Members.insert(itWhere, member);
+   return it;
+}
+
+inline Object::iterator Object::Erase(iterator itWhere) 
+{
+   return m_Members.erase(itWhere);
+}
+
+inline UnknownElement& Object::operator [](const std::string& name)
+{
+
+   iterator it = Find(name);
+   if (it == m_Members.end())
+   {
+      Member member(name);
+      it = Insert(member, End());
+   }
+   return it->element;      
+}
+
+inline const UnknownElement& Object::operator [](const std::string& name) const 
+{
+   const_iterator it = Find(name);
+   if (it == End())
+      throw Exception("Object member not found: " + name);
+   return it->element;
+}
+
+inline void Object::Clear() 
+{
+   m_Members.clear(); 
+}
+
+inline bool Object::operator == (const Object& object) const 
+{
+   return m_Members == object.m_Members;
+}
+
+
+/////////////////
+// Array members
+
+inline Array::iterator Array::Begin()  { return m_Elements.begin(); }
+inline Array::iterator Array::End()    { return m_Elements.end(); }
+inline Array::const_iterator Array::Begin() const  { return m_Elements.begin(); }
+inline Array::const_iterator Array::End() const    { return m_Elements.end(); }
+
+inline Array::iterator Array::Insert(const UnknownElement& element, iterator itWhere)
+{ 
+   return m_Elements.insert(itWhere, element);
+}
+
+inline Array::iterator Array::Insert(const UnknownElement& element)
+{
+   return Insert(element, End());
+}
+
+inline Array::iterator Array::Erase(iterator itWhere)
+{ 
+   return m_Elements.erase(itWhere);
+}
+
+inline void Array::Resize(size_t newSize)
+{
+   m_Elements.resize(newSize);
+}
+
+inline size_t Array::Size() const  { return m_Elements.size(); }
+inline bool Array::Empty() const   { return m_Elements.empty(); }
+
+inline UnknownElement& Array::operator[] (size_t index)
+{
+   size_t nMinSize = index + 1; // zero indexed
+   if (m_Elements.size() < nMinSize)
+      m_Elements.resize(nMinSize);
+   return m_Elements[index]; 
+}
+
+inline const UnknownElement& Array::operator[] (size_t index) const 
+{
+   if (index >= m_Elements.size())
+      throw Exception("Array out of bounds");
+   return m_Elements[index]; 
+}
+
+inline void Array::Clear() {
+   m_Elements.clear();
+}
+
+inline bool Array::operator == (const Array& array) const
+{
+   return m_Elements == array.m_Elements;
+}
+
+
+////////////////////////
+// TrivialType_T members
+
+template <typename DataTypeT>
+TrivialType_T<DataTypeT>::TrivialType_T(const DataTypeT& t) :
+   m_tValue(t) {}
+
+template <typename DataTypeT>
+TrivialType_T<DataTypeT>::operator DataTypeT&()
+{
+   return Value(); 
+}
+
+template <typename DataTypeT>
+TrivialType_T<DataTypeT>::operator const DataTypeT&() const
+{
+   return Value(); 
+}
+
+template <typename DataTypeT>
+DataTypeT& TrivialType_T<DataTypeT>::Value()
+{
+   return m_tValue; 
+}
+
+template <typename DataTypeT>
+const DataTypeT& TrivialType_T<DataTypeT>::Value() const
+{
+   return m_tValue; 
+}
+
+template <typename DataTypeT>
+bool TrivialType_T<DataTypeT>::operator == (const TrivialType_T<DataTypeT>& trivial) const
+{
+   return m_tValue == trivial.m_tValue;
+}
+
+
+
+//////////////////
+// Null members
+
+inline bool Null::operator == (const Null& trivial) const
+{
+   return true;
+}
+
+
+
+} // End namespace
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/reader.h b/aegisub/libaegisub/include/libaegisub/cajun/reader.h
new file mode 100644
index 000000000..cd02977ba
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/reader.h
@@ -0,0 +1,125 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#pragma once
+
+#include "elements.h"
+#include <iostream>
+#include <vector>
+
+namespace json
+{
+
+class Reader
+{
+public:
+   // this structure will be reported in one of the exceptions defined below
+   struct Location
+   {
+      Location();
+
+      unsigned int m_nLine;       // document line, zero-indexed
+      unsigned int m_nLineOffset; // character offset from beginning of line, zero indexed
+      unsigned int m_nDocOffset;  // character offset from entire document, zero indexed
+   };
+
+   // thrown during the first phase of reading. generally catches low-level problems such
+   //  as errant characters or corrupt/incomplete documents
+   class ScanException : public Exception
+   {
+   public:
+      ScanException(const std::string& sMessage, const Reader::Location& locError) :
+         Exception(sMessage),
+         m_locError(locError) {}
+
+      Reader::Location m_locError;
+   };
+
+   // thrown during the second phase of reading. generally catches higher-level problems such
+   //  as missing commas or brackets
+   class ParseException : public Exception
+   {
+   public:
+      ParseException(const std::string& sMessage, const Reader::Location& locTokenBegin, const Reader::Location& locTokenEnd) :
+         Exception(sMessage),
+         m_locTokenBegin(locTokenBegin),
+         m_locTokenEnd(locTokenEnd) {}
+
+      Reader::Location m_locTokenBegin;
+      Reader::Location m_locTokenEnd;
+   };
+
+
+   // if you know what the document looks like, call one of these...
+   static void Read(Object& object, std::istream& istr);
+   static void Read(Array& array, std::istream& istr);
+   static void Read(String& string, std::istream& istr);
+   static void Read(Number& number, std::istream& istr);
+   static void Read(Boolean& boolean, std::istream& istr);
+   static void Read(Null& null, std::istream& istr);
+
+   // ...otherwise, if you don't know, call this & visit it
+   static void Read(UnknownElement& elementRoot, std::istream& istr);
+
+private:
+   struct Token
+   {
+      enum Type
+      {
+         TOKEN_OBJECT_BEGIN,  //    {
+         TOKEN_OBJECT_END,    //    }
+         TOKEN_ARRAY_BEGIN,   //    [
+         TOKEN_ARRAY_END,     //    ]
+         TOKEN_NEXT_ELEMENT,  //    ,
+         TOKEN_MEMBER_ASSIGN, //    :
+         TOKEN_STRING,        //    "xxx"
+         TOKEN_NUMBER,        //    [+/-]000.000[e[+/-]000]
+         TOKEN_BOOLEAN,       //    true -or- false
+         TOKEN_NULL           //    null
+      };
+
+      Type nType;
+      std::string sValue;
+
+      // for malformed file debugging
+      Reader::Location locBegin;
+      Reader::Location locEnd;
+   };
+
+   class InputStream;
+   class TokenStream;
+   typedef std::vector<Token> Tokens;
+
+   template <typename ElementTypeT>   
+   static void Read_i(ElementTypeT& element, std::istream& istr);
+
+   // scanning istream into token sequence
+   void Scan(Tokens& tokens, InputStream& inputStream);
+
+   void EatWhiteSpace(InputStream& inputStream);
+   void MatchString(std::string& sValue, InputStream& inputStream);
+   void MatchNumber(std::string& sNumber, InputStream& inputStream);
+   void MatchExpectedString(const std::string& sExpected, InputStream& inputStream);
+
+   // parsing token sequence into element structure
+   void Parse(UnknownElement& element, TokenStream& tokenStream);
+   void Parse(Object& object, TokenStream& tokenStream);
+   void Parse(Array& array, TokenStream& tokenStream);
+   void Parse(String& string, TokenStream& tokenStream);
+   void Parse(Number& number, TokenStream& tokenStream);
+   void Parse(Boolean& boolean, TokenStream& tokenStream);
+   void Parse(Null& null, TokenStream& tokenStream);
+
+   const std::string& MatchExpectedToken(Token::Type nExpected, TokenStream& tokenStream);
+};
+
+
+} // End namespace
+
+
+#include "reader.inl"
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/reader.inl b/aegisub/libaegisub/include/libaegisub/cajun/reader.inl
new file mode 100644
index 000000000..8b9e1c770
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/reader.inl
@@ -0,0 +1,519 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#include <cassert>
+#include <set>
+#include <sstream>
+
+/*  
+
+TODO:
+* better documentation
+* unicode character decoding
+
+*/
+
+namespace json
+{
+
+
+   inline std::istream& operator >> (std::istream& istr, UnknownElement& elementRoot) {
+   Reader::Read(elementRoot, istr);
+   return istr;
+}
+
+inline Reader::Location::Location() :
+   m_nLine(0),
+   m_nLineOffset(0),
+   m_nDocOffset(0)
+{}
+
+
+//////////////////////
+// Reader::InputStream
+
+class Reader::InputStream // would be cool if we could inherit from std::istream & override "get"
+{
+public:
+   InputStream(std::istream& iStr) :
+      m_iStr(iStr) {}
+
+   // protect access to the input stream, so we can keeep track of document/line offsets
+   char Get(); // big, define outside
+   char Peek() {
+      assert(m_iStr.eof() == false); // enforce reading of only valid stream data 
+      return m_iStr.peek();
+   }
+
+   bool EOS() {
+      m_iStr.peek(); // apparently eof flag isn't set until a character read is attempted. whatever.
+      return m_iStr.eof();
+   }
+
+   const Location& GetLocation() const { return m_Location; }
+
+private:
+   std::istream& m_iStr;
+   Location m_Location;
+};
+
+
+inline char Reader::InputStream::Get()
+{
+   assert(m_iStr.eof() == false); // enforce reading of only valid stream data 
+   char c = m_iStr.get();
+   
+   ++m_Location.m_nDocOffset;
+   if (c == '\n') {
+      ++m_Location.m_nLine;
+      m_Location.m_nLineOffset = 0;
+   }
+   else {
+      ++m_Location.m_nLineOffset;
+   }
+
+   return c;
+}
+
+
+
+//////////////////////
+// Reader::TokenStream
+
+class Reader::TokenStream
+{
+public:
+   TokenStream(const Tokens& tokens);
+
+   const Token& Peek();
+   const Token& Get();
+
+   bool EOS() const;
+
+private:
+   const Tokens& m_Tokens;
+   Tokens::const_iterator m_itCurrent;
+};
+
+
+inline Reader::TokenStream::TokenStream(const Tokens& tokens) :
+   m_Tokens(tokens),
+   m_itCurrent(tokens.begin())
+{}
+
+inline const Reader::Token& Reader::TokenStream::Peek() {
+   assert(m_itCurrent != m_Tokens.end());
+   return *(m_itCurrent); 
+}
+
+inline const Reader::Token& Reader::TokenStream::Get() {
+   assert(m_itCurrent != m_Tokens.end());
+   return *(m_itCurrent++); 
+}
+
+inline bool Reader::TokenStream::EOS() const {
+   return m_itCurrent == m_Tokens.end(); 
+}
+
+///////////////////
+// Reader (finally)
+
+
+inline void Reader::Read(Object& object, std::istream& istr)                { Read_i(object, istr); }
+inline void Reader::Read(Array& array, std::istream& istr)                  { Read_i(array, istr); }
+inline void Reader::Read(String& string, std::istream& istr)                { Read_i(string, istr); }
+inline void Reader::Read(Number& number, std::istream& istr)                { Read_i(number, istr); }
+inline void Reader::Read(Boolean& boolean, std::istream& istr)              { Read_i(boolean, istr); }
+inline void Reader::Read(Null& null, std::istream& istr)                    { Read_i(null, istr); }
+inline void Reader::Read(UnknownElement& unknown, std::istream& istr)       { Read_i(unknown, istr); }
+
+
+template <typename ElementTypeT>   
+void Reader::Read_i(ElementTypeT& element, std::istream& istr)
+{
+   Reader reader;
+
+   Tokens tokens;
+   InputStream inputStream(istr);
+   reader.Scan(tokens, inputStream);
+
+   TokenStream tokenStream(tokens);
+   reader.Parse(element, tokenStream);
+
+   if (tokenStream.EOS() == false)
+   {
+      const Token& token = tokenStream.Peek();
+      std::string sMessage = "Expected End of token stream; found " + token.sValue;
+      throw ParseException(sMessage, token.locBegin, token.locEnd);
+   }
+}
+
+
+inline void Reader::Scan(Tokens& tokens, InputStream& inputStream)
+{
+   while (EatWhiteSpace(inputStream),              // ignore any leading white space...
+          inputStream.EOS() == false) // ...before checking for EOS
+   {
+      // if all goes well, we'll create a token each pass
+      Token token;
+      token.locBegin = inputStream.GetLocation();
+
+      // gives us null-terminated string
+      std::string sChar;
+      sChar.push_back(inputStream.Peek());
+
+      switch (sChar[0])
+      {
+         case '{':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_OBJECT_BEGIN;
+            break;
+
+         case '}':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_OBJECT_END;
+            break;
+
+         case '[':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_ARRAY_BEGIN;
+            break;
+
+         case ']':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_ARRAY_END;
+            break;
+
+         case ',':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_NEXT_ELEMENT;
+            break;
+
+         case ':':
+            token.sValue = sChar[0];
+            MatchExpectedString(sChar, inputStream);
+            token.nType = Token::TOKEN_MEMBER_ASSIGN;
+            break;
+
+         case '"':
+            MatchString(token.sValue, inputStream);
+            token.nType = Token::TOKEN_STRING;
+            break;
+
+         case '-':
+         case '0':
+         case '1':
+         case '2':
+         case '3':
+         case '4':
+         case '5':
+         case '6':
+         case '7':
+         case '8':
+         case '9':
+            MatchNumber(token.sValue, inputStream);
+            token.nType = Token::TOKEN_NUMBER;
+            break;
+
+         case 't':
+            token.sValue = "true";
+            MatchExpectedString(token.sValue, inputStream);
+            token.nType = Token::TOKEN_BOOLEAN;
+            break;
+
+         case 'f':
+            token.sValue = "false";
+            MatchExpectedString(token.sValue, inputStream);
+            token.nType = Token::TOKEN_BOOLEAN;
+            break;
+
+         case 'n':
+            token.sValue = "null";
+            MatchExpectedString(token.sValue, inputStream);
+            token.nType = Token::TOKEN_NULL;
+            break;
+
+         default: {
+            std::string sErrorMessage = "Unexpected character in stream: " + sChar;
+            throw ScanException(sErrorMessage, inputStream.GetLocation());
+         }
+      }
+
+      token.locEnd = inputStream.GetLocation();
+      tokens.push_back(token);
+   }
+}
+
+
+inline void Reader::EatWhiteSpace(InputStream& inputStream)
+{
+   while (inputStream.EOS() == false && 
+          ::isspace(inputStream.Peek()))
+      inputStream.Get();
+}
+
+inline void Reader::MatchExpectedString(const std::string& sExpected, InputStream& inputStream)
+{
+   std::string::const_iterator it(sExpected.begin()),
+                               itEnd(sExpected.end());
+   for ( ; it != itEnd; ++it) {
+      if (inputStream.EOS() ||      // did we reach the end before finding what we're looking for...
+          inputStream.Get() != *it) // ...or did we find something different?
+      {
+         std::string sMessage = "Expected string: " + sExpected;
+         throw ScanException(sMessage, inputStream.GetLocation());
+      }
+   }
+
+   // all's well if we made it here, return quietly
+}
+
+
+inline void Reader::MatchString(std::string& string, InputStream& inputStream)
+{
+   MatchExpectedString("\"", inputStream);
+   
+   while (inputStream.EOS() == false &&
+          inputStream.Peek() != '"')
+   {
+      char c = inputStream.Get();
+
+      // escape?
+      if (c == '\\' &&
+          inputStream.EOS() == false) // shouldn't have reached the end yet
+      {
+         c = inputStream.Get();
+         switch (c) {
+            case '/':      string.push_back('/');     break;
+            case '"':      string.push_back('"');     break;
+            case '\\':     string.push_back('\\');    break;
+            case 'b':      string.push_back('\b');    break;
+            case 'f':      string.push_back('\f');    break;
+            case 'n':      string.push_back('\n');    break;
+            case 'r':      string.push_back('\r');    break;
+            case 't':      string.push_back('\t');    break;
+            case 'u':      // TODO: what do we do with this?
+            default: {
+               std::string sMessage = "Unrecognized escape sequence found in string: \\" + c;
+               throw ScanException(sMessage, inputStream.GetLocation());
+            }
+         }
+      }
+      else {
+         string.push_back(c);
+      }
+   }
+
+   // eat the last '"' that we just peeked
+   MatchExpectedString("\"", inputStream);
+}
+
+
+inline void Reader::MatchNumber(std::string& sNumber, InputStream& inputStream)
+{
+   const char sNumericChars[] = "0123456789.eE-+";
+   std::set<char> numericChars;
+   numericChars.insert(sNumericChars, sNumericChars + sizeof(sNumericChars));
+
+   while (inputStream.EOS() == false &&
+          numericChars.find(inputStream.Peek()) != numericChars.end())
+   {
+      sNumber.push_back(inputStream.Get());   
+   }
+}
+
+
+inline void Reader::Parse(UnknownElement& element, Reader::TokenStream& tokenStream) 
+{
+   if (tokenStream.EOS()) {
+      std::string sMessage = "Unexpected end of token stream";
+      throw ParseException(sMessage, Location(), Location()); // nowhere to point to
+   }
+
+   const Token& token = tokenStream.Peek();
+   switch (token.nType) {
+      case Token::TOKEN_OBJECT_BEGIN:
+      {
+         // implicit non-const cast will perform conversion for us (if necessary)
+         Object& object = element;
+         Parse(object, tokenStream);
+         break;
+      }
+
+      case Token::TOKEN_ARRAY_BEGIN:
+      {
+         Array& array = element;
+         Parse(array, tokenStream);
+         break;
+      }
+
+      case Token::TOKEN_STRING:
+      {
+         String& string = element;
+         Parse(string, tokenStream);
+         break;
+      }
+
+      case Token::TOKEN_NUMBER:
+      {
+         Number& number = element;
+         Parse(number, tokenStream);
+         break;
+      }
+
+      case Token::TOKEN_BOOLEAN:
+      {
+         Boolean& boolean = element;
+         Parse(boolean, tokenStream);
+         break;
+      }
+
+      case Token::TOKEN_NULL:
+      {
+         Null& null = element;
+         Parse(null, tokenStream);
+         break;
+      }
+
+      default:
+      {
+         std::string sMessage = "Unexpected token: " + token.sValue;
+         throw ParseException(sMessage, token.locBegin, token.locEnd);
+      }
+   }
+}
+
+
+inline void Reader::Parse(Object& object, Reader::TokenStream& tokenStream)
+{
+   MatchExpectedToken(Token::TOKEN_OBJECT_BEGIN, tokenStream);
+
+   bool bContinue = (tokenStream.EOS() == false &&
+                     tokenStream.Peek().nType != Token::TOKEN_OBJECT_END);
+   while (bContinue)
+   {
+      Object::Member member;
+
+      // first the member name. save the token in case we have to throw an exception
+      const Token& tokenName = tokenStream.Peek();
+      member.name = MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
+
+      // ...then the key/value separator...
+      MatchExpectedToken(Token::TOKEN_MEMBER_ASSIGN, tokenStream);
+
+      // ...then the value itself (can be anything).
+      Parse(member.element, tokenStream);
+
+      // try adding it to the object (this could throw)
+      try
+      {
+         object.Insert(member);
+      }
+      catch (Exception&)
+      {
+         // must be a duplicate name
+         std::string sMessage = "Duplicate object member token: " + member.name; 
+         throw ParseException(sMessage, tokenName.locBegin, tokenName.locEnd);
+      }
+
+      bContinue = (tokenStream.EOS() == false &&
+                   tokenStream.Peek().nType == Token::TOKEN_NEXT_ELEMENT);
+      if (bContinue)
+         MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream);
+   }
+
+   MatchExpectedToken(Token::TOKEN_OBJECT_END, tokenStream);
+}
+
+
+inline void Reader::Parse(Array& array, Reader::TokenStream& tokenStream)
+{
+   MatchExpectedToken(Token::TOKEN_ARRAY_BEGIN, tokenStream);
+
+   bool bContinue = (tokenStream.EOS() == false &&
+                     tokenStream.Peek().nType != Token::TOKEN_ARRAY_END);
+   while (bContinue)
+   {
+      // ...what's next? could be anything
+      Array::iterator itElement = array.Insert(UnknownElement());
+      UnknownElement& element = *itElement;
+      Parse(element, tokenStream);
+
+      bContinue = (tokenStream.EOS() == false &&
+                   tokenStream.Peek().nType == Token::TOKEN_NEXT_ELEMENT);
+      if (bContinue)
+         MatchExpectedToken(Token::TOKEN_NEXT_ELEMENT, tokenStream);
+   }
+
+   MatchExpectedToken(Token::TOKEN_ARRAY_END, tokenStream);
+}
+
+
+inline void Reader::Parse(String& string, Reader::TokenStream& tokenStream)
+{
+   string = MatchExpectedToken(Token::TOKEN_STRING, tokenStream);
+}
+
+
+inline void Reader::Parse(Number& number, Reader::TokenStream& tokenStream)
+{
+   const Token& currentToken = tokenStream.Peek(); // might need this later for throwing exception
+   const std::string& sValue = MatchExpectedToken(Token::TOKEN_NUMBER, tokenStream);
+
+   std::istringstream iStr(sValue);
+   double dValue;
+   iStr >> dValue;
+
+   // did we consume all characters in the token?
+   if (iStr.eof() == false)
+   {
+      std::string sMessage = "Unexpected character in NUMBER token: " + iStr.peek();
+      throw ParseException(sMessage, currentToken.locBegin, currentToken.locEnd);
+   }
+
+   number = dValue;
+}
+
+
+inline void Reader::Parse(Boolean& boolean, Reader::TokenStream& tokenStream)
+{
+   const std::string& sValue = MatchExpectedToken(Token::TOKEN_BOOLEAN, tokenStream);
+   boolean = (sValue == "true" ? true : false);
+}
+
+
+inline void Reader::Parse(Null&, Reader::TokenStream& tokenStream)
+{
+   MatchExpectedToken(Token::TOKEN_NULL, tokenStream);
+}
+
+
+inline const std::string& Reader::MatchExpectedToken(Token::Type nExpected, Reader::TokenStream& tokenStream)
+{
+   if (tokenStream.EOS())
+   {
+      std::string sMessage = "Unexpected End of token stream";
+      throw ParseException(sMessage, Location(), Location()); // nowhere to point to
+   }
+
+   const Token& token = tokenStream.Get();
+   if (token.nType != nExpected)
+   {
+      std::string sMessage = "Unexpected token: " + token.sValue;
+      throw ParseException(sMessage, token.locBegin, token.locEnd);
+   }
+
+   return token.sValue;
+}
+
+} // End namespace
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/visitor.h b/aegisub/libaegisub/include/libaegisub/cajun/visitor.h
new file mode 100644
index 000000000..78b7b8367
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/visitor.h
@@ -0,0 +1,44 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#pragma once
+
+#include "elements.h"
+
+namespace json
+{
+
+
+class Visitor
+{
+public:
+   virtual ~Visitor() {}
+
+   virtual void Visit(Array& array) = 0;
+   virtual void Visit(Object& object) = 0;
+   virtual void Visit(Number& number) = 0;
+   virtual void Visit(String& string) = 0;
+   virtual void Visit(Boolean& boolean) = 0;
+   virtual void Visit(Null& null) = 0;
+};
+
+class ConstVisitor
+{
+public:
+   virtual ~ConstVisitor() {}
+
+   virtual void Visit(const Array& array) = 0;
+   virtual void Visit(const Object& object) = 0;
+   virtual void Visit(const Number& number) = 0;
+   virtual void Visit(const String& string) = 0;
+   virtual void Visit(const Boolean& boolean) = 0;
+   virtual void Visit(const Null& null) = 0;
+};
+
+
+} // End namespace
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/writer.h b/aegisub/libaegisub/include/libaegisub/cajun/writer.h
new file mode 100644
index 000000000..e5c83c8ee
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/writer.h
@@ -0,0 +1,57 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#pragma once
+
+#include "elements.h"
+#include "visitor.h"
+
+namespace json
+{
+
+class Writer : private ConstVisitor
+{
+public:
+   static void Write(const Object& object, std::ostream& ostr);
+   static void Write(const Array& array, std::ostream& ostr);
+   static void Write(const String& string, std::ostream& ostr);
+   static void Write(const Number& number, std::ostream& ostr);
+   static void Write(const Boolean& boolean, std::ostream& ostr);
+   static void Write(const Null& null, std::ostream& ostr);
+   static void Write(const UnknownElement& elementRoot, std::ostream& ostr);
+
+private:
+   Writer(std::ostream& ostr);
+
+   template <typename ElementTypeT>
+   static void Write_i(const ElementTypeT& element, std::ostream& ostr);
+
+   void Write_i(const Object& object);
+   void Write_i(const Array& array);
+   void Write_i(const String& string);
+   void Write_i(const Number& number);
+   void Write_i(const Boolean& boolean);
+   void Write_i(const Null& null);
+   void Write_i(const UnknownElement& unknown);
+
+   virtual void Visit(const Array& array);
+   virtual void Visit(const Object& object);
+   virtual void Visit(const Number& number);
+   virtual void Visit(const String& string);
+   virtual void Visit(const Boolean& boolean);
+   virtual void Visit(const Null& null);
+
+   std::ostream& m_ostr;
+   int m_nTabDepth;
+};
+
+
+} // End namespace
+
+
+#include "writer.inl"
diff --git a/aegisub/libaegisub/include/libaegisub/cajun/writer.inl b/aegisub/libaegisub/include/libaegisub/cajun/writer.inl
new file mode 100644
index 000000000..f7b5e2b9e
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/cajun/writer.inl
@@ -0,0 +1,153 @@
+/**********************************************
+
+License: BSD
+Project Webpage: http://cajun-jsonapi.sourceforge.net/
+Author: Terry Caton
+
+***********************************************/
+
+#include "writer.h"
+#include <iostream>
+#include <iomanip>
+
+/*  
+
+TODO:
+* better documentation
+* unicode character encoding
+
+*/
+
+namespace json
+{
+
+
+inline void Writer::Write(const UnknownElement& elementRoot, std::ostream& ostr) { Write_i(elementRoot, ostr); }
+inline void Writer::Write(const Object& object, std::ostream& ostr)              { Write_i(object, ostr); }
+inline void Writer::Write(const Array& array, std::ostream& ostr)                { Write_i(array, ostr); }
+inline void Writer::Write(const Number& number, std::ostream& ostr)              { Write_i(number, ostr); }
+inline void Writer::Write(const String& string, std::ostream& ostr)              { Write_i(string, ostr); }
+inline void Writer::Write(const Boolean& boolean, std::ostream& ostr)            { Write_i(boolean, ostr); }
+inline void Writer::Write(const Null& null, std::ostream& ostr)                  { Write_i(null, ostr); }
+
+
+inline Writer::Writer(std::ostream& ostr) :
+   m_ostr(ostr),
+   m_nTabDepth(0)
+{}
+
+template <typename ElementTypeT>
+void Writer::Write_i(const ElementTypeT& element, std::ostream& ostr)
+{
+   Writer writer(ostr);
+   writer.Write_i(element);
+   ostr.flush(); // all done
+}
+
+inline void Writer::Write_i(const Array& array)
+{
+   if (array.Empty())
+      m_ostr << "[]";
+   else
+   {
+      m_ostr << '[' << std::endl;
+      ++m_nTabDepth;
+
+      Array::const_iterator it(array.Begin()),
+                            itEnd(array.End());
+      while (it != itEnd) {
+         m_ostr << std::string(m_nTabDepth, '\t');
+         
+         Write_i(*it);
+
+         if (++it != itEnd)
+            m_ostr << ',';
+         m_ostr << std::endl;
+      }
+
+      --m_nTabDepth;
+      m_ostr << std::string(m_nTabDepth, '\t') << ']';
+   }
+}
+
+inline void Writer::Write_i(const Object& object)
+{
+   if (object.Empty())
+      m_ostr << "{}";
+   else
+   {
+      m_ostr << '{' << std::endl;
+      ++m_nTabDepth;
+
+      Object::const_iterator it(object.Begin()),
+                             itEnd(object.End());
+      while (it != itEnd) {
+         m_ostr << std::string(m_nTabDepth, '\t') << '"' << it->name << "\" : ";
+         Write_i(it->element); 
+
+         if (++it != itEnd)
+            m_ostr << ',';
+         m_ostr << std::endl;
+      }
+
+      --m_nTabDepth;
+      m_ostr << std::string(m_nTabDepth, '\t') << '}';
+   }
+}
+
+inline void Writer::Write_i(const Number& numberElement)
+{
+   m_ostr << std::setprecision(20) << numberElement.Value();
+}
+
+inline void Writer::Write_i(const Boolean& booleanElement)
+{
+   m_ostr << (booleanElement.Value() ? "true" : "false");
+}
+
+inline void Writer::Write_i(const String& stringElement)
+{
+   m_ostr << '"';
+
+   const std::string& s = stringElement.Value();
+   std::string::const_iterator it(s.begin()),
+                               itEnd(s.end());
+   for (; it != itEnd; ++it)
+   {
+      switch (*it)
+      {
+         case '"':         m_ostr << "\\\"";   break;
+         case '\\':        m_ostr << "\\\\";   break;
+         case '\b':        m_ostr << "\\b";    break;
+         case '\f':        m_ostr << "\\f";    break;
+         case '\n':        m_ostr << "\\n";    break;
+         case '\r':        m_ostr << "\\r";    break;
+         case '\t':        m_ostr << "\\t";    break;
+         //case '\u':        m_ostr << "";    break;  ??
+         default:          m_ostr << *it;       break;
+      }
+   }
+
+   m_ostr << '"';   
+}
+
+inline void Writer::Write_i(const Null& )
+{
+   m_ostr << "null";
+}
+
+inline void Writer::Write_i(const UnknownElement& unknown)
+{
+   unknown.Accept(*this); 
+}
+
+inline void Writer::Visit(const Array& array)       { Write_i(array); }
+inline void Writer::Visit(const Object& object)     { Write_i(object); }
+inline void Writer::Visit(const Number& number)     { Write_i(number); }
+inline void Writer::Visit(const String& string)     { Write_i(string); }
+inline void Writer::Visit(const Boolean& boolean)   { Write_i(boolean); }
+inline void Writer::Visit(const Null& null)         { Write_i(null); }
+
+
+
+} // End namespace
diff --git a/aegisub/libaegisub/include/libaegisub/colour.h b/aegisub/libaegisub/include/libaegisub/colour.h
new file mode 100644
index 000000000..fc35eeb5e
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/colour.h
@@ -0,0 +1,31 @@
+// 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.
+//
+// $Id$
+
+/// @file colour.h
+/// @brief Colourspace class and functions.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <string>
+#endif
+
+// This file is a stub for now.
+
+namespace agi {
+
+typedef std::string Colour;
+
+} // namespace agi
diff --git a/aegisub/src/include/aegisub/exception.h b/aegisub/libaegisub/include/libaegisub/exception.h
similarity index 88%
rename from aegisub/src/include/aegisub/exception.h
rename to aegisub/libaegisub/include/libaegisub/exception.h
index cce6d4f06..0b5514488 100644
--- a/aegisub/src/include/aegisub/exception.h
+++ b/aegisub/libaegisub/include/libaegisub/exception.h
@@ -36,13 +36,10 @@
 
 #pragma once
 
-#ifndef AGI_PRE
-#include <wx/string.h>
-#endif
-
+#include <string>
 
 /// @see aegisub.h
-namespace Aegisub {
+namespace agi {
 
 
 	/// @class Exception
@@ -61,11 +58,11 @@ namespace Aegisub {
 	/// When throwing exceptions, throw temporaries, not heap allocated
 	/// objects. (C++ FAQ Lite 17.6.) I.e. this is correct:
 	/// @code
-	/// throw Aegisub::SomeException(_T("Message for exception"));
+	/// throw Aegisub::SomeException("Message for exception");
 	/// @endcode
 	/// This is wrong:
 	/// @code
-	/// throw new Aegisub::SomeException(_T("Remember this is the wrong way!"));
+	/// throw new Aegisub::SomeException("Remember this is the wrong way!");
 	/// @endcode
 	/// Exceptions must not be allocated on heap, because of the risks of
 	/// leaking memory that way. (C++ FAQ Lite 17.8.)
@@ -96,7 +93,7 @@ namespace Aegisub {
 	class Exception {
 
 		/// The error message
-		wxString message;
+		std::string message;
 
 		/// An inner exception, the cause of this exception
 		Exception *inner;
@@ -109,7 +106,7 @@ namespace Aegisub {
 		///
 		/// Deriving classes should always use this constructor for initialising
 		/// the base class.
-		Exception(const wxString &msg, const Exception *inr = 0)
+		Exception(const std::string &msg, const Exception *inr = 0)
 			: message(msg)
 			, inner(0)
 		{
@@ -136,7 +133,7 @@ namespace Aegisub {
 
 		/// @brief Get the outer exception error message
 		/// @return Error message
-		virtual wxString GetMessage() const { return message; }
+		virtual std::string GetMessage() const { return message; }
 
 		/// @brief Get error messages for chained exceptions
 		/// @return Chained error message
@@ -144,7 +141,7 @@ namespace Aegisub {
 		/// If there is an inner exception, prepend its chained error message to
 		/// our error message, with a CRLF between. Returns our own error message
 		/// alone if there is no inner exception.
-		wxString GetChainedMessage() const { if (inner) return inner->GetChainedMessage() + _T("\r\n") + GetMessage(); else return GetMessage(); }
+		std::string GetChainedMessage() const { if (inner) return inner->GetChainedMessage() + "\r\n" + GetMessage(); else return GetMessage(); }
 		
 		/// @brief Exception class printable name
 		///
@@ -155,16 +152,16 @@ namespace Aegisub {
 		/// name for their sub-tree, further sub-classes add further levels, each
 		/// level is separated by a slash. Characters allowed in the name for a
 		/// level are [a-z0-9_].
-		virtual const wxChar * GetName() const = 0;
+		virtual const char * GetName() const = 0;
 
 
-		/// @brief Convert to wxChar array as the error message
+		/// @brief Convert to char array as the error message
 		/// @return The error message
-		operator const wxChar * () { return GetMessage().c_str(); }
+		operator const char * () { return GetMessage().c_str(); }
 
-		/// @brief Convert to wxString as the error message
+		/// @brief Convert to std::string as the error message
 		/// @return The error message
-		operator wxString () { return GetMessage(); }
+		operator std::string () { return GetMessage(); }
 
 		/// @brief Create a copy of the exception allocated on the heap
 		/// @return A heap-allocated exception object
@@ -180,7 +177,7 @@ namespace Aegisub {
 ///
 /// Intended for use in error messages where it can sometimes be convenient to
 /// indicate the exact position the error occurred at.
-#define AG_WHERE _T(" (at ") _T(__FILE__) _T(":") _T(#__LINE__) _T(")")
+#define AG_WHERE " (at " __FILE__ ":" #__LINE__ ")"
 
 
 
@@ -194,8 +191,8 @@ namespace Aegisub {
 #define DEFINE_SIMPLE_EXCEPTION_NOINNER(classname,baseclass,displayname)             \
 	class classname : public baseclass {                                             \
 	public:                                                                          \
-		classname(const wxString &msg) : baseclass(msg) { }                          \
-		const wxChar * GetName() const { return _T(displayname); }                   \
+		classname(const std::string &msg) : baseclass(msg) { }                          \
+		const char * GetName() const { return displayname; }                   \
 		Exception * Copy() const { return new classname(*this); }                    \
 	};
 
@@ -209,8 +206,8 @@ namespace Aegisub {
 #define DEFINE_SIMPLE_EXCEPTION(classname,baseclass,displayname)                     \
 	class classname : public baseclass {                                             \
 	public:                                                                          \
-		classname(const wxString &msg, Exception *inner) : baseclass(msg, inner) { } \
-		const wxChar * GetName() const { return _T(displayname); }                   \
+		classname(const std::string &msg, Exception *inner) : baseclass(msg, inner) { } \
+		const char * GetName() const { return displayname; }                   \
 		Exception * Copy() const { return new classname(*this); }                    \
 	};
 
@@ -224,7 +221,7 @@ namespace Aegisub {
 #define DEFINE_BASE_EXCEPTION_NOINNER(classname,baseclass)                           \
 	class classname : public baseclass {                                             \
 	public:                                                                          \
-		classname(const wxString &msg) : baseclass(msg) { }                          \
+		classname(const std::string &msg) : baseclass(msg) { }                          \
 	};
 
 /// @brief Macro for declaring non-instantiable exception base classes with inner
@@ -238,7 +235,7 @@ namespace Aegisub {
 #define DEFINE_BASE_EXCEPTION(classname,baseclass)                                   \
 	class classname : public baseclass {                                             \
 	public:                                                                          \
-		classname(const wxString &msg, Exception *inner) : baseclass(msg, inner) { } \
+		classname(const std::string &msg, Exception *inner) : baseclass(msg, inner) { } \
 	};
 
 
@@ -291,10 +288,10 @@ namespace Aegisub {
 
 		/// @brief Constructor, automatically builds the error message
 		/// @param filename Name of the file that could not be found
-		FileNotFoundError(const wxString &filename) : FileNotAccessibleError(wxString(_T("File not found: ")) + filename) { }
+		FileNotFoundError(const std::string &filename) : FileNotAccessibleError(std::string("File not found: ") + filename) { }
 
 		// Not documented, see  Aegisub::Exception class
-		const wxChar * GetName() const { return _T("filesystem/not_accessible/not_found"); }
+		const char * GetName() const { return "filesystem/not_accessible/not_found"; }
 
 		// Not documented, see  Aegisub::Exception class
 		Exception * Copy() const { return new FileNotFoundError(*this); }                    \
diff --git a/aegisub/libaegisub/include/libaegisub/io.h b/aegisub/libaegisub/include/libaegisub/io.h
new file mode 100644
index 000000000..672adb764
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/io.h
@@ -0,0 +1,57 @@
+// 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.
+//
+// $Id$
+
+/// @file io.h
+/// @brief Public interface for IO methods.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#endif
+
+#include <libaegisub/exception.h>
+
+namespace agi {
+	namespace io {
+
+DEFINE_BASE_EXCEPTION_NOINNER(IOError, Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IOFatal, IOError, "io/fatal")
+
+/*
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IOAccess, IOError, "io/access")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IONotFound, IOError, "io/notfound")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IONotAFile, IOError, "io/file")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IONotADirectory, IOError, "io/directory")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IOAccessRead, IOError, "io/read")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(IOAccessWrite, IOError, "io/write")
+*/
+
+std::ifstream* Open(const std::string &file);
+
+class Save {
+	std::ofstream *fp;
+	const std::string file_name;
+
+public:
+    Save(const std::string& file);
+    ~Save();
+    std::ofstream& Get();
+};
+
+
+	} // namespace io
+} // namespace agi
+
+
diff --git a/aegisub/libaegisub/include/libaegisub/mru.h b/aegisub/libaegisub/include/libaegisub/mru.h
new file mode 100644
index 000000000..a7f148fcf
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/mru.h
@@ -0,0 +1,104 @@
+// 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.
+//
+// $Id$
+
+/// @file mru.h
+/// @brief Public interface for MRU (Most Recently Used) lists.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <fstream>
+#include <map>
+
+#include "libaegisub/cajun/reader.h"
+#include "libaegisub/cajun/writer.h"
+#include "libaegisub/cajun/elements.h"
+#endif
+
+#include <libaegisub/exception.h>
+
+namespace agi {
+
+DEFINE_BASE_EXCEPTION_NOINNER(MRUError,Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(MRUErrorInvalidKey, MRUError, "mru/invalid")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(MRUErrorIndexOutOfRange, MRUError, "mru/invalid")
+
+/// @class MRUManager
+/// @brief Most Recently Used (MRU) list handling
+///
+/// Add() should be called anytime a file is opened, this will either add the
+/// entry or update it if it already exists.
+///
+/// If a file fails to open, Remove() should be called.
+///
+class MRUManager {
+
+public:
+	/// @brief Map for time->value pairs.
+	/// @param int         Last time loaded
+	/// @param std::string File or value that was last loaded.
+	typedef std::multimap<time_t, std::string, std::greater_equal<time_t> > MRUListMap;
+
+	/// @brief Constructor
+	/// @param config File to load MRU values from
+	MRUManager(const std::string &config, const std::string &default_config);
+
+	/// Destructor
+	~MRUManager();
+
+	/// @brief Add entry to the list.
+	/// @param key List name
+	/// @param entry Entry to add
+	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
+	void Add(const std::string &key, const std::string &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(const std::string &key, const std::string &entry);
+
+	/// @brief Return list
+	/// @param key List name
+	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
+	const MRUListMap* Get(const std::string &key);
+
+	/// @brief Return A single entry in a list.
+	/// @param key List name
+	/// @param entry 0-base position of entry
+	/// @exception MRUErrorInvalidKey thrown when an invalid key is used.
+	const std::string GetEntry(const std::string &key, const int entry);
+
+	/// Write MRU lists to disk.
+	void Flush();
+
+private:
+
+	/// Internal name of the config file, set during object construction.
+	const std::string config_name;
+
+	/// @brief Map for MRUListMap values.
+	/// @param std::string Name
+	/// @param MRUListMap instance.
+	typedef std::map<std::string, MRUListMap*> MRUMap;
+
+	/// Internal MRUMap values.
+	MRUMap mru;
+
+	void Load(const std::string &key, const json::Array& array);
+	inline void Prune(MRUListMap& map);
+};
+
+} // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/option.h b/aegisub/libaegisub/include/libaegisub/option.h
new file mode 100644
index 000000000..6e9e8defb
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/option.h
@@ -0,0 +1,120 @@
+// 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.
+//
+// $Id$
+
+/// @file option.h
+/// @brief Public interface for option values.
+/// @ingroup libaegisub
+
+
+#ifndef LAGI_PRE
+#include <fstream>
+
+#include "libaegisub/cajun/reader.h"
+#include "libaegisub/cajun/writer.h"
+#include "libaegisub/cajun/elements.h"
+#endif
+
+#include <libaegisub/exception.h>
+#include <libaegisub/option_value.h>
+
+namespace agi {
+
+DEFINE_BASE_EXCEPTION_NOINNER(OptionError,Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionErrorNotFound, OptionError, "options/not_found")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionErrorDuplicateKey, OptionError, "options/dump/duplicate")
+
+
+/// This is a cool trick: make a class un-copyable, in this case we always want
+/// to update our *original* map, this will ensure that it is always updated in
+/// every situation.
+class OptionValueMap : public std::map<std::string,OptionValue*> {
+private:
+	OptionValueMap(const OptionValueMap& x);
+	OptionValueMap& operator=(const OptionValueMap& x);
+public:
+	OptionValueMap() {};
+};
+
+
+class Options {
+	friend class PutOptionVisitor;
+
+	/// Root json::Object, used for loading.
+	json::UnknownElement config_root;
+
+	/// Internal OptionValueMap
+	OptionValueMap values;
+
+	/// @brief Create option object.
+	/// @param path Path to store
+	json::Object CreateObject(std::string path);
+
+	/// User config (file that will be written to disk)
+	const std::string config_file;
+
+	/// Default config (for use when config file is/gets corrupted)
+	const std::string config_default;
+
+	/// Whether the user (final) config has been loaded
+	bool config_loaded;
+
+	/// @brief Load a config file into the Options object.
+	/// @param config Config to load.
+	void LoadConfig(std::istream& stream);
+
+
+protected:
+	/// @brief Write an option to file.
+	/// @param[out] obj  Parent object
+	/// @param[in] path  Path option should be stored in.
+	/// @param[in] value Value to write.
+	static bool PutOption(json::Object &obj, const std::string &path, const json::UnknownElement &value);
+
+
+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);
+
+	/// Destructor
+	~Options();
+
+	/// @brief Get an option by name.
+	/// @param name Option to get.
+	/// Get an option value object by name throw an internal exception if the option is not found.
+	OptionValue* Get(const std::string &name);
+
+	/// @brief Next configuration file to load.
+	/// @param[in] src Stream to load from.
+	/// Load next config which will superceed any values from previous configs
+	/// can be called as many times as required, but only after ConfigDefault() and
+	/// before ConfigUser()
+	void ConfigNext(std::istream &stream);
+
+	/// @brief Set user config file.
+	/// Set the user configuration file and read options from it, closes all possible
+	/// config file loading and sets the file to write to.
+	void ConfigUser();
+
+	/// Write the user configuration to disk, throws an exeption if something goes wrong.
+	void Flush();
+
+	/// Print internal option type, name and values.
+	void DumpAll();
+};
+
+} // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/option_value.h b/aegisub/libaegisub/include/libaegisub/option_value.h
new file mode 100644
index 000000000..06aa063b4
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/option_value.h
@@ -0,0 +1,199 @@
+// 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.
+//
+// $Id$
+
+/// @file option_value.h
+/// @brief Container for holding an actual option value.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <fstream>
+#include <stdint.h>
+
+#include "libaegisub/cajun/reader.h"
+#include "libaegisub/cajun/writer.h"
+#include "libaegisub/cajun/elements.h"
+#endif
+
+#include <libaegisub/exception.h>
+#include <libaegisub/colour.h>
+
+namespace agi {
+
+
+DEFINE_BASE_EXCEPTION_NOINNER(OptionValueError, Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionValueErrorNotFound, OptionValueError, "options/not_found")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionValueErrorInvalidType, OptionValueError, "options/invalid_type")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(OptionValueErrorInvalidListType, OptionValueError, "options/array/invalid_type")
+
+
+class OptionValue;
+class ConfigVisitor;
+
+
+/// @class OptionValueListener
+/// Inherit from this class to get the proper type for the notification callback
+/// signature.
+class OptionValueListener {
+public:
+	// (I might have messed up the syntax here. It's supposed to be a pointer
+	// to a member function of an OptionValueListener-derived class.)
+	typedef void (OptionValueListener::*ChangeEvent)(const OptionValue *option);
+};
+
+
+/// @class OptionValue
+/// Holds an actual option.
+class OptionValue {
+	std::set<OptionValueListener*> listeners;
+
+protected:
+	void NotifyChanged();
+
+public:
+	OptionValue() {};
+	virtual ~OptionValue() {};
+
+
+	/// Option type
+	/// No bitsets here.
+	enum OptionType {
+		Type_String = 0,		///< String
+		Type_Int = 1,			///< Integer
+		Type_Double = 2,		///< Double
+		Type_Colour = 3,		///< Colour
+		Type_Bool = 4,			///< Bool
+		Type_List_String = 100,	///< List of Strings
+		Type_List_Int = 101,	///< List of Integers
+		Type_List_Double = 102,	///< List of Doubles
+		Type_List_Colour = 103,	///< List of Colours
+		Type_List_Bool = 104	///< List of Bools
+	};
+
+	virtual OptionType GetType() const = 0;
+	virtual std::string GetName() const = 0;
+	virtual bool IsDefault() const = 0;
+	virtual void Reset() = 0;
+
+	virtual std::string GetString() const { throw OptionValueErrorInvalidType("Attempt to retrieve string from non-string value"); }
+	virtual int64_t GetInt() const { throw OptionValueErrorInvalidType("Attempt to retrieve int from non-int value"); }
+	virtual double GetDouble() const { throw OptionValueErrorInvalidType("Attempt to retrieve double from non-double value"); }
+	virtual Colour GetColour() const { throw OptionValueErrorInvalidType("Attempt to retrieve colour from non-colour value"); }
+	virtual bool GetBool() const { throw OptionValueErrorInvalidType("Attempt to retrieve bool from non-bool value"); }
+
+	virtual void SetString(const std::string val) { throw OptionValueErrorInvalidType("Attempt to set string in a non-string value"); }
+	virtual void SetInt(const int64_t val) { throw OptionValueErrorInvalidType("Attempt to set int in a non-int value"); }
+	virtual void SetDouble(const double val) { throw OptionValueErrorInvalidType("Attempt to set double in a non-double value"); }
+	virtual void SetColour(const Colour val) { throw OptionValueErrorInvalidType("Attempt to set colour in a non-colour value"); }
+	virtual void SetBool(const bool val) { throw OptionValueErrorInvalidType("Attempt to set bool in a non-bool value"); }
+
+	virtual std::string GetDefaultString() const { throw OptionValueErrorInvalidType("Attempt to retrieve string from non-string value"); }
+	virtual int64_t GetDefaultInt() const { throw OptionValueErrorInvalidType("Attempt to retrieve int from non-int value"); }
+	virtual double GetDefaultDouble() const { throw OptionValueErrorInvalidType("Attempt to retrieve double from non-double value"); }
+	virtual Colour GetDefaultColour() const { throw OptionValueErrorInvalidType("Attempt to retrieve colour from non-colour value"); }
+	virtual bool GetDefaultBool() const { throw OptionValueErrorInvalidType("Attempt to retrieve bool from non-bool value"); }
+
+
+	virtual void GetListString(std::vector<std::string> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive string list from non-string list"); }
+	virtual void GetListInt(std::vector<int64_t> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive int list from non-int list"); }
+	virtual void GetListDouble(std::vector<double> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive double list from non-double list"); }
+	virtual void GetListColour(std::vector<Colour> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive colour list from non-colour list"); }
+	virtual void GetListBool(std::vector<bool> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive string bool from non-bool list"); }
+
+	virtual void SetListString(const std::vector<std::string> val) { throw OptionValueErrorInvalidListType("Attempt to set string list in a non-string list"); }
+	virtual void SetListInt(const std::vector<int64_t> val) { throw OptionValueErrorInvalidListType("Attempt to set int list in a non-int list"); }
+	virtual void SetListDouble(const std::vector<double> val) { throw OptionValueErrorInvalidListType("Attempt to set double list in a non-double list"); }
+	virtual void SetListColour(const std::vector<Colour> val) { throw OptionValueErrorInvalidListType("Attempt to set colour list in a non-colour list"); }
+	virtual void SetListBool(const std::vector<bool> val) { throw OptionValueErrorInvalidListType("Attempt to set string in a non-bool list"); }
+
+	virtual void GetDefaultListString(std::vector<std::string> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive string list from non-string list"); }
+	virtual void GetDefaultListInt(std::vector<int64_t> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive int list from non-int list"); }
+	virtual void GetDefaultListDouble(std::vector<double> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive double list from non-double list"); }
+	virtual void GetDefaultListColour(std::vector<Colour> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive colour list from non-colour list"); }
+	virtual void GetDefaultListBool(std::vector<bool> &out) const { throw OptionValueErrorInvalidListType("Attempt to retrive string bool from non-bool list"); }
+
+
+	void Subscribe(OptionValueListener*);
+	void Unsubscribe(OptionValueListener*);
+
+	void Subscribe(OptionValueListener *listener, OptionValueListener::ChangeEvent handler);
+	void Unsubscribe(OptionValueListener *listener, OptionValueListener::ChangeEvent handler);
+
+};
+
+#define CONFIG_OPTIONVALUE(type_name, type)                                                   \
+	class OptionValue##type_name : public OptionValue {                                       \
+		type value;                                                                           \
+		type value_default;                                                                   \
+		std::string name;                                                                     \
+	public:                                                                                   \
+		OptionValue##type_name(std::string member_name, type member_value):                   \
+						  value(member_value), name(member_name) {}                           \
+		type Get##type_name() const { return value; }                                         \
+		void Set##type_name(const type new_val) { value = new_val; }                          \
+		type GetDefault##type_name() const { return value_default; }                          \
+		OptionType GetType() const { return OptionValue::Type_##type_name; }                  \
+		std::string GetName() const { return name; }                                          \
+		void Reset() { value = value_default; }                                               \
+		bool IsDefault() const { return (value == value_default) ? 1 : 0; }                   \
+	};
+
+CONFIG_OPTIONVALUE(String, std::string)
+CONFIG_OPTIONVALUE(Int, int64_t)
+CONFIG_OPTIONVALUE(Double, double)
+CONFIG_OPTIONVALUE(Colour, Colour)
+CONFIG_OPTIONVALUE(Bool, bool)
+
+
+class OptionValueList: public OptionValue {
+	friend class ConfigVisitor;
+
+protected:
+	OptionValueList() {};
+	virtual ~OptionValueList() {};
+	virtual void InsertString(const std::string val) { throw OptionValueErrorInvalidListType("Attempt to insert string in a non-string list"); }
+	virtual void InsertInt(const int64_t val) { throw OptionValueErrorInvalidListType("Attempt to insert int in a non-int list"); }
+	virtual void InsertDouble(const double val) { throw OptionValueErrorInvalidListType("Attempt to insert double in a non-double list"); }
+	virtual void InsertColour(const Colour val) { throw OptionValueErrorInvalidListType("Attempt insert set colour in a from non-colour list"); }
+	virtual void InsertBool(const bool val) { throw OptionValueErrorInvalidListType("Attempt to insert bool in a non-bool list"); }
+};
+
+
+#define CONFIG_OPTIONVALUE_LIST(type_name, type)                                              \
+	class OptionValueList##type_name : public OptionValueList {                               \
+		std::vector<type> array;                                                              \
+		std::vector<type> array_default;                                                      \
+		std::string name;                                                                     \
+		void Insert##type_name(const type val) { array.push_back(val); }                      \
+	public:                                                                                   \
+	virtual std::string GetString() const { return "";}                                       \
+		OptionValueList##type_name(std::string member_name): name(member_name) {}             \
+		void GetList##type_name(std::vector<type> &out) const { out = array; }                \
+		void SetList##type_name(const std::vector<type> val) { array = val;}                  \
+		void GetDefaultList##type_name(std::vector<type> &out) const { out = array_default; } \
+		OptionType GetType() const { return OptionValue::Type_List_##type_name; }             \
+		std::string GetName() const { return name; }                                          \
+		void Reset() { array = array_default; }                                               \
+		bool IsDefault() const { return (array == array_default) ? 1 : 0; }                   \
+	};
+
+
+CONFIG_OPTIONVALUE_LIST(String, std::string)
+CONFIG_OPTIONVALUE_LIST(Int, int64_t)
+CONFIG_OPTIONVALUE_LIST(Double, double)
+CONFIG_OPTIONVALUE_LIST(Colour, Colour)
+CONFIG_OPTIONVALUE_LIST(Bool, bool)
+
+} // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/util.h b/aegisub/libaegisub/include/libaegisub/util.h
new file mode 100644
index 000000000..61ebb0c53
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/util.h
@@ -0,0 +1,36 @@
+// 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.
+//
+// $Id$
+
+/// @file util.h
+/// @brief Public interface for general utilities.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#include <string>
+#include <stdio.h>
+#endif
+
+#include <libaegisub/access.h>
+
+namespace agi {
+	namespace util {
+
+	const std::string DirName(const std::string& path);
+	void Rename(const std::string& from, const std::string& to);
+
+
+	} // namespace util
+} // namespace agi
diff --git a/aegisub/libaegisub/include/libaegisub/validator.h b/aegisub/libaegisub/include/libaegisub/validator.h
new file mode 100644
index 000000000..f8b644049
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/validator.h
@@ -0,0 +1,83 @@
+// 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.
+//
+// $Id$
+
+/// @file validator.h
+/// @brief Input validation.
+/// @ingroup libaegisub
+
+#ifndef LAGI_PRE
+#endif
+
+#include <libaegisub/colour.h>
+
+namespace agi {
+
+
+class Validator {
+public:
+	/// Types supported.
+	enum ValidType {
+		Type_Any = 0,		///< Any (should be used instead of "String"
+                            ///  to accept any value for code clarity.)
+		Type_String = 1,	///< String
+		Type_Int = 2,		///< Integer
+		Type_Bool = 3,		///< Bool
+		Type_Colour = 4		///< Colour
+	};
+
+	/// @brief Check value type.
+	/// @param value Value
+	/// @return true/false
+	///
+	/// If the value type is "int" or "string" it will return true/false based on this alone.
+	/// This should validate against full values or single characters to make it suitable for input boxes.
+	virtual bool CheckType(std::string &value)=0;
+
+	/// @brief Check value including constraints.
+	/// @param value Value
+	/// @return true/false
+	///
+	/// Check value including bounds checking.
+	/// CheckType() should always be the first function called.
+	virtual bool Check(std::string &value)=0;
+
+	/// @brief Return validation type.
+	/// @return Type
+	virtual ValidType GetType()=0;
+};
+
+
+#define VALID_BASE(type_name)                                      \
+	class Valid##type_name : public Validator {                    \
+	public:                                                        \
+		ValidType GetType() { return Type_##type_name; }           \
+		bool CheckType(std::string &value);                        \
+		virtual bool Check(std::string &value);                    \
+	};
+
+VALID_BASE(Any)
+VALID_BASE(String)
+VALID_BASE(Int)
+VALID_BASE(Bool)
+
+
+class ValidColour: public ValidString {
+public:
+	ValidType GetType() { return Type_Colour; }
+	virtual bool Check(std::string &value);
+};
+
+} // namespace agi
diff --git a/aegisub/libaegisub/lagi_pre.h b/aegisub/libaegisub/lagi_pre.h
new file mode 100644
index 000000000..0d5b5f5f2
--- /dev/null
+++ b/aegisub/libaegisub/lagi_pre.h
@@ -0,0 +1,23 @@
+#define LAGI_PRE
+
+#include <errno.h>
+#include <io.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "libaegisub/cajun/elements.h"
+#include "libaegisub/cajun/reader.h"
+#include "libaegisub/cajun/visitor.h"
+#include "libaegisub/cajun/writer.h"
+
+#define LAGI_PRE
diff --git a/aegisub/libaegisub/unix/access.cpp b/aegisub/libaegisub/unix/access.cpp
new file mode 100644
index 000000000..5e3b69783
--- /dev/null
+++ b/aegisub/libaegisub/unix/access.cpp
@@ -0,0 +1,109 @@
+// 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.
+//
+// $Id$
+
+/// @file access.cpp
+/// @brief Unix access methods.
+/// @ingroup libaegisub unix
+
+#ifndef LAGI_PRE
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <iostream>
+#include <fstream>
+#endif
+
+#include "libaegisub/util.h"
+
+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) {
+	struct stat file_stat;
+	int file_status;
+
+	file_status = stat(file.c_str(), &file_stat);
+
+	if (file_status != 0) {
+		switch (errno) {
+			case ENOENT:
+				throw AcsNotFound("File or path not found.");
+			break;
+
+			case EACCES:
+				throw AcsAccess("Access Denied to file, path or path component.");
+			break;
+
+			case EIO:
+				throw AcsFatal("Fatal I/O error occurred.");
+			break;
+		}
+	}
+
+	switch (type) {
+		case FileRead:
+		case FileWrite:
+			if ((file_stat.st_mode & S_IFREG) == 0)
+				throw AcsNotAFile("Not a file.");
+		break;
+
+		case DirRead:
+		case DirWrite:
+			if ((file_stat.st_mode & S_IFDIR) == 0)
+				throw AcsNotADirectory("Not a directory.");
+		break;
+	}
+
+	switch (type) {
+		case DirRead:
+		case FileRead:
+			file_status = access(file.c_str(), R_OK);
+			if (file_status != 0)
+				throw AcsRead("File or directory is not readable.");
+		break;
+
+		case DirWrite:
+		case FileWrite:
+			file_status = access(file.c_str(), W_OK);
+			if (file_status != 0)
+				throw AcsWrite("File or directory is not writable.");
+		break;
+	}
+}
+
+	} // namespace acs
+} // namespace agi
diff --git a/aegisub/libaegisub/unix/io.cpp b/aegisub/libaegisub/unix/io.cpp
new file mode 100644
index 000000000..2cf64356c
--- /dev/null
+++ b/aegisub/libaegisub/unix/io.cpp
@@ -0,0 +1,87 @@
+// 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.
+//
+// $Id$
+
+/// @file io.cpp
+/// @brief Unix IO methods.
+/// @ingroup libaegisub unix
+
+#ifndef LAGI_PRE
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <iostream>
+#include <fstream>
+#endif
+
+#include "libaegisub/io.h"
+#include "libaegisub/util.h"
+
+
+namespace agi {
+	namespace io {
+
+std::ifstream* Open(const std::string &file) {
+	acs::CheckFileRead(file);
+
+	std::ifstream *stream = new std::ifstream(file.c_str());
+
+	if (stream->fail())
+		throw IOFatal("Unknown fatal error as occurred");
+
+	return stream;
+}
+
+
+Save::Save(const std::string& file): file_name(file) {
+
+	const std::string pwd = util::DirName(file);
+
+	acs::CheckDirWrite(pwd.c_str());
+
+	try {
+		acs::CheckFileWrite(file);
+	} catch (acs::AcsNotFound& e) {
+		// 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 fp_touch(file.c_str());
+		fp_touch.close();
+	}
+
+	/// @todo This is a temp hack, proper implementation needs to come after
+	///       Windows support is added.  The code in the destructor needs fixing
+	///       as well.
+	const std::string tmp = file + "_tmp";
+
+	// This will open to file.XXXX. (tempfile)
+	fp = new std::ofstream(tmp.c_str());
+}
+
+Save::~Save() {
+
+	const std::string tmp(file_name + "_tmp");
+	util::Rename(tmp, file_name);
+	delete fp;
+	fp = 0;	// to avoid any silly errors.
+}
+
+std::ofstream& Save::Get() {
+	return *fp;
+}
+
+
+	} // namespace io
+} // namespace agi
diff --git a/aegisub/libaegisub/unix/util.cpp b/aegisub/libaegisub/unix/util.cpp
new file mode 100644
index 000000000..565f9aad3
--- /dev/null
+++ b/aegisub/libaegisub/unix/util.cpp
@@ -0,0 +1,59 @@
+// 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.
+//
+// $Id$
+
+/// @file util.cpp
+/// @brief Unix utility methods.
+/// @ingroup libaegisub unix
+
+#ifndef LAGI_PRE
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <string>
+#include <fstream>
+#endif
+
+#include <string.h>
+#include "libaegisub/util.h"
+
+namespace agi {
+	namespace util {
+
+
+const std::string DirName(const std::string& path) {
+    if (path.find('/') == std::string::npos) {
+		const std::string cwd(".");
+		return cwd;
+	}
+
+	const std::string stripped = path.substr(0, path.rfind("/")+1);
+	return stripped;
+}
+
+void Rename(const std::string& from, const std::string& to) {
+	acs::CheckFileWrite(from);
+
+	try {
+		acs::CheckFileWrite(to);
+	} catch (acs::AcsNotFound& e) {
+		acs::CheckDirWrite(DirName(to));
+	}
+
+	rename(from.c_str(), to.c_str());
+}
+
+	} // namespace io
+} // namespace agi
diff --git a/aegisub/libaegisub/windows/access.cpp b/aegisub/libaegisub/windows/access.cpp
new file mode 100644
index 000000000..4264aefd6
--- /dev/null
+++ b/aegisub/libaegisub/windows/access.cpp
@@ -0,0 +1,118 @@
+// 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.
+//
+// $Id$
+
+/// @file access.cpp
+/// @brief Windows access methods.
+/// @ingroup libaegisub windows
+
+#ifndef LAGI_PRE
+#include <sys/stat.h>
+#include <io.h>
+#include <errno.h>
+
+#include <iostream>
+#include <fstream>
+#endif
+
+#ifndef R_OK
+#define R_OK 04
+#endif
+#ifndef W_OK
+#define W_OK 02
+#endif
+
+#pragma warning(disable: 4996)
+
+
+#include "libaegisub/util.h"
+
+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) {
+	struct stat file_stat;
+	int file_status;
+
+	file_status = stat(file.c_str(), &file_stat);
+
+	if (file_status != 0) {
+		switch (errno) {
+			case ENOENT:
+				throw AcsNotFound("File or path not found.");
+			break;
+
+			case EACCES:
+				throw AcsAccess("Access Denied to file, path or path component.");
+			break;
+
+			case EIO:
+				throw AcsFatal("Fatal I/O error occurred.");
+			break;
+		}
+	}
+
+	switch (type) {
+		case FileRead:
+		case FileWrite:
+			if ((file_stat.st_mode & S_IFREG) == 0)
+				throw AcsNotAFile("Not a file.");
+		break;
+
+		case DirRead:
+		case DirWrite:
+			if ((file_stat.st_mode & S_IFDIR) == 0)
+				throw AcsNotADirectory("Not a directory.");
+		break;
+	}
+
+	switch (type) {
+		case DirRead:
+		case FileRead:
+			file_status = access(file.c_str(), R_OK);
+			if (file_status != 0)
+				throw AcsRead("File or directory is not readable.");
+		break;
+
+		case DirWrite:
+		case FileWrite:
+			file_status = access(file.c_str(), W_OK);
+			if (file_status != 0)
+				throw AcsWrite("File or directory is not writable.");
+		break;
+	}
+}
+	}
+}
diff --git a/aegisub/libaegisub/windows/io.cpp b/aegisub/libaegisub/windows/io.cpp
new file mode 100644
index 000000000..8a21a2546
--- /dev/null
+++ b/aegisub/libaegisub/windows/io.cpp
@@ -0,0 +1,89 @@
+// 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.
+//
+// $Id$
+
+/// @file io.cpp
+/// @brief Windows IO methods.
+/// @ingroup libaegisub windows
+
+#ifndef LAGI_PRE
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <iostream>
+#include <fstream>
+#endif
+
+#include "libaegisub/io.h"
+#include "libaegisub/util.h"
+
+
+namespace agi {
+	namespace io {
+
+std::ifstream* Open(const std::string &file) {
+	acs::CheckFileRead(file);
+
+	std::ifstream *stream = new std::ifstream(file.c_str());
+
+	if (stream->fail()) {
+		delete stream;
+		throw IOFatal("Unknown fatal error as occurred");
+	}
+
+	return stream;
+}
+
+
+Save::Save(const std::string& file): file_name(file) {
+
+	const std::string pwd = util::DirName(file);
+
+	acs::CheckDirWrite(pwd.c_str());
+
+	try {
+		acs::CheckFileWrite(file);
+	} catch (acs::AcsNotFound&) {
+		// 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 fp_touch(file.c_str());
+		fp_touch.close();
+	}
+
+	/// @todo This is a temp hack, proper implementation needs to come after
+	///       Windows support is added.  The code in the destructor needs fixing
+	///       as well.
+	const std::string tmp = file + "_tmp";
+
+	// This will open to file.XXXX. (tempfile)
+	fp = new std::ofstream(tmp.c_str());
+}
+
+Save::~Save() {
+
+	const std::string tmp(file_name + "_tmp");
+	util::Rename(tmp, file_name);
+	delete fp;
+	fp = 0;	// to avoid any silly errors.
+}
+
+std::ofstream& Save::Get() {
+	return *fp;
+}
+
+
+	} // namespace io
+} // namespace agi
diff --git a/aegisub/libaegisub/windows/lagi_pre.cpp b/aegisub/libaegisub/windows/lagi_pre.cpp
new file mode 100644
index 000000000..e69de29bb
diff --git a/aegisub/libaegisub/windows/util.cpp b/aegisub/libaegisub/windows/util.cpp
new file mode 100644
index 000000000..4358e7ff6
--- /dev/null
+++ b/aegisub/libaegisub/windows/util.cpp
@@ -0,0 +1,59 @@
+// 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.
+//
+// $Id$
+
+/// @file util.cpp
+/// @brief Windows utility methods.
+/// @ingroup libaegisub windows
+
+#ifndef LAGI_PRE
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <string>
+#include <fstream>
+#endif
+
+#include <string.h>
+#include "libaegisub/util.h"
+
+namespace agi {
+	namespace util {
+
+
+const std::string DirName(const std::string& path) {
+    if (path.find('/') == std::string::npos) {
+		const std::string cwd(".");
+		return cwd;
+	}
+
+	const std::string stripped = path.substr(0, path.rfind("/")+1);
+	return stripped;
+}
+
+void Rename(const std::string& from, const std::string& to) {
+	acs::CheckFileWrite(from);
+
+	try {
+		acs::CheckFileWrite(to);
+	} catch (acs::AcsNotFound&) {
+		acs::CheckDirWrite(DirName(to));
+	}
+
+	rename(from.c_str(), to.c_str());
+}
+
+	} // namespace io
+} // namespace agi
diff --git a/aegisub/src/Makefile.am b/aegisub/src/Makefile.am
index 3e99c0d01..b174e2ce1 100644
--- a/aegisub/src/Makefile.am
+++ b/aegisub/src/Makefile.am
@@ -23,10 +23,10 @@ SUBDIRS = \
 	libresrc \
 	$(libosxutil_subdir)
 
-AM_CXXFLAGS += -DAEGISUB -Iinclude -I../libffms/include @WX_CPPFLAGS@ @OPENMP_CXXFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVCODEC_CFLAGS@ @LIBSWSCALE_CFLAGS@ @LIBAVUTIL_CFLAGS@
+AM_CXXFLAGS += -DAEGISUB -Iinclude -I../libffms/include -I../libaegisub/include @WX_CPPFLAGS@ @OPENMP_CXXFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVCODEC_CFLAGS@ @LIBSWSCALE_CFLAGS@ @LIBAVUTIL_CFLAGS@
 
 bin_PROGRAMS = aegisub-2.2
-aegisub_2_2_LDADD = libresrc/libresrc.a $(libosxutil_lib)
+aegisub_2_2_LDADD = libresrc/libresrc.a $(libosxutil_lib) -L../libaegisub -laegisub-2.2
 aegisub_2_2_CPPFLAGS = @FREETYPE_CFLAGS@
 aegisub_2_2_LDFLAGS = @DEBUG_FLAGS@ @PROFILE_FLAGS@ @GL_LIBS@ @PTHREAD_LIBS@ @WX_LIBS@ @ICONV_LDFLAGS@ $(libosxutil_ldflags) @CCMALLOC_LDFLAGS@ @EFENCE_LDFLAGS@
 LIBS += @FREETYPE_LIBS@ @FONTCONFIG_LIBS@ @CCMALLOC_LIBS@
@@ -222,6 +222,7 @@ aegisub_2_2_SOURCES = \
 	charset_conv.cpp \
 	colorspace.cpp \
 	colour_button.cpp \
+	compat.cpp \
 	dialog_about.cpp \
 	dialog_attachments.cpp \
 	dialog_automation.cpp \
@@ -232,7 +233,6 @@ aegisub_2_2_SOURCES = \
 	dialog_fonts_collector.cpp \
 	dialog_jumpto.cpp \
 	dialog_kara_timing_copy.cpp \
-	dialog_options.cpp \
 	dialog_paste_over.cpp \
 	dialog_progress.cpp \
 	dialog_properties.cpp \
@@ -274,6 +274,7 @@ aegisub_2_2_SOURCES = \
 	mythes.cxx \
 	options.cpp \
 	plugin_manager.cpp \
+	preferences.cpp \
 	scintilla_text_ctrl.cpp \
 	spellchecker.cpp \
 	spline.cpp \
diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h
index cd97a4757..2e2557e1f 100644
--- a/aegisub/src/agi_pre.h
+++ b/aegisub/src/agi_pre.h
@@ -243,10 +243,6 @@
 #undef _CRT_SECURE_NO_WARNINGS
 #endif
 
-///////////////////
-// Aegisub headers
-#include "include/aegisub/exception.h"
-
 #endif // C++
 
 #endif // AGI_PRE_H
diff --git a/aegisub/src/ass_entry.h b/aegisub/src/ass_entry.h
index f2d65a420..4f511c049 100644
--- a/aegisub/src/ass_entry.h
+++ b/aegisub/src/ass_entry.h
@@ -42,10 +42,9 @@
 // Headers
 #ifndef AGI_PRE
 #include <wx/string.h>
-
-#include "include/aegisub/exception.h"
 #endif
 
+#include <libaegisub/exception.h>
 
 //////////////
 // Prototypes
@@ -81,18 +80,18 @@ namespace Aegisub {
 	/// @brief DOCME
 	///
 	/// DOCME
-	class InvalidMarginIdError : public InternalError {
+	class InvalidMarginIdError : public agi::InternalError {
 	public:
 
 		/// @brief DOCME
 		/// @return 
 		///
-		InvalidMarginIdError() : InternalError(_T("Invalid margin id"), 0) { }
+		InvalidMarginIdError() : InternalError("Invalid margin id", 0) { }
 
 		/// @brief DOCME
 		/// @return 
 		///
-		const wxChar *GetName() { return _T("internal_error/invalid_margin_id"); }
+		const char *GetName() { return "internal_error/invalid_margin_id"; }
 	};
 };
 
diff --git a/aegisub/src/ass_file.cpp b/aegisub/src/ass_file.cpp
index 8be39161f..94c98228e 100644
--- a/aegisub/src/ass_file.cpp
+++ b/aegisub/src/ass_file.cpp
@@ -50,6 +50,8 @@
 #include "ass_file.h"
 #include "ass_override.h"
 #include "ass_style.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "subtitle_format.h"
 #include "text_file_reader.h"
@@ -794,7 +796,7 @@ AssStyle *AssFile::GetStyle(wxString name) {
 /// @brief Adds file name to list of recent 
 /// @param file 
 void AssFile::AddToRecent(wxString file) {
-	Options.AddToRecentList(file,_T("Recent sub"));
+	AegisubApp::Get()->mru->Add("Subtitle", STD_STR(file));
 }
 
 /// @brief List of supported wildcards 
@@ -861,7 +863,7 @@ void AssFile::StackPush(wxString desc) {
 	for (std::list<AssFile*>::iterator cur=UndoStack.begin();cur!=UndoStack.end();cur++) {
 		n++;
 	}
-	int depth = Options.AsInt(_T("Undo levels"));
+	int depth = OPT_GET("Limits/Undo Levels")->GetInt();
 	while (n > depth) {
 		delete UndoStack.front();
 		UndoStack.pop_front();
diff --git a/aegisub/src/ass_file.h b/aegisub/src/ass_file.h
index 30d585de8..49f39a95d 100644
--- a/aegisub/src/ass_file.h
+++ b/aegisub/src/ass_file.h
@@ -113,7 +113,7 @@ public:
 	void Save(wxString file,bool setfilename=false,bool addToRecent=true,const wxString encoding=_T(""));	// Save to a file. Pass true to second argument if this isn't a copy
 	void SaveMemory(std::vector<char> &dst,const wxString encoding=_T(""));	// Save to a memory string
 	void Export(wxString file);							// Saves exported copy, with effects applied
-	void AddToRecent(wxString file);					// Adds file name to list of recently opened files
+	void AddToRecent(wxString file);                    // Adds file name to list of recently opened files
 	bool CanSave();										// Returns true if the file can be saved in its current format
 	static wxString GetWildcardList(int mode);			// Returns the list of wildcards supported (0 = open, 1 = save, 2 = export)
 
diff --git a/aegisub/src/audio_box.cpp b/aegisub/src/audio_box.cpp
index 93c6887fe..3cea98e7f 100644
--- a/aegisub/src/audio_box.cpp
+++ b/aegisub/src/audio_box.cpp
@@ -54,6 +54,7 @@
 #include "frame_main.h"
 #include "hotkeys.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "toggle_bitmap.h"
 #include "tooltip_manager.h"
@@ -97,7 +98,7 @@ wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISE
 	VolumeBar = new wxSlider(this,Audio_Volume,50,0,100,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE);
 	VolumeBar->PushEventHandler(new FocusEvent());
 	VolumeBar->SetToolTip(_("Audio Volume"));
-	bool link = Options.AsBool(_T("Audio Link"));
+	bool link = OPT_GET("Audio/Link")->GetBool();
 	if (link) {
 		VolumeBar->SetValue(VerticalZoom->GetValue());
 		VolumeBar->Enable(false);
@@ -177,23 +178,23 @@ wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISE
 
 	AutoCommit = new ToggleBitmap(this,Audio_Check_AutoCommit,GETIMAGE(toggle_audio_autocommit_24),wxSize(30,-1));
 	AutoCommit->SetToolTip(_("Automatically commit all changes"));
-	AutoCommit->SetValue(Options.AsBool(_T("Audio Autocommit")));
+	AutoCommit->SetValue(OPT_GET("Audio/Auto/Commit")->GetBool());
 	ButtonSizer->Add(AutoCommit,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
 	NextCommit = new ToggleBitmap(this,Audio_Check_NextCommit,GETIMAGE(toggle_audio_nextcommit_24),wxSize(30,-1));
 	NextCommit->SetToolTip(_("Auto goes to next line on commit"));
-	NextCommit->SetValue(Options.AsBool(_T("Audio Next Line on Commit")));
+	NextCommit->SetValue(OPT_GET("Audio/Next Line on Commit")->GetBool());
 	ButtonSizer->Add(NextCommit,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
 	AutoScroll = new ToggleBitmap(this,Audio_Check_AutoGoto,GETIMAGE(toggle_audio_autoscroll_24),wxSize(30,-1));
 	AutoScroll->SetToolTip(_("Auto scrolls audio display to selected line"));
-	AutoScroll->SetValue(Options.AsBool(_T("Audio Autoscroll")));
+	AutoScroll->SetValue(OPT_GET("Audio/Auto/Scroll")->GetBool());
 	ButtonSizer->Add(AutoScroll,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
 	SpectrumMode = new ToggleBitmap(this,Audio_Check_Spectrum,GETIMAGE(toggle_audio_spectrum_24),wxSize(30,-1));
 	SpectrumMode->SetToolTip(_("Spectrum analyzer mode"));
-	SpectrumMode->SetValue(Options.AsBool(_T("Audio Spectrum")));
+	SpectrumMode->SetValue(OPT_GET("Audio/Spectrum")->GetBool());
 	ButtonSizer->Add(SpectrumMode,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
 	MedusaMode = new ToggleBitmap(this,Audio_Check_Medusa,GETIMAGE(toggle_audio_medusa_24),wxSize(30,-1));
 	MedusaMode->SetToolTip(_("Enable Medusa-Style Timing Shortcuts"));
-	MedusaMode->SetValue(Options.AsBool(_T("Audio Medusa Timing Hotkeys")));
+	MedusaMode->SetValue(OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool());
 	ButtonSizer->Add(MedusaMode,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
 	ButtonSizer->AddStretchSpacer(1);
 
@@ -383,8 +384,7 @@ void AudioBox::OnVerticalLink(wxCommandEvent &event) {
 	}
 	VolumeBar->Enable(!VerticalLink->GetValue());
 
-	Options.SetBool(_T("Audio Link"),VerticalLink->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Link")->SetBool(VerticalLink->GetValue());
 }
 
 
@@ -419,8 +419,7 @@ void AudioBox::OnSash(wxSashEvent& event) {
 	Sash->GetParent()->Layout();
 
 	// Store new size
-	Options.SetInt(_T("Audio Display Height"),h);
-	Options.Save();
+	OPT_SET("Audio/Display Height")->SetInt(h);
 
 	// Fix layout
 	frameMain->Freeze();
@@ -683,8 +682,7 @@ void AudioBox::OnGoto(wxCommandEvent &event) {
 ///
 void AudioBox::OnAutoGoto(wxCommandEvent &event) {
 	audioDisplay->SetFocus();
-	Options.SetBool(_T("Audio Autoscroll"),AutoScroll->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Auto/Scroll")->SetBool(AutoScroll->GetValue());
 }
 
 
@@ -694,8 +692,7 @@ void AudioBox::OnAutoGoto(wxCommandEvent &event) {
 ///
 void AudioBox::OnAutoCommit(wxCommandEvent &event) {
 	audioDisplay->SetFocus();
-	Options.SetBool(_T("Audio Autocommit"),AutoCommit->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Auto/Commit")->SetBool(AutoCommit->GetValue());
 }
 
 
@@ -705,8 +702,7 @@ void AudioBox::OnAutoCommit(wxCommandEvent &event) {
 ///
 void AudioBox::OnNextLineCommit(wxCommandEvent &event) {
 	audioDisplay->SetFocus();
-	Options.SetBool(_T("Audio Next Line on Commit"),NextCommit->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Next Line on Commit")->SetBool(NextCommit->GetValue());
 }
 
 
@@ -716,8 +712,7 @@ void AudioBox::OnNextLineCommit(wxCommandEvent &event) {
 ///
 void AudioBox::OnMedusaMode(wxCommandEvent &event) {
 	audioDisplay->SetFocus();
-	Options.SetBool(_T("Audio Medusa Timing Hotkeys"),MedusaMode->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Medusa Timing Hotkeys")->SetBool(MedusaMode->GetValue());
 	frameMain->SetAccelerators();
 }
 
@@ -727,8 +722,7 @@ void AudioBox::OnMedusaMode(wxCommandEvent &event) {
 /// @param event 
 ///
 void AudioBox::OnSpectrumMode(wxCommandEvent &event) {
-	Options.SetBool(_T("Audio Spectrum"),SpectrumMode->GetValue());
-	Options.Save();
+	OPT_SET("Audio/Spectrum")->SetBool(SpectrumMode->GetValue());
 	audioDisplay->UpdateImage(false);
 	audioDisplay->SetFocus();
 	audioDisplay->Refresh(false);
diff --git a/aegisub/src/audio_display.cpp b/aegisub/src/audio_display.cpp
index e45234bde..46e945b2c 100644
--- a/aegisub/src/audio_display.cpp
+++ b/aegisub/src/audio_display.cpp
@@ -55,8 +55,10 @@
 #endif
 #include "audio_provider_stream.h"
 #include "colorspace.h"
+#include "compat.h"
 #include "fft.h"
 #include "hotkeys.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "subs_edit_box.h"
@@ -79,7 +81,7 @@
 /// @brief Constructor 
 /// @param parent 
 AudioDisplay::AudioDisplay(wxWindow *parent)
-: wxWindow (parent, -1, wxDefaultPosition, wxSize(200,Options.AsInt(_T("Audio Display Height"))), AudioDisplayWindowStyle , _T("Audio Display"))
+: wxWindow (parent, -1, wxDefaultPosition, wxSize(200,OPT_GET("Audio/Display Height")->GetInt()), AudioDisplayWindowStyle , _T("Audio Display"))
 {
 	// Set variables
 	origImage = NULL;
@@ -118,7 +120,7 @@ AudioDisplay::AudioDisplay(wxWindow *parent)
 	// Init
 	UpdateTimer.SetOwner(this,Audio_Update_Timer);
 	GetClientSize(&w,&h);
-	h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	h -= OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 	SetSamplesPercent(50,false);
 
 	// Set cursor
@@ -188,7 +190,7 @@ void AudioDisplay::DoUpdateImage() {
 	bool weak = needImageUpdateWeak;
 
 	// Prepare bitmap
-	int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 	int displayH = h+timelineHeight;
 	if (origImage) {
 		if (origImage->GetWidth() != w || origImage->GetHeight() != displayH) {
@@ -198,9 +200,9 @@ void AudioDisplay::DoUpdateImage() {
 	}
 
 	// Options
-	bool draw_boundary_lines = Options.AsBool(_T("Audio Draw Secondary Lines"));
-	bool draw_selection_background = Options.AsBool(_T("Audio Draw Selection Background"));
-	bool drawKeyframes = Options.AsBool(_T("Audio Draw Keyframes"));
+	bool draw_boundary_lines = OPT_GET("Audio/Display/Draw/Secondary Lines")->GetBool();
+	bool draw_selection_background = OPT_GET("Audio/Display/Draw/Selection Background")->GetBool();
+	bool drawKeyframes = OPT_GET("Audio/Display/Draw/Keyframes")->GetBool();
 
 	// Invalid dimensions
 	if (w == 0 || displayH == 0) return;
@@ -210,7 +212,7 @@ void AudioDisplay::DoUpdateImage() {
 
 	// Is spectrum?
 	bool spectrum = false;
-	if (provider && Options.AsBool(_T("Audio Spectrum"))) {
+	if (provider && OPT_GET("Audio/Spectrum")->GetBool()) {
 		spectrum = true;
 	}
 
@@ -220,7 +222,7 @@ void AudioDisplay::DoUpdateImage() {
 
 	// Black background
 	dc.SetPen(*wxTRANSPARENT_PEN);
-	dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Background"))));
+	dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Background")->GetColour())));
 	dc.DrawRectangle(0,0,w,h);
 
 	// Selection position
@@ -254,8 +256,8 @@ void AudioDisplay::DoUpdateImage() {
 
 	// Draw selection bg
 	if (hasSel && drawSelStart < drawSelEnd && draw_selection_background) {
-		if (NeedCommit && !karaoke->enabled) dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background Modified"))));
-		else dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Selection Background"))));
+		if (NeedCommit && !karaoke->enabled) dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Selection Modified")->GetColour())));
+		else dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Background")->GetColour())));
 		dc.DrawRectangle(drawSelStart,0,drawSelEnd-drawSelStart,h);
 	}
 
@@ -279,7 +281,7 @@ void AudioDisplay::DoUpdateImage() {
 		int64_t start = Position*samples;
 		int rate = provider->GetSampleRate();
 		int pixBounds = rate / samples;
-		dc.SetPen(wxPen(Options.AsColour(_T("Audio Seconds Boundaries")),1,wxDOT));
+		dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Seconds Boundaries")->GetColour()),1,wxDOT));
 		if (pixBounds >= 8) {
 			for (int x=0;x<w;x++) {
 				if (((x*samples)+start) % rate < samples) {
@@ -290,9 +292,9 @@ void AudioDisplay::DoUpdateImage() {
 	}
 
 	// Draw current frame
-	if (Options.AsBool(_T("Audio Draw Video Position"))) {
+	if (OPT_GET("Audio/Display/Draw/Video Position")->GetBool()) {
 		if (VideoContext::Get()->IsLoaded()) {
-			dc.SetPen(wxPen(Options.AsColour(_T("Audio Play Cursor")),2,wxLONG_DASH));
+			dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Play Cursor")->GetColour())));
 			int x = GetXAtMS(VFR_Output.GetTimeAtFrame(VideoContext::Get()->GetFrameN()));
 			dc.DrawLine(x,0,x,h);
 		}
@@ -310,9 +312,9 @@ void AudioDisplay::DoUpdateImage() {
 		// Draw boundaries
 		if (true) {
 			// Draw start boundary
-			int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
-			dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary start"))));
-			dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary start"))));
+			int selWidth = OPT_GET("Audio/Line Boundaries Thickness")->GetInt();
+			dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary Start")->GetColour())));
+			dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary Start")->GetColour())));
 			dc.DrawRectangle(lineStart-selWidth/2+1,0,selWidth,h);
 			wxPoint points1[3] = { wxPoint(lineStart,0), wxPoint(lineStart+10,0), wxPoint(lineStart,10) };
 			wxPoint points2[3] = { wxPoint(lineStart,h-1), wxPoint(lineStart+10,h-1), wxPoint(lineStart,h-11) };
@@ -320,8 +322,8 @@ void AudioDisplay::DoUpdateImage() {
 			dc.DrawPolygon(3,points2);
 
 			// Draw end boundary
-			dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary end"))));
-			dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary end"))));
+			dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary End")->GetColour())));
+			dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary End")->GetColour())));
 			dc.DrawRectangle(lineEnd-selWidth/2+1,0,selWidth,h);
 			wxPoint points3[3] = { wxPoint(lineEnd,0), wxPoint(lineEnd-10,0), wxPoint(lineEnd,10) };
 			wxPoint points4[3] = { wxPoint(lineEnd,h-1), wxPoint(lineEnd-10,h-1), wxPoint(lineEnd,h-11) };
@@ -333,11 +335,11 @@ void AudioDisplay::DoUpdateImage() {
 		if (hasKaraoke) {
 			try {
 				// Prepare
-				wxPen curPen(Options.AsColour(_T("Audio Syllable boundaries")),1,wxDOT);
+				wxPen curPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Syllable Boundaries")->GetColour()),1,wxDOT);
 				dc.SetPen(curPen);
 				wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"),wxFONTENCODING_SYSTEM);
 				dc.SetFont(curFont);
-				if (!spectrum) dc.SetTextForeground(Options.AsColour(_T("Audio Syllable text")));
+				if (!spectrum) dc.SetTextForeground(lagi_wxColour(OPT_GET("Colour/Audio Display/Syllable Text")->GetColour()));
 				else dc.SetTextForeground(wxColour(255,255,255));
 				size_t karn = karaoke->syllables.size();
 				int64_t pos1,pos2;
@@ -403,18 +405,18 @@ void AudioDisplay::DoUpdateImage() {
 /// Draws markers for inactive lines, eg. the previous line, per configuration.
 void AudioDisplay::DrawInactiveLines(wxDC &dc) {
 	// Check if there is anything to do
-	int shadeType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
+	int shadeType = OPT_GET("Audio/Inactive Lines Display Mode")->GetInt();
 	if (shadeType == 0) return;
 
 	// Spectrum?
 	bool spectrum = false;
-	if (provider && Options.AsBool(_T("Audio Spectrum"))) {
+	if (provider && OPT_GET("Audio/Spectrum")->GetBool()) {
 		spectrum = true;
 	}
 
 	// Set options
-	dc.SetBrush(wxBrush(Options.AsColour(_T("Audio Line boundary inactive line"))));
-	int selWidth = Options.AsInt(_T("Audio Line boundaries Thickness"));
+	dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line Boundary Inactive Line")->GetColour())));
+	int selWidth = OPT_GET("Audio/Line Boundaries Thickness")->GetInt();
 	AssDialogue *shade;
 	int shadeX1,shadeX2;
 	int shadeFrom,shadeTo;
@@ -459,13 +461,13 @@ void AudioDisplay::DrawInactiveLines(wxDC &dc) {
 				x2 = MIN(x2,selX1);
 
 				// Set pen and draw
-				dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Inactive"))));
+				dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Inactive")->GetColour())));
 				for (int i=x1;i<x2;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
 				for (int i=x3;i<x4;i++) dc.DrawLine(i,peak[i],i,min[i]-1);
 			}
 
 			// Draw boundaries
-			dc.SetPen(wxPen(Options.AsColour(_T("Audio Line boundary inactive line"))));
+			dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Line Boundary Inactive Line")->GetColour())));
 			dc.DrawRectangle(shadeX1-selWidth/2+1,0,selWidth,h);
 			dc.DrawRectangle(shadeX2-selWidth/2+1,0,selWidth,h);
 		}
@@ -498,7 +500,7 @@ void AudioDisplay::DrawKeyframes(wxDC &dc) {
 /// @param dc The DC to draw to.
 void AudioDisplay::DrawTimescale(wxDC &dc) {
 	// Set size
-	int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 
 	// Set colours
 	dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
@@ -577,23 +579,23 @@ void AudioDisplay::DrawWaveform(wxDC &dc,bool weak) {
 
 	// Draw pre-selection
 	if (!hasSel) selStartCap = w;
-	dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
+	dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform")->GetColour())));
 	for (int64_t i=0;i<selStartCap;i++) {
 		dc.DrawLine(i,peak[i],i,min[i]-1);
 	}
 
 	if (hasSel) {
 		// Draw selection
-		if (Options.AsBool(_T("Audio Draw Selection Background"))) {
-			if (NeedCommit && !karaoke->enabled) dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Modified"))));
-			else dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform Selected"))));
+		if (OPT_GET("Audio/Display/Draw/Selection Background")->GetBool()) {
+			if (NeedCommit && !karaoke->enabled) dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Modified")->GetColour())));
+			else dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Selected")->GetColour())));
 		}
 		for (int64_t i=selStartCap;i<selEndCap;i++) {
 			dc.DrawLine(i,peak[i],i,min[i]-1);
 		}
 
 		// Draw post-selection
-		dc.SetPen(wxPen(Options.AsColour(_T("Audio Waveform"))));
+		dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform")->GetColour())));
 		for (int64_t i=selEndCap;i<w;i++) {
 			dc.DrawLine(i,peak[i],i,min[i]-1);
 		}
@@ -707,7 +709,7 @@ void AudioDisplay::GetKaraokePos(int64_t &karStart,int64_t &karEnd, bool cap) {
 void AudioDisplay::Update() {
 	if (blockUpdate) return;
 	if (loaded) {
-		if (Options.AsBool(_T("Audio Autoscroll")))
+		if (OPT_GET("Audio/Auto/Scroll")->GetBool())
 			MakeDialogueVisible();
 		else
 			UpdateImage(true);
@@ -717,7 +719,7 @@ void AudioDisplay::Update() {
 /// @brief Recreate the image 
 void AudioDisplay::RecreateImage() {
 	GetClientSize(&w,&h);
-	h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	h -= OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 	delete origImage;
 	origImage = NULL;
 	UpdateImage(false);
@@ -919,7 +921,7 @@ void AudioDisplay::SetFile(wxString file) {
 			// Add to recent
 			if (!is_dummy) {
 				wxLogDebug(_T("AudioDisplay::SetFile: add to recent"));
-				Options.AddToRecentList(file,_T("Recent aud"));
+				AegisubApp::Get()->mru->Add("Audio", STD_STR(file));
 				wxFileName fn(file);
 				StandardPaths::SetPathValue(_T("?audio"),fn.GetPath());
 			}
@@ -1140,7 +1142,7 @@ void AudioDisplay::SetDialogue(SubtitlesGrid *_grid,AssDialogue *diag,int n) {
 		NeedCommit = false;
 
 		// Set times
-		if (dialogue && !dontReadTimes && Options.AsBool(_T("Audio grab times on select"))) {
+		if (dialogue && !dontReadTimes && OPT_GET("Audio/Grab Times on Select")->GetBool()) {
 			wxLogDebug(_T("AudioDisplay::SetDialogue: grabbing times"));
 			int s = dialogue->Start.GetMS();
 			int e = dialogue->End.GetMS();
@@ -1249,7 +1251,7 @@ void AudioDisplay::CommitChanges (bool nextLine) {
 	}
 
 	// Next line (ugh what a condition, can this be simplified?)
-	if (nextLine && !karaoke->enabled && Options.AsBool(_T("Audio Next Line on Commit")) && !wasKaraSplitting) {
+	if (nextLine && !karaoke->enabled && OPT_GET("Audio/Next Line on Commit")->GetBool() && !wasKaraSplitting) {
 		wxLogDebug(_T("AudioDisplay::CommitChanges: going to next line"));
 		// Insert a line if it doesn't exist
 		int nrows = grid->GetRows();
@@ -1258,15 +1260,15 @@ void AudioDisplay::CommitChanges (bool nextLine) {
 			AssDialogue *def = new AssDialogue;
 			def->Start = grid->GetDialogue(line_n)->End;
 			def->End = grid->GetDialogue(line_n)->End;
-			def->End.SetMS(def->End.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+			def->End.SetMS(def->End.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 			def->Style = grid->GetDialogue(line_n)->Style;
 			grid->InsertLine(def,line_n,true);
 			curStartMS = curEndMS;
-			curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
+			curEndMS = curStartMS + OPT_GET("Timing/Default Duration")->GetInt();
 		}
 		else if (grid->GetDialogue(line_n+1)->Start.GetMS() == 0 && grid->GetDialogue(line_n+1)->End.GetMS() == 0) {
 			curStartMS = curEndMS;
-			curEndMS = curStartMS + Options.AsInt(_T("Timing Default Duration"));
+			curEndMS = curStartMS + OPT_GET("Timing/Default Duration")->GetInt();
 		}
 		else {
 			curStartMS = grid->GetDialogue(line_n+1)->Start.GetMS();
@@ -1289,19 +1291,19 @@ void AudioDisplay::CommitChanges (bool nextLine) {
 void AudioDisplay::AddLead(bool in,bool out) {
 	// Lead in
 	if (in) {
-		curStartMS -= Options.AsInt(_T("Audio Lead in"));
+		curStartMS -= OPT_GET("Audio/Lead/IN")->GetInt();
 		if (curStartMS < 0) curStartMS = 0;
 	}
 
 	// Lead out
 	if (out) {
-		curEndMS += Options.AsInt(_T("Audio Lead out"));
+		curEndMS += OPT_GET("Audio/Lead/OUT")->GetInt();
 	}
 
 	// Set changes
 	UpdateTimeEditCtrls();
 	NeedCommit = true;
-	if (Options.AsBool(_T("Audio Autocommit"))) CommitChanges();
+	if (OPT_GET("Audio/Auto/Commit")->GetBool()) CommitChanges();
 	Update();
 }
 
@@ -1337,7 +1339,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 	int64_t y = event.GetY();
 	bool karMode = karaoke->enabled;
 	bool shiftDown = event.m_shiftDown;
-	int timelineHeight = Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 
 	// Leaving event
 	if (event.Leaving()) {
@@ -1358,7 +1360,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 			inside = true;
 
 			// Get focus
-			if (wxWindow::FindFocus() != this && Options.AsBool(_T("Audio Autofocus"))) SetFocus();
+			if (wxWindow::FindFocus() != this && OPT_GET("Audio/Auto/Focus")->GetBool()) SetFocus();
 		}
 		else if (y < h+timelineHeight) onScale = true;
 	}
@@ -1387,7 +1389,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 	if (event.GetWheelRotation() != 0) {
 		// Zoom or scroll?
 		bool zoom = shiftDown;
-		if (Options.AsBool(_T("Audio Wheel Default To Zoom"))) zoom = !zoom;
+		if (OPT_GET("Audio/Wheel Default to Zoom")->GetBool()) zoom = !zoom;
 
 		// Zoom
 		if (zoom) {
@@ -1423,7 +1425,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 			dc.DrawLine(x,0,x,h);
 
 			// Time
-			if (Options.AsBool(_T("Audio Draw Cursor Time"))) {
+			if (OPT_GET("Audio/Display/Draw/Cursor")->GetBool()) {
 				// Time string
 				AssTime time;
 				time.SetMS(GetMSAtX(x));
@@ -1525,7 +1527,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 			// Line timing mode
 			if (!karTime) {
 				// Grab start
-				if (abs64 (x - selStart) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
+				if (abs64 (x - selStart) < 6 && OPT_GET("Audio/Display/Dragging Times")->GetBool()==false) {
 					wxCursor cursor(wxCURSOR_SIZEWE);
 					SetCursor(cursor);
 					defCursor = false;
@@ -1536,7 +1538,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 				}
 
 				// Grab end
-				else if (abs64 (x - selEnd) < 6 && Options.AsBool(_T("Disable Dragging Times"))==false) {
+				else if (abs64 (x - selEnd) < 6 && OPT_GET("Audio/Display/Dragging Times")->GetBool()==false) {
 					wxCursor cursor(wxCURSOR_SIZEWE);
 					SetCursor(cursor);
 					defCursor = false;
@@ -1608,7 +1610,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 						updated = true;
 						diagUpdated = true;
 
-						if (leftIsDown && abs((long)(x-lastX)) > Options.AsInt(_T("Audio Start Drag Sensitivity"))) {
+						if (leftIsDown && abs((long)(x-lastX)) > OPT_GET("Audio/Start Drag Sensitivity")->GetInt()) {
 							selStart = lastX;
 							selEnd = x;
 							curStartMS = GetBoundarySnap(GetMSAtX(lastX),10,event.ShiftDown(),true);
@@ -1697,7 +1699,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
 					NeedCommit = true;
 					if (curStartMS <= curEndMS) {
 						UpdateTimeEditCtrls();
-						if (Options.AsBool(_T("Audio Autocommit"))) CommitChanges();
+						if (OPT_GET("Audio/Auto/Commit")->GetBool()) CommitChanges();
 					}
 
 					else UpdateImage(true);
@@ -1750,9 +1752,9 @@ int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
 
 	// Keyframe boundaries
 	wxArrayInt boundaries;
-	bool snapKey = Options.AsBool(_T("Audio snap to keyframes"));
+	bool snapKey = OPT_GET("Audio/Display/Snap/Keyframes")->GetBool();
 	if (shiftHeld) snapKey = !snapKey;
-	if (snapKey && VideoContext::Get()->KeyFramesLoaded() && Options.AsBool(_T("Audio Draw Keyframes"))) {
+	if (snapKey && VideoContext::Get()->KeyFramesLoaded() && OPT_GET("Audio/Display/Draw/Keyframes")->GetBool()) {
 		int64_t keyMS;
 		wxArrayInt keyFrames = VideoContext::Get()->GetKeyFrames();
 		int frame;
@@ -1767,8 +1769,8 @@ int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
 	}
 
 	// Other subtitles' boundaries
-	int inactiveType = Options.AsInt(_T("Audio Inactive Lines Display Mode"));
-	bool snapLines = Options.AsBool(_T("Audio snap to other lines"));
+	int inactiveType = OPT_GET("Audio/Inactive Lines Display Mode")->GetInt();
+	bool snapLines = OPT_GET("Audio/Display/Snap/Other Lines")->GetBool();
 	if (shiftHeld) snapLines = !snapLines;
 	if (snapLines && (inactiveType == 1 || inactiveType == 2)) {
 		AssDialogue *shade;
@@ -1932,7 +1934,7 @@ int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
 void AudioDisplay::OnSize(wxSizeEvent &event) {
 	// Set size
 	GetClientSize(&w,&h);
-	h -= Options.AsBool(_T("Audio Draw Timeline")) ? 20 : 0;
+	h -= OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
 
 	// Update image
 	UpdateSamples();
@@ -1973,7 +1975,7 @@ void AudioDisplay::OnUpdateTimer(wxTimerEvent &event) {
 			int posX = GetXAtSample(curPos);
 			bool fullDraw = false;
 			bool centerLock = false;
-			bool scrollToCursor = Options.AsBool(_T("Audio lock scroll on cursor"));
+			bool scrollToCursor = OPT_GET("Audio/Lock Scroll on Cursor")->GetBool();
 			if (centerLock) {
 				int goTo = MAX(0,curPos - w*samples/2);
 				if (goTo >= 0) {
@@ -1999,7 +2001,7 @@ void AudioDisplay::OnUpdateTimer(wxTimerEvent &event) {
 			wxMemoryDC src;
 			curpos = GetXAtSample(curPos);
 			if (curpos >= 0 && curpos < GetClientSize().GetWidth()) {
-				dc.SetPen(wxPen(Options.AsColour(_T("Audio Play cursor"))));
+				dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Play Cursor")->GetColour())));
 				src.SelectObject(*origImage);
 				if (fullDraw) {
 					//dc.Blit(0,0,w,h,&src,0,0);
@@ -2195,7 +2197,7 @@ void AudioDisplay::OnKeyDown(wxKeyEvent &event) {
 	if (diagUpdated) {
 		diagUpdated = false;
 		NeedCommit = true;
-		if (Options.AsBool(_T("Audio Autocommit")) && curStartMS <= curEndMS) CommitChanges();
+		if (OPT_GET("Audio/Auto/Commit")->GetBool() && curStartMS <= curEndMS) CommitChanges();
 		else UpdateImage(true);
 	}
 }
diff --git a/aegisub/src/audio_player.cpp b/aegisub/src/audio_player.cpp
index ea822dc8e..8d354f939 100644
--- a/aegisub/src/audio_player.cpp
+++ b/aegisub/src/audio_player.cpp
@@ -59,6 +59,8 @@
 #ifdef WITH_PULSEAUDIO
 #include "audio_player_pulse.h"
 #endif
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -151,7 +153,7 @@ void AudioPlayer::OnStopAudio(wxCommandEvent &event) {
 ///
 AudioPlayer* AudioPlayerFactoryManager::GetAudioPlayer() {
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Audio player")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Audio/Player")->GetString()));
 
 	// None available
 	if (list.Count() == 0) throw _T("No audio players are available.");
diff --git a/aegisub/src/audio_player_alsa.cpp b/aegisub/src/audio_player_alsa.cpp
index 60b782e32..1c39b7178 100644
--- a/aegisub/src/audio_player_alsa.cpp
+++ b/aegisub/src/audio_player_alsa.cpp
@@ -85,7 +85,7 @@ void AlsaPlayer::OpenStream()
 	// We want playback
 	stream = SND_PCM_STREAM_PLAYBACK;
 	// And get a device name
-	wxString device = Options.AsText(_T("Audio Alsa Device"));
+	wxString device = lagi_wxString(OPT_GET("Player/Audio/ALSA/Device")->GetString());
 
 	// Open device for blocking access
 	if (snd_pcm_open(&pcm_handle, device.mb_str(wxConvUTF8), stream, 0) < 0) { // supposedly we don't want SND_PCM_ASYNC even for async playback
diff --git a/aegisub/src/audio_player_dsound2.cpp b/aegisub/src/audio_player_dsound2.cpp
index 118b0e8e3..f36bafd96 100644
--- a/aegisub/src/audio_player_dsound2.cpp
+++ b/aegisub/src/audio_player_dsound2.cpp
@@ -863,8 +863,8 @@ DirectSoundPlayer2::DirectSoundPlayer2()
 	thread = 0;
 
 	// The buffer will hold BufferLength times WantedLatency milliseconds of audio
-	WantedLatency = Options.AsInt(_T("Audio dsound buffer latency"));
-	BufferLength = Options.AsInt(_T("Audio dsound buffer length"));
+	WantedLatency = OPT_GET("Player/Audio/DirectSound/Buffer Latency")->GetInt();
+	BufferLength = OPT_GET("Player/Audio/DirectSound/Buffer Length")->GetInt();
 
 	// sanity checking
 	if (WantedLatency <= 0)
diff --git a/aegisub/src/audio_player_oss.cpp b/aegisub/src/audio_player_oss.cpp
index 8fcbff164..e01eb2d79 100644
--- a/aegisub/src/audio_player_oss.cpp
+++ b/aegisub/src/audio_player_oss.cpp
@@ -43,6 +43,8 @@
 #include "audio_player_oss.h"
 #include "audio_provider_manager.h"
 #include "frame_main.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "utils.h"
 
@@ -81,7 +83,7 @@ void OSSPlayer::OpenStream()
     bpf = provider->GetChannels() * provider->GetBytesPerSample();
 
     // Open device
-    wxString device = Options.AsText(_T("Audio OSS Device"));
+    wxString device = lagi_wxString(OPT_GET("Audio/OSS/Device")->GetString());
     dspdev = ::open(device.mb_str(wxConvUTF8), O_WRONLY, 0);
     if (dspdev < 0) {
         throw _T("OSS player: opening device failed");
diff --git a/aegisub/src/audio_player_portaudio.cpp b/aegisub/src/audio_player_portaudio.cpp
index f10e3792f..08d49e5fa 100644
--- a/aegisub/src/audio_player_portaudio.cpp
+++ b/aegisub/src/audio_player_portaudio.cpp
@@ -44,6 +44,7 @@
 #include "audio_player_portaudio.h"
 #include "audio_provider_manager.h"
 #include "charset_conv.h"
+#include "main.h"
 #include "options.h"
 #include "utils.h"
 #include <wx/log.h>
@@ -84,7 +85,7 @@ void PortAudioPlayer::OpenStream() {
 	// Open stream
 	PaStreamParameters pa_output_p;
 
-	int pa_config_default = Options.AsInt(_T("Audio PortAudio Device"));
+	int pa_config_default = OPT_GET("Player/Audio/PortAudio/Device")->GetInt();
 	PaDeviceIndex pa_device;
 
 	if (pa_config_default < 0) {
diff --git a/aegisub/src/audio_provider.cpp b/aegisub/src/audio_provider.cpp
index 71c7a2c61..62ee5d85a 100644
--- a/aegisub/src/audio_provider.cpp
+++ b/aegisub/src/audio_provider.cpp
@@ -57,6 +57,8 @@
 #include "audio_provider_quicktime.h"
 #endif
 #include "audio_provider_ram.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -243,7 +245,7 @@ AudioProvider *AudioProviderFactoryManager::GetAudioProvider(wxString filename,
 	// Prepare provider
 	AudioProvider *provider = NULL;
 
-	if (!Options.AsBool(_T("Audio Disable PCM Provider"))) {
+	if (!OPT_GET("Provider/Audio/PCM/Disable")->GetBool()) {
 		// Try a PCM provider first
 		provider = CreatePCMAudioProvider(filename);
 		if (provider) {
@@ -257,7 +259,7 @@ AudioProvider *AudioProviderFactoryManager::GetAudioProvider(wxString filename,
 	}
 
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Audio provider")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Audio/Provider")->GetString()));
 
 	// None available
 	if (list.Count() == 0) throw _T("No audio providers are available.");
@@ -285,7 +287,7 @@ AudioProvider *AudioProviderFactoryManager::GetAudioProvider(wxString filename,
 		provider = CreateConvertAudioProvider(provider);
 
 	// Change provider to RAM/HD cache if needed
-	if (cache == -1) cache = Options.AsInt(_T("Audio Cache"));
+	if (cache == -1) cache = OPT_GET("Audio/Cache/Type")->GetInt();
 	if (cache) {
 		AudioProvider *final = NULL;
 
diff --git a/aegisub/src/audio_provider_avs.cpp b/aegisub/src/audio_provider_avs.cpp
index 04e6d6466..a3d778ede 100644
--- a/aegisub/src/audio_provider_avs.cpp
+++ b/aegisub/src/audio_provider_avs.cpp
@@ -51,6 +51,8 @@
 
 #include "audio_provider_avs.h"
 #include "charset_conv.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "utils.h"
@@ -152,7 +154,7 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue _clip) {
 
 	// Convert to one channel
 	char buffer[1024];
-	strcpy(buffer,Options.AsText(_T("Audio Downmixer")).mb_str(csConvLocal));
+	strcpy(buffer,lagi_wxString(OPT_GET("Audio/Downmixer")->GetString()).mb_str(csConvLocal));
 	script = env->Invoke(buffer, _clip);
 
 	// Convert to 16 bits per sample
@@ -160,7 +162,7 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue _clip) {
 	vi = script.AsClip()->GetVideoInfo();
 
 	// Convert sample rate
-	int setsample = Options.AsInt(_T("Audio Sample Rate"));
+	int setsample = OPT_GET("Provider/Audio/AVS/Sample Rate")->GetInt();
 	if (vi.SamplesPerSecond() < 32000) setsample = 44100;
 	if (setsample != 0) {
 		AVSValue args[2] = { script, setsample };
diff --git a/aegisub/src/audio_provider_ffmpegsource.cpp b/aegisub/src/audio_provider_ffmpegsource.cpp
index 1134b2445..2d9b50203 100644
--- a/aegisub/src/audio_provider_ffmpegsource.cpp
+++ b/aegisub/src/audio_provider_ffmpegsource.cpp
@@ -50,6 +50,7 @@
 
 #include "audio_provider_ffmpegsource.h"
 #include "include/aegisub/aegisub.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -164,7 +165,7 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
 	// moment of truth
 	if (!IndexIsValid) {
 		int TrackMask;
-		if (Options.AsBool(_T("FFmpegSource always index all tracks")) || TrackNumber == FFMS_TRACKMASK_ALL)
+		if (OPT_GET("Provider/FFmpegSource/Index All Tracks")->GetBool() || TrackNumber == FFMS_TRACKMASK_ALL)
 			TrackMask = FFMS_TRACKMASK_ALL;
 		else
 			TrackMask = (1 << TrackNumber);
diff --git a/aegisub/src/audio_provider_hd.cpp b/aegisub/src/audio_provider_hd.cpp
index 8704322a8..5d6eb1fce 100644
--- a/aegisub/src/audio_provider_hd.cpp
+++ b/aegisub/src/audio_provider_hd.cpp
@@ -47,6 +47,7 @@
 #endif
 
 #include "audio_provider_hd.h"
+#include "compat.h"
 #include "dialog_progress.h"
 #include "frame_main.h"
 #include "main.h"
@@ -159,7 +160,7 @@ void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
 ///
 wxString HDAudioProvider::DiskCachePath() {
 	// Default
-	wxString path = Options.AsText(_T("Audio HD Cache Location"));
+	wxString path = lagi_wxString(OPT_GET("Audio/Cache/HD/Location")->GetString());
 	if (path == _T("default")) return StandardPaths::DecodePath(_T("?temp/"));
 
 	// Specified
@@ -172,7 +173,7 @@ wxString HDAudioProvider::DiskCachePath() {
 ///
 wxString HDAudioProvider::DiskCacheName() {
 	// Get pattern
-	wxString pattern = Options.AsText(_T("Audio HD Cache Name"));
+	wxString pattern = lagi_wxString(OPT_GET("Audio/Cache/HD/Name")->GetString());
 	if (pattern.Find(_T("%02i")) == wxNOT_FOUND) pattern = _T("audio%02i.tmp");
 	
 	// Try from 00 to 99
diff --git a/aegisub/src/audio_renderer_spectrum.cpp b/aegisub/src/audio_renderer_spectrum.cpp
index a37a76b75..dbac71246 100644
--- a/aegisub/src/audio_renderer_spectrum.cpp
+++ b/aegisub/src/audio_renderer_spectrum.cpp
@@ -54,6 +54,7 @@
 #include "audio_renderer_spectrum.h"
 #include "colorspace.h"
 #include "fft.h"
+#include "main.h"
 #include "options.h"
 #include "utils.h"
 
@@ -565,7 +566,7 @@ public:
 		cache_root = new IntermediateSpectrumCache(provider, 0, num_lines, num_overlaps, 0);
 
 		// option is stored in megabytes, but we want number of bytes
-		unsigned long max_cache_size = Options.AsInt(_T("Audio Spectrum Memory Max"));
+		unsigned long max_cache_size = OPT_GET("Audio/Renderer/Spectrum/Memory Max")->GetInt();
 		// It can't go too low
 		if (max_cache_size < 5) max_cache_size = 128;
 		max_cache_size *= 1024 * 1024;
@@ -590,7 +591,7 @@ AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
 	provider = _provider;
 
 	// Determine the quality of the spectrum rendering based on an index
-	int quality_index = Options.AsInt(_T("Audio Spectrum Quality"));
+	int quality_index = OPT_GET("Audio/Renderer/Spectrum/Quality")->GetInt();
 	if (quality_index < 0) quality_index = 0;
 	if (quality_index > 5) quality_index = 5; // no need to go freaking insane
 
@@ -655,7 +656,7 @@ AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
 	cache = new AudioSpectrumCacheManager(provider, line_length, num_lines, fft_overlaps);
 
 	power_scale = 1;
-	minband = Options.AsInt(_T("Audio Spectrum Cutoff"));
+	minband = OPT_GET("Audio/Renderer/Spectrum/Cutoff")->GetInt();
 	maxband = line_length - minband * 2/3; // TODO: make this customisable?
 
 	// Generate colour maps
diff --git a/aegisub/src/auto4_base.cpp b/aegisub/src/auto4_base.cpp
index a8254b2ff..b0dc0506d 100644
--- a/aegisub/src/auto4_base.cpp
+++ b/aegisub/src/auto4_base.cpp
@@ -68,6 +68,8 @@
 #include "ass_file.h"
 #include "ass_style.h"
 #include "auto4_base.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "string_codec.h"
@@ -491,7 +493,7 @@ namespace Automation4 {
 		Center();
 
 		// Init trace level
-		trace_level = Options.AsInt(_T("Automation Trace Level"));
+		trace_level = OPT_GET("Automation/Trace Level")->GetInt();
 	}
 
 
@@ -678,7 +680,7 @@ namespace Automation4 {
 		// copied from auto3
 		include_path.clear();
 		include_path.EnsureFileAccessible(filename);
-		wxStringTokenizer toker(Options.AsText(_T("Automation Include Path")), _T("|"), wxTOKEN_STRTOK);
+		wxStringTokenizer toker(lagi_wxString(OPT_GET("Path/Automation/Include")->GetString()), _T("|"), wxTOKEN_STRTOK);
 		while (toker.HasMoreTokens()) {
 			// todo? make some error reporting here
 			wxFileName path(StandardPaths::DecodePath(toker.GetNextToken()));
diff --git a/aegisub/src/auto4_lua.cpp b/aegisub/src/auto4_lua.cpp
index 34a31ab56..fc1e5b6b4 100644
--- a/aegisub/src/auto4_lua.cpp
+++ b/aegisub/src/auto4_lua.cpp
@@ -58,6 +58,7 @@
 #include "auto4_lua.h"
 #include "auto4_lua_factory.h"
 #include "auto4_lua_scriptreader.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "text_file_reader.h"
@@ -196,7 +197,7 @@ namespace Automation4 {
 			lua_pushstring(L, "path");
 			lua_gettable(L, -3);
 
-			wxStringTokenizer toker(Options.AsText(_T("Automation Include Path")), _T("|"), wxTOKEN_STRTOK);
+			wxStringTokenizer toker(lagi_wxString(OPT_GET("Path/Automation/Include")->GetString()), _T("|"), wxTOKEN_STRTOK);
 			while (toker.HasMoreTokens()) {
 				wxFileName path(StandardPaths::DecodePath(toker.GetNextToken()));
 				if (path.IsOk() && !path.IsRelative() && path.DirExists()) {
@@ -563,7 +564,7 @@ namespace Automation4 {
 		, nargs(_nargs)
 		, nresults(_nresults)
 	{
-		int prio = Options.AsInt(_T("Automation Thread Priority"));
+		int prio = OPT_GET("Automation/Lua/Thread Priority")->GetInt();
 		if (prio == 0) prio = 50; // normal
 		else if (prio == 1) prio = 30; // below normal
 		else if (prio == 2) prio = 10; // lowest
diff --git a/aegisub/src/auto4_lua.h b/aegisub/src/auto4_lua.h
index 6c3565683..d896500ea 100644
--- a/aegisub/src/auto4_lua.h
+++ b/aegisub/src/auto4_lua.h
@@ -42,6 +42,7 @@
 #include <wx/thread.h>
 #endif
 
+#include "compat.h"
 #include "auto4_base.h"
 
 #ifdef __WINDOWS__
diff --git a/aegisub/src/avisynth_wrap.cpp b/aegisub/src/avisynth_wrap.cpp
index 6f5f74a04..95a8aae02 100644
--- a/aegisub/src/avisynth_wrap.cpp
+++ b/aegisub/src/avisynth_wrap.cpp
@@ -42,6 +42,7 @@
 
 #ifdef WITH_AVISYNTH
 #include "avisynth_wrap.h"
+#include "main.h"
 #include "options.h"
 
 #ifdef DEBUG_AVISYNTH_CODE
@@ -101,7 +102,7 @@ AviSynthWrapper::AviSynthWrapper() {
 		AVSTRACE(_T("Got address of CreateScriptEnv"));
 
 		// Require Avisynth 2.5.6+?
-		if (Options.AsBool(_T("Allow Ancient Avisynth")))
+		if (OPT_GET("Provider/Avisynth/Allow Ancient")->GetBool())
 			env = CreateScriptEnv(AVISYNTH_INTERFACE_VERSION-1);
 		else
 			env = CreateScriptEnv(AVISYNTH_INTERFACE_VERSION);
@@ -112,7 +113,7 @@ AviSynthWrapper::AviSynthWrapper() {
 		}
 		AVSTRACE(_T("Created script environment"));
 		// Set memory limit
-		int memoryMax = Options.AsInt(_T("Avisynth MemoryMax"));
+		const int memoryMax = OPT_GET("Provider/Avisynth/Memory Max")->GetInt();
 		if (memoryMax != 0) {
 			env->SetMemoryMax(memoryMax);
 			AVSTRACE(_T("Set Avisynth memory limit"));
diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp
index a855a2cb1..329caf23c 100644
--- a/aegisub/src/base_grid.cpp
+++ b/aegisub/src/base_grid.cpp
@@ -48,7 +48,9 @@
 #include "ass_style.h"
 #include "audio_display.h"
 #include "base_grid.h"
+#include "compat.h"
 #include "frame_main.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "utils.h"
@@ -106,10 +108,10 @@ BaseGrid::~BaseGrid() {
 ///
 void BaseGrid::UpdateStyle() {
 	// Set font
-	wxString fontname = Options.AsText(_T("Grid Font Face"));
+	wxString fontname = lagi_wxString(OPT_GET("Subtitle/Grid/Font Face")->GetString());
 	if (fontname.IsEmpty()) fontname = _T("Tahoma");
 	font.SetFaceName(fontname);
-	font.SetPointSize(Options.AsInt(_T("Grid font size")));
+	font.SetPointSize(OPT_GET("Subtitle/Grid/Font Size")->GetInt());
 	font.SetWeight(wxFONTWEIGHT_NORMAL);
 
 	// Set line height
@@ -122,7 +124,9 @@ void BaseGrid::UpdateStyle() {
 	}
 
 	// Set column widths
-	for (int i=0;i<10;i++) showCol[i] = Options.AsBool(_T("Grid show column ") + AegiIntegerToString(i));
+	std::vector<bool> column_array;
+	OPT_GET("Subtitle/Grid/Column")->GetListBool(column_array);
+	for (int i=0;i<10;i++) showCol[i] = column_array.at(i);
 	SetColumnWidths();
 
 	// Update
@@ -418,12 +422,12 @@ void BaseGrid::DrawImage(wxDC &dc) {
 	dc.SetFont(font);
 
 	// Clear background
-	dc.SetBackground(wxBrush(Options.AsColour(_T("Grid Background"))));
+	dc.SetBackground(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Background")->GetColour())));
 	dc.Clear();
 
 	// Draw labels
 	dc.SetPen(*wxTRANSPARENT_PEN);
-	dc.SetBrush(wxBrush(Options.AsColour(_T("Grid left column"))));
+	dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Left Column")->GetColour())));
 	dc.DrawRectangle(0,lineHeight,colWidth[0],h-lineHeight);
 
 	// Visible lines
@@ -434,23 +438,23 @@ void BaseGrid::DrawImage(wxDC &dc) {
 	// Row colors
 	std::vector<wxBrush> rowColors;
 	std::vector<wxColor> foreColors;
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid Background"))));					// 0 = Standard
-	foreColors.push_back(Options.AsColour(_T("Grid standard foreground")));
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid Header"))));						// 1 = Header
-	foreColors.push_back(Options.AsColour(_T("Grid standard foreground")));
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid selection background"))));		// 2 = Selected
-	foreColors.push_back(Options.AsColour(_T("Grid selection foreground")));
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid comment background"))));			// 3 = Commented
-	foreColors.push_back(Options.AsColour(_T("Grid selection foreground")));
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid inframe background"))));			// 4 = Video Highlighted
-	foreColors.push_back(Options.AsColour(_T("Grid selection foreground")));
-	rowColors.push_back(wxBrush(Options.AsColour(_T("Grid selected comment background"))));	// 5 = Commented & selected
-	foreColors.push_back(Options.AsColour(_T("Grid selection foreground")));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Background")->GetColour())));					// 0 = Standard
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Standard")->GetColour()));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Header")->GetColour())));						// 1 = Header
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Standard")->GetColour()));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColour())));		// 2 = Selected
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Comment")->GetColour())));			// 3 = Commented
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Inframe")->GetColour())));			// 4 = Video Highlighted
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
+	rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Selected Comment")->GetColour())));	// 5 = Commented & selected
+	foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
 
 	// First grid row
 	bool drawGrid = true;
 	if (drawGrid) {
-		dc.SetPen(wxPen(Options.AsColour(_T("Grid lines"))));
+		dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
 		dc.DrawLine(0,0,w,0);
 		dc.SetPen(*wxTRANSPARENT_PEN);
 	}
@@ -516,12 +520,12 @@ void BaseGrid::DrawImage(wxDC &dc) {
 			strings.Add(curDiag->GetMarginString(2));
 
 			// Set text
-			int mode = Options.AsInt(_T("Grid Hide Overrides"));
+			int mode = OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt();
 			wxString value = _T("");
 
 			// Hidden overrides
 			if (mode == 1 || mode == 2) {
-				wxString replaceWith = Options.AsText(_T("Grid hide overrides char"));
+				wxString replaceWith = lagi_wxString(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString());
 				int textlen = curDiag->Text.Length();
 				int depth = 0;
 				wxChar curChar;
@@ -550,7 +554,7 @@ void BaseGrid::DrawImage(wxDC &dc) {
 			if (inSel && curDiag->Comment) curColor = 5;
 			else if (inSel) curColor = 2;
 			else if (curDiag->Comment) curColor = 3;
-			else if (Options.AsBool(_T("Highlight subs in frame")) && IsDisplayed(curDiag)) curColor = 4;
+			else if (OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")->GetBool() && IsDisplayed(curDiag)) curColor = 4;
 		}
 
 		else {
@@ -564,7 +568,7 @@ void BaseGrid::DrawImage(wxDC &dc) {
 		}
 
 		// Set text color
-		if (collides) dc.SetTextForeground(Options.AsColour(_T("Grid collision foreground")));
+		if (collides) dc.SetTextForeground(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Collision")->GetColour()));
 		else {
 			dc.SetTextForeground(foreColors[curColor]);
 		}
@@ -595,7 +599,7 @@ void BaseGrid::DrawImage(wxDC &dc) {
 		// Draw grid
 		dc.DestroyClippingRegion();
 		if (drawGrid) {
-			dc.SetPen(wxPen(Options.AsColour(_T("Grid lines"))));
+			dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
 			dc.DrawLine(0,dy+lineHeight,w,dy+lineHeight);
 			dc.SetPen(*wxTRANSPARENT_PEN);
 		}
@@ -604,7 +608,7 @@ void BaseGrid::DrawImage(wxDC &dc) {
 	// Draw grid columns
 	dx = 0;
 	if (drawGrid) {
-		dc.SetPen(wxPen(Options.AsColour(_T("Grid lines"))));
+		dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
 		for (int i=0;i<10;i++) {
 			dx += colWidth[i];
 			dc.DrawLine(dx,0,dx,maxH);
@@ -614,7 +618,7 @@ void BaseGrid::DrawImage(wxDC &dc) {
 	}
 
 	// Draw currently active line border
-	dc.SetPen(wxPen(Options.AsColour(_T("Grid Active border"))));
+	dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColour())));
 	dc.SetBrush(*wxTRANSPARENT_BRUSH);
 	dy = (editBox->linen+1-yPos) * lineHeight;
 	dc.DrawRectangle(0,dy,w,lineHeight+1);
@@ -677,7 +681,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
 
 	// Get focus
 	if (event.ButtonDown()) {
-		if (Options.AsBool(_T("Grid Allow Focus"))) {
+		if (OPT_GET("Subtitle/Grid/Focus Allow")->GetBool()) {
 			SetFocus();
 		}
 	}
diff --git a/aegisub/src/compat.cpp b/aegisub/src/compat.cpp
new file mode 100644
index 000000000..f9781ee1b
--- /dev/null
+++ b/aegisub/src/compat.cpp
@@ -0,0 +1,14 @@
+#include "compat.h"
+#include "main.h"
+
+wxArrayString lagi_MRU_wxAS(const wxString &list) {
+	wxArrayString work;
+
+	const agi::MRUManager::MRUListMap *map_list = AegisubApp::Get()->mru->Get(STD_STR(list));
+
+	for (agi::MRUManager::MRUListMap::const_iterator i_lst = map_list->begin(); i_lst != map_list->end(); ++i_lst) {
+		work.Add(wxString(i_lst->second));
+	}
+
+    return work;
+}
diff --git a/aegisub/src/compat.h b/aegisub/src/compat.h
new file mode 100644
index 000000000..fe794f772
--- /dev/null
+++ b/aegisub/src/compat.h
@@ -0,0 +1,15 @@
+#ifndef AGI_PRE
+#include <string>
+
+#include <wx/colour.h>
+#include <wx/arrstr.h>
+#include <wx/string.h>
+#endif
+
+#include <libaegisub/colour.h>
+
+#define STD_STR(x) std::string(x.mb_str())
+
+inline wxColour lagi_wxColour(const agi::Colour &colour) { return wxColour(colour); }
+inline wxString lagi_wxString(const std::string &str) { return wxString(str); }
+wxArrayString lagi_MRU_wxAS(const wxString &list);
diff --git a/aegisub/src/dialog_attachments.cpp b/aegisub/src/dialog_attachments.cpp
index da4fcb653..390cb84f8 100644
--- a/aegisub/src/dialog_attachments.cpp
+++ b/aegisub/src/dialog_attachments.cpp
@@ -50,9 +50,11 @@
 
 #include "ass_attachment.h"
 #include "ass_file.h"
+#include "compat.h"
 #include "dialog_attachments.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "utils.h"
 
@@ -153,7 +155,7 @@ void DialogAttachments::OnAttachFont(wxCommandEvent &event) {
 	wxArrayString filenames;
 	wxArrayString paths;
 	{
-		wxFileDialog diag (this,_("Choose file to be attached"), Options.AsText(_T("Fonts Collector Destination")), _T(""), _T("Font Files (*.ttf)|*.ttf"), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
+		wxFileDialog diag (this,_("Choose file to be attached"), lagi_wxString(OPT_GET("Path/Fonts Collector Destination")->GetString()), _T(""), _T("Font Files (*.ttf)|*.ttf"), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
 		if (diag.ShowModal() == wxID_CANCEL) return;
 		diag.GetFilenames(filenames);
 		diag.GetPaths(paths);
@@ -230,11 +232,11 @@ void DialogAttachments::OnExtract(wxCommandEvent &event) {
 		bool fullPath = false;
 
 		// Multiple or single?
-		if (listView->GetNextSelected(i) != -1) path = wxDirSelector(_("Select the path to save the files to:"),Options.AsText(_T("Fonts Collector Destination"))) + _T("/");
+		if (listView->GetNextSelected(i) != -1) path = wxDirSelector(_("Select the path to save the files to:"),lagi_wxString(OPT_GET("Path/Fonts Collector Destination")->GetString())) + _T("/");
 		else {
 			// Default path
 			wxString defPath = ((AssAttachment*) wxUIntToPtr(listView->GetItemData(i)))->GetFileName();
-			path = wxFileSelector(_("Select the path to save the file to:"),Options.AsText(_T("Fonts Collector Destination")),defPath);
+			path = wxFileSelector(_("Select the path to save the file to:"),lagi_wxString(OPT_GET("Path/Fonts Collector Destination")->GetString()),defPath);
 			fullPath = true;
 		}
 		if (path.IsEmpty()) return;
diff --git a/aegisub/src/dialog_automation.cpp b/aegisub/src/dialog_automation.cpp
index 290ec7d93..bb07b6a62 100644
--- a/aegisub/src/dialog_automation.cpp
+++ b/aegisub/src/dialog_automation.cpp
@@ -47,6 +47,7 @@
 #endif
 
 #include "auto4_base.h"
+#include "compat.h"
 #include "dialog_automation.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
@@ -230,12 +231,12 @@ void DialogAutomation::OnAdd(wxCommandEvent &evt)
 		fnfilter = _T("All supported scripts|") + catchall + _T("|") + fnfilter;
 	}
 
-	wxString fname = wxFileSelector(_("Add Automation script"), Options.AsText(_T("Last open automation path")), wxEmptyString, wxEmptyString, fnfilter, wxFD_OPEN|wxFD_FILE_MUST_EXIST, this);
+	wxString fname = wxFileSelector(_("Add Automation script"), lagi_wxString(OPT_GET("Path/Last/Automation")->GetString()), wxEmptyString, wxEmptyString, fnfilter, wxFD_OPEN|wxFD_FILE_MUST_EXIST, this);
 
 	if (!fname.IsEmpty()) {
 
 		wxFileName fnpath(fname);
-		Options.SetText(_T("Last open automation path"), fnpath.GetPath());
+		OPT_SET("Path/Last/Automation")->SetString(STD_STR(fnpath.GetPath()));
 
 		// TODO: make sure each script is only loaded once. check in both local and global managers!!
 		// it doesn't break for macros, but will for export filters, and maybe for file formats,
diff --git a/aegisub/src/dialog_colorpicker.cpp b/aegisub/src/dialog_colorpicker.cpp
index bfc3cf305..4994d816b 100644
--- a/aegisub/src/dialog_colorpicker.cpp
+++ b/aegisub/src/dialog_colorpicker.cpp
@@ -56,9 +56,11 @@
 
 #include "ass_style.h"
 #include "colorspace.h"
+#include "compat.h"
 #include "dialog_colorpicker.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "utils.h"
 
@@ -723,7 +725,7 @@ DialogColorPicker::DialogColorPicker(wxWindow *parent, wxColour initial_color)
 
 	wxSizer *recent_sizer = new wxBoxSizer(wxVERTICAL);
 	recent_sizer->Add(recent_box, 1, wxEXPAND);
-	if (Options.AsBool(_T("RGBAdjust Tool"))) recent_sizer->Add(new wxButton(this,BUTTON_RGBADJUST,_T("rgbadjust()")), 0, wxEXPAND);
+	if (OPT_GET("Tool/Colour Picker/RGBAdjust Tool")->GetBool()) recent_sizer->Add(new wxButton(this,BUTTON_RGBADJUST,_T("rgbadjust()")), 0, wxEXPAND);
 
 	wxSizer *picker_sizer = new wxBoxSizer(wxHORIZONTAL);
 	picker_sizer->AddStretchSpacer();
@@ -764,11 +766,11 @@ DialogColorPicker::DialogColorPicker(wxWindow *parent, wxColour initial_color)
 
 	// Fill the controls
 	updating_controls = false;
-	int mode = Options.AsInt(_T("Color Picker Mode"));
+	int mode = OPT_GET("Tool/Colour Picker/Mode")->GetInt();
 	if (mode < 0 || mode > 4) mode = 3; // HSL default
 	colorspace_choice->SetSelection(mode);
 	SetColor(initial_color);
-	recent_box->LoadFromString(Options.AsText(_T("Color Picker Recent")));
+	recent_box->LoadFromString(lagi_wxString(OPT_GET("Tool/Colour Picker/Recent")->GetString()));
 
 	// The mouse event handler for the Dropper control must be manually assigned
 	// The EVT_MOUSE_EVENTS macro can't take a control id
@@ -821,8 +823,7 @@ void DialogColorPicker::SetColor(wxColour new_color)
 wxColour DialogColorPicker::GetColor()
 {
 	recent_box->AddColor(cur_color);
-	Options.SetText(_T("Color Picker Recent"), recent_box->StoreToString());
-	Options.Save();
+	OPT_SET("Tool/Colour Picker/Recent")->SetString(STD_STR(recent_box->StoreToString()));
 	return cur_color;
 }
 
@@ -1326,7 +1327,7 @@ void DialogColorPicker::OnChangeMode(wxCommandEvent &evt)
 {
 	if (!updating_controls)
 		spectrum_dirty = true;
-	Options.SetInt(_T("Color Picker Mode"), colorspace_choice->GetSelection());
+	OPT_SET("Tool/Colour Picker/Mode")->SetInt(colorspace_choice->GetSelection());
 	UpdateSpectrumDisplay();
 }
 
diff --git a/aegisub/src/dialog_detached_video.cpp b/aegisub/src/dialog_detached_video.cpp
index b632208b2..a617ceece 100644
--- a/aegisub/src/dialog_detached_video.cpp
+++ b/aegisub/src/dialog_detached_video.cpp
@@ -44,6 +44,7 @@
 
 #include "dialog_detached_video.h"
 #include "frame_main.h"
+#include "main.h"
 #include "options.h"
 #include "video_box.h"
 #include "video_context.h"
@@ -63,10 +64,10 @@ DialogDetachedVideo::DialogDetachedVideo(FrameMain *par, const wxSize &initialDi
 	parent = par;
 
 	// Set up window
-	int x = Options.AsInt(_T("Detached video last x"));
-	int y = Options.AsInt(_T("Detached video last y"));
+	int x = OPT_GET("Video/Detached/Last/X")->GetInt();
+	int y = OPT_GET("Video/Detached/Last/Y")->GetInt();
 	if (x != -1 && y != -1) SetPosition(wxPoint(x,y));
-	if (Options.AsBool(_T("Detached video maximized"))) Maximize();
+	if (OPT_GET("Video/Detached/Maximized")->GetBool()) Maximize();
 
 	// Set obscure stuff
 	SetExtraStyle((GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS) | wxWS_EX_PROCESS_UI_UPDATES);
@@ -127,10 +128,9 @@ DialogDetachedVideo::DialogDetachedVideo(FrameMain *par, const wxSize &initialDi
 	// Update
 	parent->SetDisplayMode(0, -1);
 	GetPosition(&x, &y);
-	Options.SetInt(_T("Detached video last x"), x);
-	Options.SetInt(_T("Detached video last y"), y);
-	Options.SetBool(_T("Detached video"),true);
-	Options.Save();
+	OPT_SET("Video/Detached/Last/X")->SetInt(x);
+	OPT_SET("Video/Detached/Last/Y")->SetInt(y);
+	OPT_SET("Video/Detached/Enabled")->SetBool(true);
 
 	// Copy the main accelerator table to this dialog
 	wxAcceleratorTable *table = par->GetAcceleratorTable();
@@ -139,8 +139,7 @@ DialogDetachedVideo::DialogDetachedVideo(FrameMain *par, const wxSize &initialDi
 
 /// @brief Destructor
 DialogDetachedVideo::~DialogDetachedVideo() {
-	Options.SetBool(_T("Detached video maximized"),IsMaximized());
-	Options.Save();
+	OPT_SET("Video/Detached/Maximized")->SetBool(IsMaximized());
 }
 
 // Event table
@@ -154,7 +153,7 @@ END_EVENT_TABLE()
 /// @param event UNUSED
 void DialogDetachedVideo::OnClose(wxCloseEvent &WXUNUSED(event)) {
 	FrameMain *par = parent;
-	Options.SetBool(_T("Detached video"),false);
+	OPT_SET("Video/Detached/Enabled")->SetBool(false);
 	Destroy();
 	par->detachedVideo = NULL;
 	par->SetDisplayMode(1,-1);
@@ -164,8 +163,8 @@ void DialogDetachedVideo::OnClose(wxCloseEvent &WXUNUSED(event)) {
 /// @param event 
 void DialogDetachedVideo::OnMove(wxMoveEvent &event) {
 	wxPoint pos = event.GetPosition();
-	Options.SetInt(_T("Detached video last x"),pos.x);
-	Options.SetInt(_T("Detached video last y"),pos.y);
+	OPT_SET("Video/Detached/Last/X")->SetInt(pos.x);
+	OPT_SET("Video/Detached/Last/Y")->SetInt(pos.y);
 }
 
 /// @brief Minimize event handler
diff --git a/aegisub/src/dialog_dummy_video.cpp b/aegisub/src/dialog_dummy_video.cpp
index 37b002da1..401f767a0 100644
--- a/aegisub/src/dialog_dummy_video.cpp
+++ b/aegisub/src/dialog_dummy_video.cpp
@@ -44,10 +44,12 @@
 #include <wx/statline.h>
 #endif
 
+#include "compat.h"
 #include "dialog_dummy_video.h"
 #include "help_button.h"
+#include "main.h"
 #include "options.h"
-
+#include "utils.h"
 
 /// DOCME
 struct ResolutionShortcut {
@@ -123,12 +125,12 @@ bool DialogDummyVideo::CreateDummyVideo(wxWindow *parent, wxString &out_filename
 		pattern = dlg.pattern->GetValue();
 
 		// Write to options
-		Options.SetFloat(_T("Video Dummy Last FPS"), fps);
-		Options.SetInt(_T("Video Dummy Last Width"), width);
-		Options.SetInt(_T("Video Dummy Last Height"), height);
-		Options.SetInt(_T("Video Dummy Last Length"), length);
-		Options.SetColour(_T("Video Dummy Last Colour"), colour);
-		Options.SetBool(_T("Video Dummy Pattern"), pattern);
+		OPT_SET("Video/Dummy/FPS")->SetDouble(fps);
+		OPT_SET("Video/Dummy/Last/Width")->SetInt(width);
+		OPT_SET("Video/Dummy/Last/Height")->SetInt(height);
+		OPT_SET("Video/Dummy/Last/Length")->SetInt(length);
+		OPT_SET("Colour/Video Dummy/Last Colour")->SetColour(STD_STR(colour.GetAsString(wxC2S_CSS_SYNTAX)));
+		OPT_SET("Video/Dummy/Pattern")->SetBool(pattern);
 
 		out_filename = DummyVideoProvider::MakeFilename(fps, length, width, height, colour, pattern);
 		return true;
@@ -150,10 +152,10 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
 	resolution_shortcuts = new wxComboBox(this, Dummy_Video_Resolution_Shortcut, _T(""), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY);
 	width = new wxTextCtrl(this, -1);
 	height = new wxTextCtrl(this, -1);
-	colour = new ColourButton(this, -1, wxSize(30, 17), Options.AsColour(_T("Video Dummy Last Colour")));
+	colour = new ColourButton(this, -1, wxSize(30, 17), lagi_wxColour(OPT_GET("Colour/Video Dummy/Last Colour")->GetColour()));
 	pattern = new wxCheckBox(this, -1, _("Checkerboard pattern"));
 	//fps = new wxComboBox(this, Dummy_Video_FPS, Options.AsText(_T("Video Dummy Last FPS")), wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_DROPDOWN);
-	fps = new wxTextCtrl(this, Dummy_Video_FPS, Options.AsText(_T("Video Dummy Last FPS")));
+	fps = new wxTextCtrl(this, Dummy_Video_FPS, wxString::Format("%f", OPT_GET("Video/Dummy/FPS")->GetDouble()));
 	length = new wxSpinCtrl(this, Dummy_Video_Length);
 	length_display = new wxStaticText(this, -1, _T(""));
 
@@ -195,24 +197,24 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
 
 	// Initialise controls
 	int lastwidth, lastheight, lastres = 0;
-	lastwidth = Options.AsInt(_T("Video Dummy Last Width"));
-	lastheight = Options.AsInt(_T("Video Dummy Last Height"));
+	lastwidth = OPT_GET("Video/Dummy/Last/Width")->GetInt();
+	lastheight = OPT_GET("Video/Dummy/Last/Height")->GetInt();
 	for (ResolutionShortcut *res = resolutions; res->name; ++res) {
 		resolution_shortcuts->Append(res->name);
 		if (res->width == lastwidth && res->height == lastheight)
 			resolution_shortcuts->SetSelection(lastres);
 		lastres++;
 	}
-	pattern->SetValue(Options.AsBool(_T("Video Dummy Pattern")));
+	pattern->SetValue(OPT_GET("Video/Dummy/Pattern")->GetBool());
 	/*fps->Append(_T("23.976"));
 	fps->Append(_T("29.97"));
 	fps->Append(_T("24"));
 	fps->Append(_T("25"));
 	fps->Append(_T("30"));*/
-	width->ChangeValue(Options.AsText(_T("Video Dummy Last Width")));
-	height->ChangeValue(Options.AsText(_T("Video Dummy Last Height")));
+	width->ChangeValue(AegiIntegerToString(OPT_GET("Video/Dummy/Last/Width")->GetInt()));
+	height->ChangeValue(AegiIntegerToString(OPT_GET("Video/Dummy/Last/Height")->GetInt()));
 	length->SetRange(0, 0x10000000);
-	length->SetValue(Options.AsInt(_T("Video Dummy Last Length")));
+	length->SetValue(OPT_GET("Video/Dummy/Last/Length")->GetInt());
 	UpdateLengthDisplay();
 
 	// Layout
diff --git a/aegisub/src/dialog_fonts_collector.cpp b/aegisub/src/dialog_fonts_collector.cpp
index 4726095c4..b1b239cf4 100644
--- a/aegisub/src/dialog_fonts_collector.cpp
+++ b/aegisub/src/dialog_fonts_collector.cpp
@@ -51,11 +51,13 @@
 #include "ass_file.h"
 #include "ass_override.h"
 #include "ass_style.h"
+#include "compat.h"
 #include "dialog_fonts_collector.h"
 #include "font_file_lister.h"
 #include "frame_main.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "scintilla_text_ctrl.h"
 #include "subs_grid.h"
@@ -96,7 +98,7 @@ DialogFontsCollector::DialogFontsCollector(wxWindow *parent)
 	main = (FrameMain*) parent;
 
 	// Destination box
-	wxString dest = Options.AsText(_T("Fonts Collector Destination"));
+	wxString dest = lagi_wxString(OPT_GET("Path/Fonts Collector Destination")->GetString());
 	if (dest == _T("?script")) {
 		wxFileName filename(AssFile::top->filename);
 		dest = filename.GetPath();
@@ -122,7 +124,7 @@ DialogFontsCollector::DialogFontsCollector(wxWindow *parent)
 	choices.Add(_("DEBUG: Verify all fonts in system"));
 #endif
 	CollectAction = new wxRadioBox(this,RADIO_BOX,_T("Action"),wxDefaultPosition,wxDefaultSize,choices,1);
-	size_t lastAction = Options.AsInt(_T("Fonts Collector Action"));
+	size_t lastAction = OPT_GET("Tool/Fonts Collector/Action")->GetInt();
 	if (lastAction >= choices.GetCount()) lastAction = 0;
 	CollectAction->SetSelection(lastAction);
 
@@ -241,10 +243,9 @@ void DialogFontsCollector::OnStart(wxCommandEvent &event) {
 		if (filename.GetPath() == dest) {
 			dest = _T("?script");
 		}
-		Options.SetText(_T("Fonts Collector Destination"),dest);
+		OPT_SET("Path/Fonts Collector Destination")->SetString(STD_STR(dest));
 	}
-	Options.SetInt(_T("Fonts Collector Action"),action);
-	Options.Save();
+	OPT_SET("Tool/Fonts Collector/Action")->SetInt(action);
 
 	// Set buttons
 	StartButton->Enable(false);
diff --git a/aegisub/src/dialog_kara_timing_copy.cpp b/aegisub/src/dialog_kara_timing_copy.cpp
index b87648e53..6947ab8ce 100644
--- a/aegisub/src/dialog_kara_timing_copy.cpp
+++ b/aegisub/src/dialog_kara_timing_copy.cpp
@@ -54,6 +54,7 @@
 #include "dialog_kara_timing_copy.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "subs_grid.h"
 #include "utils.h"
 #include "validators.h"
@@ -852,7 +853,7 @@ DialogKanjiTimer::DialogKanjiTimer(wxWindow *parent, SubtitlesGrid *_grid)
 
 	//Checkbox
 	Interpolate = new wxCheckBox(this,-1,_("Attempt to interpolate kanji."),wxDefaultPosition,wxDefaultSize,wxALIGN_LEFT);
-	Interpolate->SetValue(Options.AsBool(_T("kanji timer interpolation")));
+	Interpolate->SetValue(OPT_GET("Tool/Kanji Timer/Interpolation")->GetBool());
 
 	SourceStyle=new wxComboBox(this,-1,_T(""),wxDefaultPosition,wxSize(160,-1),
 								 subs->GetStyles(),wxCB_READONLY,wxDefaultValidator,_("Source Style"));
@@ -934,8 +935,7 @@ END_EVENT_TABLE()
 /// @param event 
 ///
 void DialogKanjiTimer::OnClose(wxCommandEvent &event) {
-	Options.SetBool(_T("kanji timer interpolation"),Interpolate->IsChecked());
-	Options.Save();
+	OPT_SET("Tool/Kanji Timer/Interpolation")->SetBool(Interpolate->IsChecked());
 	bool modified = !LinesToChange.empty();
 	
 	while(LinesToChange.empty()==false) {
diff --git a/aegisub/src/dialog_options.cpp b/aegisub/src/dialog_options.cpp
deleted file mode 100644
index 93fbe493c..000000000
--- a/aegisub/src/dialog_options.cpp
+++ /dev/null
@@ -1,1229 +0,0 @@
-// Copyright (c) 2006, Rodrigo Braz Monteiro
-// 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/
-//
-// $Id$
-
-/// @file dialog_options.cpp
-/// @brief Main configuration dialogue box, including all pages
-/// @ingroup configuration_ui
-///
-
-
-///////////
-// Headers
-#include "config.h"
-
-#ifndef AGI_PRE
-#include <wx/filefn.h>
-#include <wx/spinctrl.h>
-#include <wx/stdpaths.h>
-#if wxUSE_TREEBOOK
-#include <wx/treebook.h>
-#endif
-#endif
-
-#include "audio_box.h"
-#include "audio_display.h"
-#include "browse_button.h"
-#include "colour_button.h"
-#include "dialog_options.h"
-#include "frame_main.h"
-#include "help_button.h"
-#include "libresrc/libresrc.h"
-#include "options.h"
-#include "standard_paths.h"
-#include "subs_edit_box.h"
-#include "subs_grid.h"
-#include "subtitles_provider_manager.h"
-#include "tooltip_manager.h"
-#include "utils.h"
-#include "validators.h"
-#include "video_box.h"
-#include "video_context.h"
-#include "video_provider_manager.h"
-#include "video_slider.h"
-
-
-#if wxUSE_TREEBOOK
-/// DOCME
-#define AddSubPage(page,text,select) AddPage(page,wxString::Format(_T("\t%s"),text),select)
-#endif
-
-///////
-// IDs
-enum {
-
-	 /// DOCME
-	 BUTTON_DEFAULTS = 2500,
-
-	 /// DOCME
-	 HOTKEY_LIST,
-
-	 /// DOCME
-	 BUTTON_HOTKEY_SET,
-
-	 /// DOCME
-	 BUTTON_HOTKEY_CLEAR,
-
-	 /// DOCME
-	 BUTTON_HOTKEY_DEFAULT,
-
-	 /// DOCME
-	 BUTTON_HOTKEY_DEFAULT_ALL
-};
-
-
-
-/// @brief Constructor 
-/// @param parent 
-///
-DialogOptions::DialogOptions(wxWindow *parent)
-: wxDialog(parent, -1, _("Options"), wxDefaultPosition, wxDefaultSize)
-{
-	// Set icon
-	SetIcon(BitmapToIcon(GETIMAGE(options_button_24)));
-
-	// Create book
-	book = new wxTreebook(this,-1,wxDefaultPosition,wxDefaultSize);
-	needsRestart = false;
-
-	// Panels
-	wxPanel *generalPage = new wxPanel(book,-1);
-	wxPanel *filePage = new wxPanel(book,-1);
-	wxPanel *gridPage = new wxPanel(book,-1);
-	wxPanel *editPage = new wxPanel(book,-1);
-	wxPanel *videoPage = new wxPanel(book,-1);
-	wxPanel *audioPage = new wxPanel(book,-1);
-	wxPanel *audioAdvPage = new wxPanel(book,-1);
-	wxPanel *displayPage = new wxPanel(book,-1);
-	wxPanel *autoPage = new wxPanel(book,-1);
-	wxPanel *hotkeysPage = new wxPanel(book,-1);
-	BrowseButton *browse;
-
-	// General page
-	{
-		wxSizer *genMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *genSizer1 = new wxStaticBoxSizer(wxHORIZONTAL,generalPage,_("Startup"));
-		wxFlexGridSizer *genSizer4 = new wxFlexGridSizer(2,5,5);
-
-		AddCheckBox(generalPage,genSizer4,_("Show Splash Screen"),_T("Show splash"));
-#ifdef __WXMSW__
-		AddCheckBox(generalPage,genSizer4,_("Auto Check for Updates"),_T("Auto check for updates"));
-#endif
-		genSizer4->AddGrowableCol(0,1);
-
-		genSizer1->Add(genSizer4,1,wxEXPAND|wxALL,5);
-		wxSizer *genSizer2 = new wxStaticBoxSizer(wxVERTICAL,generalPage,_("Limits for levels and recent files"));
-		wxFlexGridSizer *genSizer3 = new wxFlexGridSizer(2,5,5);
-		wxString options[8] = { _T("Undo levels"), _T("Recent timecodes max"), _T("Recent keyframes max"), _T("Recent sub max"), _T("Recent vid max"), _T("Recent aud max"), _T("Recent find max"), _T("Recent replace max") };
-		wxString labels[8] = { _("Maximum undo levels"), _("Maximum recent timecode files"), _("Maximum recent keyframe files"), _("Maximum recent subtitle files"), _("Maximum recent video files"), _("Maximum recent audio files"), _("Maximum recent find strings"), _("Maximum recent replace strings") };
-		for (int i=0;i<8;i++) {
-			wxSpinCtrl *spin = new wxSpinCtrl(generalPage,-1,_T(""),wxDefaultPosition,wxSize(70,-1),wxSP_ARROW_KEYS,0,32,0);
-			Bind(spin,options[i]);
-			genSizer3->Add(new wxStaticText(generalPage,-1,labels[i] + _T(": ")),1,wxALIGN_CENTRE_VERTICAL);
-			genSizer3->Add(spin,0);
-		}
-		genSizer3->AddGrowableCol(0,1);
-		genSizer2->Add(genSizer3,1,wxEXPAND | wxALL,5);
-		genMainSizer->Add(genSizer1,0,wxEXPAND | wxBOTTOM,5);
-		genMainSizer->Add(genSizer2,0,wxEXPAND,0);
-		genMainSizer->AddStretchSpacer(1);
-		generalPage->SetSizerAndFit(genMainSizer);
-	}
-
-	// File save/load page
-	{
-		// Sizers
-		wxSizer *fileMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *fileSizer1 = new wxStaticBoxSizer(wxVERTICAL,filePage,_("Auto-save"));
-		wxSizer *fileSizer2 = new wxBoxSizer(wxHORIZONTAL);
-		wxSizer *fileSizer3 = new wxStaticBoxSizer(wxHORIZONTAL,filePage,_("File paths"));
-		wxFlexGridSizer *fileSizer4 = new wxFlexGridSizer(3,5,5);
-		wxSizer *fileSizer5 = new wxStaticBoxSizer(wxHORIZONTAL,filePage,_("Miscellanea"));
-		wxFlexGridSizer *fileSizer6 = new wxFlexGridSizer(2,5,5);
-
-		// First static box
-		wxCheckBox *check = new wxCheckBox(filePage,-1,_("Auto-backup"));
-		Bind(check,_T("Auto backup"));
-		wxTextCtrl *edit = new wxTextCtrl(filePage,-1,_T(""),wxDefaultPosition,wxSize(50,-1),0,NumValidator(NULL,false));
-		Bind(edit,_T("Auto save every seconds"));
-		fileSizer2->Add(check,0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,10);
-		fileSizer2->AddStretchSpacer(1);
-		fileSizer2->Add(new wxStaticText(filePage,-1,_("Auto-save every")),0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,5);
-		fileSizer2->Add(edit,0,wxRIGHT,5);
-		fileSizer2->Add(new wxStaticText(filePage,-1,_("seconds.")),0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,0);
-
-		// Second static box
-		fileSizer4->Add(new wxStaticText(filePage,-1,_("Auto-save path:")),0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,5);
-		edit = new wxTextCtrl(filePage,-1);
-		Bind(edit,_T("Auto save path"));
-		fileSizer4->Add(edit,1,wxEXPAND);
-		browse = new BrowseButton(filePage,-1,_T(""),BROWSE_FOLDER);
-		browse->Bind(edit);
-		fileSizer4->Add(browse,0,wxEXPAND);
-
-		fileSizer4->Add(new wxStaticText(filePage,-1,_("Auto-backup path:")),0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,5);
-		edit = new wxTextCtrl(filePage,-1);
-		Bind(edit,_T("Auto backup path"));
-		fileSizer4->Add(edit,1,wxEXPAND);
-		browse = new BrowseButton(filePage,-1,_T(""),BROWSE_FOLDER);
-		browse->Bind(edit);
-		fileSizer4->Add(browse,0,wxEXPAND);
-		fileSizer4->AddGrowableCol(1,1);
-
-		// Third static box
-		fileSizer6->Add(new wxStaticText(filePage,-1,_("Auto-load linked files:")),0,wxRIGHT | wxALIGN_CENTRE_VERTICAL,5);
-		wxString choices[3] = { _("Never"), _("Always"), _("Ask") };
-		wxComboBox *combo = new wxComboBox(filePage,-1,_T(""),wxDefaultPosition,wxDefaultSize,3,choices,wxCB_DROPDOWN | wxCB_READONLY);
-		Bind(combo,_T("Autoload linked files"));
-		fileSizer6->Add(combo,1,wxEXPAND);
-		fileSizer6->AddGrowableCol(1,1);
-
-		// Sizers
-		fileSizer1->Add(fileSizer2,0,wxEXPAND | wxALL,5);
-		fileSizer3->Add(fileSizer4,1,wxEXPAND | wxALL,5);
-		fileSizer5->Add(fileSizer6,1,wxEXPAND | wxALL,5);
-		fileMainSizer->Add(fileSizer1,0,wxEXPAND | wxALL,0);
-		fileMainSizer->Add(fileSizer3,0,wxEXPAND | wxTOP,5);
-		fileMainSizer->Add(fileSizer5,0,wxEXPAND | wxTOP,5);
-		fileMainSizer->AddStretchSpacer(1);
-		filePage->SetSizerAndFit(fileMainSizer);
-	}
-
-	// Edit box page
-	{
-		// Sizers
-		wxSizer *editMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *editSizer1 = new wxStaticBoxSizer(wxVERTICAL,editPage,_("Options"));
-		wxSizer *editSizer6 = new wxBoxSizer(wxHORIZONTAL);
-		wxFlexGridSizer *editSizer2 = new wxFlexGridSizer(2,5,5);
-		wxSizer *editSizer3 = new wxStaticBoxSizer(wxVERTICAL,editPage,_("Style"));
-		wxFlexGridSizer *editSizer4 = new wxFlexGridSizer(2,2,2);
-		wxSizer *editSizer5 = new wxBoxSizer(wxHORIZONTAL);
-
-		// First static box
-		wxString labels1[4] = { _("Enable call tips"), _("Enable syntax highlighting"), _("Link commiting of times"), _("Overwrite-Insertion in time boxes") };
-		wxString options1[4] = { _T("Call Tips Enabled"), _T("Syntax Highlight Enabled"), _T("Link Time Boxes Commit"), _T("Insert Mode on Time Boxes") };
-		for (int i=0;i<4;i++) {
-			wxCheckBox *control = new wxCheckBox(editPage,-1,labels1[i]);
-			Bind(control,options1[i]);
-			editSizer2->Add(control,1,wxEXPAND,0);
-		}
-		//editSizer2->AddGrowableCol(0,1);
-		editSizer2->AddGrowableCol(1,1);
-		editSizer6->Add(new wxStaticText(editPage,-1,_("Path to dictionary files:")),0,wxALIGN_CENTER_VERTICAL|wxRIGHT,5);
-		wxTextCtrl *edit = new wxTextCtrl(editPage,-1,_T(""));
-		Bind(edit,_T("Dictionaries path"));
-		editSizer6->Add(edit,1,wxEXPAND|wxALIGN_CENTER_VERTICAL|wxRIGHT,5);
-		browse = new BrowseButton(editPage,-1,_T(""),BROWSE_FOLDER);
-		browse->Bind(edit);
-		editSizer6->Add(browse,0,wxEXPAND);
-
-		// Second static box
-		wxControl *control;
-		wxString labels2[10] = { _("Normal"), _("Brackets"), _("Slashes and Parentheses"), _("Tags"), _("Parameters") ,
-			                    _("Error"), _("Error Background"), _("Line Break"), _("Karaoke templates"), _("Modified Background") };
-		wxString options2[12] = { _T("Normal"), _T("Brackets"), _T("Slashes"), _T("Tags"), _T("Parameters") ,
-			                      _T("Error"), _T("Error Background"), _T("Line Break"), _T("Karaoke Template"), _T("Edit box need enter background"), _T("Edit Font Face"), _T("Edit Font Size") };
-		for (int i=0;i<10;i++) {
-			wxString caption = labels2[i]+_T(": ");
-			wxString option = options2[i];
-			if (i < 9) {
-				caption = _("Syntax highlighter - ") + caption;
-				option = _T("Syntax highlight ") + option;
-			}
-			control = new ColourButton(editPage,-1,wxSize(40,10));
-			Bind(control,option);
-			editSizer4->Add(new wxStaticText(editPage,-1,caption),0,wxALIGN_CENTER_VERTICAL|wxRIGHT,5);
-			editSizer4->Add(control,1,wxALIGN_RIGHT,0);
-		}
-		editSizer4->AddGrowableCol(1,1);
-
-		// Third sizer
-		editSizer5->Add(new wxStaticText(editPage,-1,_("Font: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		control = new wxTextCtrl(editPage,-1);
-		Bind(control,options2[10]);
-		browse = new BrowseButton(editPage,-1,_T(""),BROWSE_FONT);
-		browse->Bind((wxTextCtrl*)control);
-		editSizer5->Add(control,1,wxEXPAND | wxRIGHT,5);
-		control = new wxTextCtrl(editPage,-1,_T(""),wxDefaultPosition,wxSize(50,-1),0,NumValidator(NULL,false));;
-		Bind(control,options2[11]);
-		editSizer5->Add(control,0,wxEXPAND | wxRIGHT,5);
-		browse->Bind((wxTextCtrl*)control,1);
-		editSizer5->Add(browse,0,wxEXPAND);
-
-		// Sizers
-		editSizer1->Add(editSizer2,1,wxEXPAND | wxALL,5);
-		editSizer1->Add(editSizer6,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,5);
-		editSizer3->Add(editSizer4,1,wxEXPAND | wxALL,5);
-		editSizer3->Add(editSizer5,0,wxEXPAND | wxALL,5);
-		editMainSizer->Add(editSizer1,0,wxEXPAND | wxALL,0);
-		editMainSizer->Add(editSizer3,0,wxEXPAND | wxTOP,5);
-		editMainSizer->AddStretchSpacer(1);
-		editPage->SetSizerAndFit(editMainSizer);
-	}
-
-	// Grid page
-	{
-		// Sizers
-		wxSizer *gridMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *gridSizer1 = new wxStaticBoxSizer(wxVERTICAL,gridPage,_("Options"));
-		wxSizer *gridSizer2 = new wxStaticBoxSizer(wxVERTICAL,gridPage,_("Style"));
-		wxFlexGridSizer *gridSizer3 = new wxFlexGridSizer(2,2,2);
-		wxSizer *gridSizer4 = new wxBoxSizer(wxHORIZONTAL);
-		wxSizer *gridSizer5 = new wxBoxSizer(wxHORIZONTAL);
-
-		// First sizer
-		wxString labels1[2] = { _("Allow grid to take focus"), _("Highlight subtitles that are currently visible in video") };
-		wxString options1[2] = { _T("Grid allow focus"), _T("Highlight subs in frame") };
-		for (int i=0;i<2;i++) {
-			wxCheckBox *control = new wxCheckBox(gridPage,-1,labels1[i]);
-			Bind(control,options1[i]);
-			gridSizer1->Add(control,1,wxEXPAND | wxALL,5);
-		}
-
-		// Second sizer
-		wxControl *control;
-		wxString labels2[12] = { _("Standard foreground"), _("Standard background"), _("Selection foreground"), 
-			                     _("Selection background"), _("Comment background"), _("Selected comment background"),
-								 _("Collision foreground"), _("Line in frame background"), _("Header"),
-		                         _("Left Column"), _("Active Line Border"), _("Lines") };
-		wxString options2[12] = { _T("standard foreground"), _T("background"), _T("selection foreground"),
-		                         _T("selection background"), _T("comment background"), _T("selected comment background"),
-		                        _T("collision foreground") , _T("inframe background"), _T("header"),
-		                         _T("left column"), _T("active border"), _T("lines") };
-		for (int i=0;i<12;i++) {
-			wxString caption = labels2[i] + _T(": ");
-			wxString option = _T("Grid ") + options2[i];
-			control = new ColourButton(gridPage,-1,wxSize(40,10));
-			Bind(control,option);
-			gridSizer3->Add(new wxStaticText(gridPage,-1,caption),0,wxALIGN_CENTER_VERTICAL|wxRIGHT,5);
-			gridSizer3->Add(control,1,wxALIGN_CENTER,0);
-		}
-		gridSizer3->AddGrowableCol(0,1);
-
-		// Third sizer
-		gridSizer4->Add(new wxStaticText(gridPage,-1,_("Font: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		control = new wxTextCtrl(gridPage,-1);
-		Bind(control,_T("Grid font face"));
-		browse = new BrowseButton(gridPage,-1,_T(""),BROWSE_FONT);
-		browse->Bind((wxTextCtrl*)control);
-		gridSizer4->Add(control,1,wxEXPAND | wxRIGHT,5);
-		control = new wxTextCtrl(gridPage,-1,_T(""),wxDefaultPosition,wxSize(50,-1),0,NumValidator(NULL,false));;
-		Bind(control,_T("Grid font size"));
-		browse->Bind((wxTextCtrl*)control,1);
-		gridSizer4->Add(control,0,wxEXPAND | wxRIGHT,5);
-		gridSizer4->Add(browse,0,wxEXPAND);
-
-		// Fourth sizer
-		gridSizer5->Add(new wxStaticText(gridPage,-1,_("Replace override tags with: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		control = new wxTextCtrl(gridPage,-1);
-		Bind(control,_T("Grid hide overrides char"));
-		gridSizer5->Add(control,1,wxEXPAND | wxRIGHT,5);
-
-		// Sizers
-		gridSizer2->Add(gridSizer3,1,wxEXPAND | wxALL,5);
-		gridSizer2->Add(gridSizer4,0,wxEXPAND | wxALL,5);
-		gridSizer2->Add(gridSizer5,0,wxEXPAND | wxALL,5);
-		gridMainSizer->Add(gridSizer1,0,wxEXPAND | wxALL,0);
-		gridMainSizer->Add(gridSizer2,0,wxEXPAND | wxTOP,5);
-		gridMainSizer->AddStretchSpacer(1);
-		gridPage->SetSizerAndFit(gridMainSizer);
-	}
-
-	// Video page
-	{
-		// Sizers
-		wxSizer *videoMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *videoSizer1 = new wxStaticBoxSizer(wxVERTICAL,videoPage,_("Options"));
-		wxSizer *videoSizer2 = new wxStaticBoxSizer(wxVERTICAL,videoPage,_("Advanced - EXPERT USERS ONLY"));
-		wxFlexGridSizer *videoSizer3 = new wxFlexGridSizer(2,5,5);
-		wxFlexGridSizer *videoSizer4 = new wxFlexGridSizer(2,5,5);
-		wxControl *control;
-
-		// First sizer
-		videoSizer3->Add(new wxStaticText(videoPage,-1,_("Match video resolution on open: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		wxString choices1[3] = { _("Never"), _("Ask"), _("Always") };
-		control = new wxComboBox(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,3,choices1,wxCB_READONLY | wxCB_DROPDOWN);
-		Bind(control,_T("Video check script res"));
-		videoSizer3->Add(control,1,wxEXPAND);
-		videoSizer3->Add(new wxStaticText(videoPage,-1,_("Default Zoom: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		wxArrayString choices2;
-		for (int i=1;i<=16;i++) {
-			wxString toAdd = wxString::Format(_T("%i"),int(i*12.5));
-			if (i%2) toAdd += _T(".5");
-			toAdd += _T("%");
-			choices2.Add(toAdd);
-		}
-		control = new wxComboBox(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,choices2,wxCB_READONLY | wxCB_DROPDOWN);
-		Bind(control,_T("Video Default Zoom"));
-		videoSizer3->Add(control,1,wxEXPAND);
-		videoSizer3->Add(new wxStaticText(videoPage,-1,_("Fast jump step in frames: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		control = new wxTextCtrl(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,0,NumValidator());
-		Bind(control,_T("Video fast jump step"));
-		videoSizer3->Add(control,1,wxEXPAND);
-		videoSizer3->Add(new wxStaticText(videoPage,-1,_("Screenshot save path: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		wxString choices3[3] = { _T("?video"), _T("?script"), _T(".") };
-		//control = new wxTextCtrl(videoPage,-1);
-		control = new wxComboBox(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,3,choices3,wxCB_DROPDOWN);
-		Bind(control,_T("Video screenshot path"));
-		videoSizer3->Add(control,1,wxEXPAND);
-		control = new wxCheckBox(videoPage,-1,_("Show keyframes in slider"));
-		Bind(control,_T("Show keyframes on video slider"));
-		videoSizer3->Add(control,0,wxEXPAND);
-		control = new wxCheckBox(videoPage,-1,_("Always show visual tools"));
-		Bind(control,_T("Always show visual tools"));
-		videoSizer3->Add(control,0,wxEXPAND);
-		videoSizer3->AddGrowableCol(1,1);
-
-		// Second sizer
-		videoSizer4->Add(new wxStaticText(videoPage,-1,_("Video provider: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		wxArrayString choices4 = VideoProviderFactoryManager::GetFactoryList();
-		control = new wxComboBox(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,choices4,wxCB_DROPDOWN | wxCB_READONLY);
-		Bind(control,_T("Video provider"),1);
-		videoSizer4->Add(control,1,wxEXPAND);
-		videoSizer4->Add(new wxStaticText(videoPage,-1,_("Subtitles provider: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		wxArrayString choices5 = SubtitlesProviderFactoryManager::GetFactoryList();
-		control = new wxComboBox(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,choices5,wxCB_DROPDOWN | wxCB_READONLY);
-		Bind(control,_T("Subtitles provider"),1);
-		videoSizer4->Add(control,1,wxEXPAND);
-#ifdef WIN32
-		videoSizer4->Add(new wxStaticText(videoPage,-1,_("Avisynth memory limit: ")),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-		control = new wxTextCtrl(videoPage,-1,_T(""),wxDefaultPosition,wxDefaultSize,0,NumValidator(NULL,false));
-		Bind(control,_T("Avisynth memorymax"));
-		videoSizer4->Add(control,1,wxEXPAND);
-		//control = new wxCheckBox(videoPage,-1,_("Threaded video"));
-		//Bind(control,_T("Threaded video"));
-		//videoSizer4->Add(control,1,wxEXPAND);
-		control = new wxCheckBox(videoPage,-1,_("Allow pre-2.56a Avisynth"));
-		Bind(control,_T("Allow Ancient Avisynth"));
-		videoSizer4->Add(control,1,wxEXPAND);
-		videoSizer4->AddGrowableCol(1,1);
-#endif
-
-		// Sizers
-		videoSizer1->Add(videoSizer3,1,wxEXPAND | wxALL,5);
-		videoSizer2->Add(new wxStaticText(videoPage,-1,_("WARNING: Changing these settings might result in bugs,\ncrashes, glitches and/or movax.\nDon't touch these unless you know what you're doing.")),0,wxEXPAND | wxALL,5);
-		videoSizer2->Add(videoSizer4,1,wxEXPAND | wxALL,5);
-		videoMainSizer->Add(videoSizer1,0,wxEXPAND | wxALL,0);
-		videoMainSizer->Add(videoSizer2,0,wxEXPAND | wxTOP,5);
-		videoMainSizer->AddStretchSpacer(1);
-		videoPage->SetSizerAndFit(videoMainSizer);
-	}
-
-	// Audio page
-	{
-		// Sizers
-		wxSizer *audioMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *audioSizer1 = new wxStaticBoxSizer(wxVERTICAL,audioPage,_("Options"));
-		wxFlexGridSizer *audioSizer3 = new wxFlexGridSizer(2,5,5);
-		wxFlexGridSizer *audioSizer4 = new wxFlexGridSizer(2,5,5);
-
-		// First sizer
-		AddCheckBox(audioPage,audioSizer3,_("Grab times from line upon selection"),_T("Audio grab times on select"));
-		AddCheckBox(audioPage,audioSizer3,_("Default mouse wheel to zoom"),_T("Audio Wheel Default To Zoom"));
-		AddCheckBox(audioPage,audioSizer3,_("Lock scroll on Cursor"),_T("Audio lock scroll on cursor"));
-		AddCheckBox(audioPage,audioSizer3,_("Snap to keyframes"),_T("Audio snap to keyframes"));
-		AddCheckBox(audioPage,audioSizer3,_("Snap to adjacent lines"),_T("Audio snap to other lines"));
-		AddCheckBox(audioPage,audioSizer3,_("Auto-focus on mouse over"),_T("Audio Autofocus"));
-		AddCheckBox(audioPage,audioSizer3,_("Play audio when stepping in video"),_T("Audio Plays When Stepping Video"));
-		audioSizer3->AddGrowableCol(1,1);
-
-		// Second sizer
-		wxString choices1[3] = { _("Don't show"), _("Show previous"), _("Show all") };
-		AddTextControl(audioPage,audioSizer4,_("Default timing length"),_T("Timing Default Duration"),TEXT_TYPE_NUMBER);
-		AddTextControl(audioPage,audioSizer4,_("Default lead-in length"),_T("Audio lead in"),TEXT_TYPE_NUMBER);
-		AddTextControl(audioPage,audioSizer4,_("Default lead-out length"),_T("Audio lead out"),TEXT_TYPE_NUMBER);
-		AddComboControl(audioPage,audioSizer4,_("Show inactive lines"),_T("Audio Inactive Lines Display Mode"),wxArrayString(3,choices1));
-		AddTextControl(audioPage,audioSizer4,_("Start-marker drag sensitivity"),_T("Audio Start Drag Sensitivity"),TEXT_TYPE_NUMBER);
-		audioSizer4->AddGrowableCol(0,1);
-
-		// Sizers
-		audioSizer1->Add(audioSizer3,0,wxEXPAND | wxALL,5);
-		audioSizer1->Add(audioSizer4,1,wxEXPAND | wxALL,5);
-		audioMainSizer->Add(audioSizer1,0,wxEXPAND | wxALL,0);
-		audioMainSizer->AddStretchSpacer(1);
-		audioPage->SetSizerAndFit(audioMainSizer);
-	}
-
-	// Audio display page
-	{
-		// Sizers
-		wxSizer *displayMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *displaySizer1 = new wxStaticBoxSizer(wxVERTICAL,displayPage,_("Options"));
-		wxSizer *displaySizer2 = new wxStaticBoxSizer(wxVERTICAL,displayPage,_("Style"));
-		wxFlexGridSizer *displaySizer3 = new wxFlexGridSizer(2,2,2);
-		wxFlexGridSizer *displaySizer4 = new wxFlexGridSizer(2,2,2);
-
-		// First sizer
-		wxString labels1[6] = { _("Draw secondary lines"), _("Draw selection background"), _("Draw timeline"),
-								_("Draw cursor time"), _("Draw keyframes"), _("Draw video position") };
-		wxString options1[6] = { _T("Draw Secondary Lines"), _T("Draw Selection Background") , _T("Draw Timeline"),
-								_T("Draw Cursor Time"), _T("Draw keyframes"), _T("Draw video position")};
-		for (int i=0;i<6;i++) {
-			wxCheckBox *control = new wxCheckBox(displayPage,-1,labels1[i]);
-			Bind(control,_T("Audio ") + options1[i]);
-			displaySizer3->Add(control,1,wxEXPAND | wxALL,5);
-		}
-
-		// Second sizer
-		wxControl *control;
-		wxString labels2[14] = { _("Play cursor"), _("Background"), _("Selection background"), 
-		                         _("Selection background - modified"), _("Seconds boundary"), _("Waveform"), 
-		                         _("Waveform - selection"), _("Waveform - modified"), _("Waveform - inactive"), 
-		                         _("Boundary - start"), _("Boundary - end"), _("Boundary - inactive"), 
-		                         _("Syllable text"), _("Syllable boundary") };
-		wxString options2[14] = { _T("Play cursor"), _T("Background"), _T("Selection background"), 
-		                          _T("Selection background modified"), _T("Seconds boundaries"), _T("Waveform"), 
-		                          _T("Waveform selected"), _T("Waveform Modified"), _T("Waveform Inactive"), 
-		                          _T("Line boundary start"), _T("Line boundary end"), _T("Line boundary inactive line"), 
-		                          _T("Syllable text"), _T("Syllable boundaries") };
-		for (int i=0;i<14;i++) {
-			wxString caption = labels2[i] + _T(": ");
-			wxString option = _T("Audio ") + options2[i];
-			control = new ColourButton(displayPage,-1,wxSize(40,10));
-			Bind(control,option);
-			displaySizer4->Add(new wxStaticText(displayPage,-1,caption),0,wxALIGN_CENTER_VERTICAL|wxRIGHT,5);
-			displaySizer4->Add(control,1,wxALIGN_CENTER,0);
-		}
-		displaySizer4->AddGrowableCol(0,1);
-
-		// Sizers
-		displaySizer1->Add(displaySizer3,1,wxEXPAND | wxALL,5);
-		displaySizer2->Add(displaySizer4,1,wxEXPAND | wxALL,5);
-		displayMainSizer->Add(displaySizer1,0,wxEXPAND | wxALL,0);
-		displayMainSizer->Add(displaySizer2,0,wxEXPAND | wxTOP,5);
-		displayMainSizer->AddStretchSpacer(1);
-		displayPage->SetSizerAndFit(displayMainSizer);
-	}
-
-	// Audio advanced page
-	{
-		// Sizers
-		wxFlexGridSizer *audioAdvSizer1 = new wxFlexGridSizer(2,5,5);
-		wxSizer *audioAdvSizer2 = new wxStaticBoxSizer(wxVERTICAL,audioAdvPage,_("Advanced - EXPERT USERS ONLY"));
-		wxSizer *audioAdvSizer3 = new wxBoxSizer(wxVERTICAL);
-
-		// Controls
-		wxString choices2[3] = { _("None (NOT RECOMMENDED)"), _("RAM"), _("Hard Disk") };
-#ifdef WIN32
-		wxString choices3[3] = { _T("ConvertToMono"), _T("GetLeftChannel"), _T("GetRightChannel") };
-#endif
-		
-		AddComboControl(audioAdvPage,audioAdvSizer1,_("Audio provider"),_T("Audio Provider"),AudioProviderFactoryManager::GetFactoryList(),true,1);
-		AddComboControl(audioAdvPage,audioAdvSizer1,_("Audio player"),_T("Audio Player"),AudioPlayerFactoryManager::GetFactoryList(),true,1);
-		AddComboControl(audioAdvPage,audioAdvSizer1,_("Cache type"),_T("Audio Cache"),wxArrayString(3,choices2),true);
-#ifdef WIN32
-		AddComboControl(audioAdvPage,audioAdvSizer1,_("Avisynth down-mixer"),_T("Audio Downmixer"),wxArrayString(3,choices3),false);
-#endif
-		AddTextControl(audioAdvPage,audioAdvSizer1,_("HD cache path"),_T("Audio HD Cache Location"),TEXT_TYPE_FOLDER);
-		AddTextControl(audioAdvPage,audioAdvSizer1,_("HD cache name"),_T("Audio HD CAche Name"));
-		AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cutoff"),_T("Audio spectrum cutoff"),TEXT_TYPE_NUMBER);
-		wxString spectrum_quality_choices[] = { _("0 - Regular quality"), _("1 - Better quality"), _("2 - High quality"), _("3 - Insane quality") };
-		AddComboControl(audioAdvPage,audioAdvSizer1,_("Spectrum quality"),_T("Audio spectrum quality"),wxArrayString(4,spectrum_quality_choices));
-		AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cache memory max (MB)"),_T("Audio spectrum memory max"),TEXT_TYPE_NUMBER);
-		audioAdvSizer1->AddGrowableCol(0,1);
-
-		// Main sizer
-		audioAdvSizer2->Add(new wxStaticText(audioAdvPage,-1,_("WARNING: Changing these settings might result in bugs,\ncrashes, glitches and/or movax.\nDon't touch these unless you know what you're doing.")),0,wxEXPAND | wxALL,5);
-		audioAdvSizer2->Add(audioAdvSizer1,1,wxEXPAND | wxALL,5);
-		audioAdvSizer3->Add(audioAdvSizer2,0,wxEXPAND);
-		audioAdvSizer3->AddStretchSpacer(1);
-		audioAdvPage->SetSizerAndFit(audioAdvSizer3);
-	}
-
-	// Automation page
-	{
-		// Sizers
-		wxSizer *autoMainSizer = new wxBoxSizer(wxVERTICAL);
-		wxSizer *autoSizer1 = new wxStaticBoxSizer(wxVERTICAL,autoPage,_("Options"));
-		wxFlexGridSizer *autoSizer2 = new wxFlexGridSizer(2,5,5);
-
-		// First sizer
-		AddTextControl(autoPage,autoSizer2,_("Base path"),_T("Automation Base Path"));
-		AddTextControl(autoPage,autoSizer2,_("Include path"),_T("Automation Include Path"));
-		AddTextControl(autoPage,autoSizer2,_("Auto-load path"),_T("Automation Autoload Path"));
-		wxString trace_choices[] = { _("0: Fatal"), _("1: Error"), _("2: Warning"), _("3: Hint"), _("4: Debug"), _("5: Trace") };
-		wxString prio_choices[] = { _("Normal"), _("Below Normal (recommended)"), _("Lowest") };
-		wxString reload_choices[] = { _("No scripts"), _("Subtitle-local scripts"), _("Global autoload scripts"), _("All scripts") };
-		AddComboControl(autoPage,autoSizer2,_("Trace level"),_T("Automation Trace Level"),wxArrayString(6,trace_choices));
-		AddComboControl(autoPage,autoSizer2,_("Thread priority"),_T("Automation Thread Priority"),wxArrayString(3,prio_choices));
-		AddComboControl(autoPage,autoSizer2,_("Autoreload on Export"),_T("Automation Autoreload Mode"),wxArrayString(4,reload_choices));
-		autoSizer2->AddGrowableCol(1,1);
-
-		// Sizers
-		autoSizer1->Add(autoSizer2,1,wxEXPAND | wxALL,5);
-		autoMainSizer->Add(autoSizer1,0,wxEXPAND | wxALL,0);
-		autoMainSizer->AddStretchSpacer(1);
-		autoPage->SetSizerAndFit(autoMainSizer);
-	}
-
-	// Hotkeys page
-	{
-		// Variables
-		hotkeysModified = false;
-		origKeys = Hotkeys.key;
-
-		// List of shortcuts
-		Shortcuts = new wxListView(hotkeysPage,HOTKEY_LIST,wxDefaultPosition,wxDefaultSize,wxLC_REPORT | wxLC_SINGLE_SEL);
-		Shortcuts->InsertColumn(0,_("Function"),wxLIST_FORMAT_LEFT,200);
-		Shortcuts->InsertColumn(1,_("Key"),wxLIST_FORMAT_LEFT,120);
-
-		// Populate list
-		std::map<wxString,HotkeyType>::iterator cur;
-		for (cur = Hotkeys.key.end();cur-- != Hotkeys.key.begin();) {
-			wxListItem item;
-			item.SetText(wxGetTranslation(cur->second.origName));
-			item.SetData(&cur->second);
-			int pos = Shortcuts->InsertItem(item);
-			Shortcuts->SetItem(pos,1,cur->second.GetText());
-		}
-
-		// Create buttons
-		wxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
-		buttons->Add(new wxButton(hotkeysPage,BUTTON_HOTKEY_SET,_("Set Hotkey...")),1,wxEXPAND|wxRIGHT,5);
-		buttons->Add(new wxButton(hotkeysPage,BUTTON_HOTKEY_CLEAR,_("Clear Hotkey")),0,wxEXPAND|wxRIGHT,5);
-		buttons->Add(new wxButton(hotkeysPage,BUTTON_HOTKEY_DEFAULT,_("Default")),0,wxEXPAND|wxRIGHT,5);
-		buttons->Add(new wxButton(hotkeysPage,BUTTON_HOTKEY_DEFAULT_ALL,_("Default All")),0,wxEXPAND|wxRIGHT,0);
-
-		// Main sizer
-		wxSizer *hotkeysSizer = new wxBoxSizer(wxVERTICAL);
-		hotkeysSizer->Add(Shortcuts,1,wxLEFT|wxRIGHT|wxTOP|wxEXPAND,5);
-		hotkeysSizer->Add(buttons,0,wxALL|wxEXPAND,5);
-		hotkeysPage->SetSizerAndFit(hotkeysSizer);
-	}
-
-	// List book
-	book->AddPage(generalPage,_("General"),true);
-	book->AddSubPage(filePage,_("File save/load"),true);
-	book->AddSubPage(editPage,_("Subtitles edit box"),true);
-	book->AddSubPage(gridPage,_("Subtitles grid"),true);
-	book->AddPage(videoPage,_("Video"),true);
-	book->AddPage(audioPage,_("Audio"),true);
-	book->AddSubPage(displayPage,_("Display"),true);
-	book->AddSubPage(audioAdvPage,_("Advanced"),true);
-	book->AddPage(autoPage,_("Automation"),true);
-	book->AddPage(hotkeysPage,_("Hotkeys"),true);
-	#ifdef wxUSE_TREEBOOK
-	book->ChangeSelection(Options.AsInt(_T("Options page")));
-	#endif
-	book->Fit();
-
-	// Buttons Sizer
-	wxStdDialogButtonSizer *stdButtonSizer = new wxStdDialogButtonSizer();
-	stdButtonSizer->AddButton(new wxButton(this,wxID_OK));
-	stdButtonSizer->AddButton(new wxButton(this,wxID_CANCEL));
-	stdButtonSizer->AddButton(new wxButton(this,wxID_APPLY));
-	stdButtonSizer->AddButton(new HelpButton(this,_T("Options")));
-	stdButtonSizer->Realize();
-	wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
-	wxButton *defaultButton = new wxButton(this,BUTTON_DEFAULTS,_("Restore Defaults"));
-	buttonSizer->Add(defaultButton,0,wxEXPAND);
-	buttonSizer->AddStretchSpacer(1);
-	buttonSizer->Add(stdButtonSizer,0,wxEXPAND);
-
-	// Main Sizer
-	wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
-	mainSizer->Add(book,1,wxEXPAND | wxALL,5);
-	mainSizer->Add(buttonSizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,5);
-	mainSizer->SetSizeHints(this);
-	SetSizerAndFit(mainSizer);
-	CenterOnParent();
-
-	// Read
-	ReadFromOptions();
-}
-
-
-
-/// @brief Destructor 
-///
-DialogOptions::~DialogOptions() {
-}
-
-
-
-/// @brief Bind control to option 
-/// @param ctrl   
-/// @param option 
-/// @param param  
-///
-void DialogOptions::Bind(wxControl *ctrl, wxString option,int param) {
-	OptionsBind bind;
-	bind.ctrl = ctrl;
-	bind.option = option;
-	bind.param = param;
-	binds.push_back(bind);
-}
-
-
-
-/// @brief Add a wxTextCtrl 
-/// @param parent 
-/// @param sizer  
-/// @param label  
-/// @param option 
-/// @param type   
-///
-void DialogOptions::AddTextControl(wxWindow *parent,wxSizer *sizer,wxString label,wxString option,TextType type) {
-	sizer->Add(new wxStaticText(parent,-1,label + wxString(_T(": "))),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-	wxTextCtrl *control;
-	if (type == TEXT_TYPE_NUMBER) control = new wxTextCtrl(parent,-1,_T(""),wxDefaultPosition,wxDefaultSize,0,NumValidator());
-	else control = new wxTextCtrl(parent,-1);
-	Bind(control,option);
-	sizer->Add(control,1,wxEXPAND);
-}
-
-
-
-/// @brief Add a wxComboBox 
-/// @param parent    
-/// @param sizer     
-/// @param label     
-/// @param option    
-/// @param choices   
-/// @param readOnly  
-/// @param bindParam 
-///
-void DialogOptions::AddComboControl(wxWindow *parent,wxSizer *sizer,wxString label,wxString option,wxArrayString choices,bool readOnly,int bindParam) {
-	sizer->Add(new wxStaticText(parent,-1,label + wxString(_T(": "))),0,wxALIGN_CENTER_VERTICAL | wxRIGHT,10);
-	int flags = wxCB_DROPDOWN;
-	if (readOnly) flags |= wxCB_READONLY;
-	wxComboBox *control = new wxComboBox(parent,-1,_T(""),wxDefaultPosition,wxDefaultSize,choices,flags);
-	Bind(control,option,bindParam);
-	sizer->Add(control,1,wxEXPAND);
-}
-
-
-
-/// @brief Add a checkbox 
-/// @param parent 
-/// @param sizer  
-/// @param label  
-/// @param option 
-///
-void DialogOptions::AddCheckBox(wxWindow *parent,wxSizer *sizer,wxString label,wxString option) {
-	wxControl *control = new wxCheckBox(parent,-1,label);
-	Bind(control,option);
-	sizer->Add(control,1,wxEXPAND,0);
-}
-
-
-///////////////
-// Event table
-BEGIN_EVENT_TABLE(DialogOptions,wxDialog)
-	EVT_BUTTON(wxID_OK,DialogOptions::OnOK)
-	EVT_BUTTON(wxID_CANCEL,DialogOptions::OnCancel)
-	EVT_BUTTON(wxID_APPLY,DialogOptions::OnApply)
-	EVT_BUTTON(BUTTON_DEFAULTS,DialogOptions::OnDefaults)
-	EVT_BUTTON(BUTTON_HOTKEY_SET,DialogOptions::OnEditHotkey)
-	EVT_BUTTON(BUTTON_HOTKEY_CLEAR,DialogOptions::OnClearHotkey)
-	EVT_BUTTON(BUTTON_HOTKEY_DEFAULT,DialogOptions::OnDefaultHotkey)
-	EVT_BUTTON(BUTTON_HOTKEY_DEFAULT_ALL,DialogOptions::OnDefaultAllHotkey)
-END_EVENT_TABLE()
-
-
-
-/// @brief OK 
-/// @param event 
-///
-void DialogOptions::OnOK(wxCommandEvent &event) {
-	Options.SetInt(_T("Options page"),book->GetSelection());
-	WriteToOptions();
-	EndModal(0);
-
-	// Restart
-	if (needsRestart) {
-		int answer = wxMessageBox(_("Aegisub must restart for the changes to take effect. Restart now?"),_("Restart Aegisub"),wxYES_NO);
-		if (answer == wxYES) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			if (frame->Close()) {
-				RestartAegisub();
-				//wxStandardPaths stand;
-				//wxExecute(stand.GetExecutablePath());
-			}
-		}
-	}
-}
-
-
-
-/// @brief Apply 
-/// @param event 
-///
-void DialogOptions::OnApply(wxCommandEvent &event) {
-	Options.SetInt(_T("Options page"),book->GetSelection());
-	WriteToOptions(true);
-}
-
-
-
-/// @brief Cancel 
-/// @param event 
-///
-void DialogOptions::OnCancel(wxCommandEvent &event) {
-	// Undo hotkeys
-	if (hotkeysModified) Hotkeys.key = origKeys;
-
-	// Set options
-	Options.SetInt(_T("Options page"),book->GetSelection());
-	Options.Save();
-	EndModal(0);
-
-	// Restart
-	if (needsRestart) {
-		int answer = wxMessageBox(_("Aegisub must restart for the changes to take effect. Restart now?"),_("Restart Aegisub"),wxYES_NO);
-		if (answer == wxYES) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			if (frame->Close()) {
-				RestartAegisub();
-				//wxStandardPaths stand;
-				//wxExecute(stand.GetExecutablePath());
-			}
-		}
-	}
-}
-
-
-
-/// @brief Restore defaults 
-/// @param event 
-///
-void DialogOptions::OnDefaults(wxCommandEvent &event) {
-	int result = wxMessageBox(_("Are you sure that you want to restore the defaults? All your settings will be overriden."),_("Restore defaults?"),wxYES_NO);
-	if (result == wxYES) {
-		Options.LoadDefaults(true);
-		Options.Save();
-		ReadFromOptions();
-	}
-}
-
-
-
-/// @brief Write to options 
-/// @param justApply 
-///
-void DialogOptions::WriteToOptions(bool justApply) {
-	// Flags
-	bool mustRestart = false;
-	bool editBox = false;
-	bool grid = false;
-	bool video = false;
-	bool audio = false;
-	bool videoReload = false;
-	bool audioReload = false;
-
-	// For each bound item
-	for (unsigned int i=0;i<binds.size();i++) {
-		// Modified?
-		bool modified = false;
-
-		// Checkbox
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxCheckBox))) {
-			wxCheckBox *check = (wxCheckBox*) binds[i].ctrl;
-			if (Options.AsBool(binds[i].option) != check->GetValue()) {
-				Options.SetBool(binds[i].option,check->GetValue());
-				modified = true;
-			}
-		}
-
-		// Spin control
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxSpinCtrl))) {
-			wxSpinCtrl *spin = (wxSpinCtrl*) binds[i].ctrl;
-			if (spin->GetValue() != Options.AsInt(binds[i].option)) {
-				Options.SetInt(binds[i].option,spin->GetValue());
-				modified = true;
-			}
-		}
-
-		// Text control
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxTextCtrl))) {
-			wxTextCtrl *text = (wxTextCtrl*) binds[i].ctrl;
-			if (text->GetValue() != Options.AsText(binds[i].option)) {
-				Options.ResetWith(binds[i].option,text->GetValue());
-				modified = true;
-			}
-		}
-
-		// Combo box
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxComboBox))) {
-			wxComboBox *combo = (wxComboBox*) binds[i].ctrl;
-			int style = combo->GetWindowStyleFlag();
-
-			// Read-only, use as value
-			if (style & wxCB_READONLY && binds[i].param == 0) {
-				if (combo->GetSelection() != Options.AsInt(binds[i].option)) {
-					Options.SetInt(binds[i].option,combo->GetSelection());
-					modified = true;
-				}
-			}
-
-			// Editable, use as text
-			else {
-				if (!combo->GetValue().IsEmpty() && combo->GetValue() != Options.AsText(binds[i].option)) {
-					Options.SetText(binds[i].option,combo->GetValue());
-					modified = true;
-				}
-			}
-		}
-
-		// Colour button
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxBitmapButton))) {
-			ColourButton *button = (ColourButton*) binds[i].ctrl;
-			if (button->GetColour() != Options.AsColour(binds[i].option)) {
-				Options.SetColour(binds[i].option,button->GetColour());
-				modified = true;
-			}
-		}
-
-		// Set modification type
-		if (modified) {
-			ModType type = Options.GetModType(binds[i].option);
-			if (type == MOD_RESTART) mustRestart = true;
-			if (type == MOD_EDIT_BOX) editBox = true;
-			if (type == MOD_GRID) grid = true;
-			if (type == MOD_VIDEO) video = true;
-			if (type == MOD_VIDEO_RELOAD) videoReload = true;
-			if (type == MOD_AUDIO) audio = true;
-			if (type == MOD_AUDIO_RELOAD) audioReload = true;
-		}
-	}
-
-	// Apply hotkey changes if modified
-	if (hotkeysModified) {
-		// Save changes
-		Hotkeys.modified = true;
-		Hotkeys.Save();
-		hotkeysModified = false;
-		origKeys = Hotkeys.key;
-
-		// Rebuild menu
-		FrameMain *parent = (FrameMain*) GetParent();
-		parent->InitMenu();
-
-		// Rebuild accelerator table
-		parent->SetAccelerators();
-
-		// Update tooltips
-		ToolTipManager::Update();
-	}
-
-	// Save options
-	Options.SetFile(StandardPaths::DecodePath(_T("?user/config.dat")));
-	Options.Save();
-
-	// Need restart?
-	if (mustRestart) {
-		if (justApply) needsRestart = true;
-		else {
-			int answer = wxMessageBox(_("Aegisub must restart for the changes to take effect. Restart now?"),_("Restart Aegisub"),wxYES_NO);
-			if (answer == wxYES) {
-				FrameMain *frame = (FrameMain*) GetParent();
-				if (frame->Close()) {
-					RestartAegisub();
-					//wxStandardPaths stand;
-					//wxExecute(stand.GetExecutablePath());
-				}
-			}
-		}
-	}
-
-	// Other modifications
-	if (!mustRestart || justApply) {
-		// Edit box
-		if (editBox) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			frame->EditBox->TextEdit->SetStyles();
-			frame->EditBox->TextEdit->UpdateStyle();
-		}
-		
-		// Grid
-		if (grid) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			frame->SubsBox->UpdateStyle();
-		}
-
-		// Video
-		if (videoReload) {
-			VideoContext::Get()->Reload();
-		}
-		else if (video) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			frame->videoBox->videoSlider->Refresh();
-		}
-
-		// Audio
-		if (audioReload) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			frame->audioBox->audioDisplay->Reload();
-		}
-		else if (audio) {
-			FrameMain *frame = (FrameMain*) GetParent();
-			frame->audioBox->audioDisplay->RecreateImage();
-			frame->audioBox->audioDisplay->Refresh();
-		}
-	}
-}
-
-
-
-/// @brief Read form options 
-///
-void DialogOptions::ReadFromOptions() {
-	for (unsigned int i=0;i<binds.size();i++) {
-		// Checkbox
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxCheckBox))) {
-			wxCheckBox *check = (wxCheckBox*) binds[i].ctrl;
-			check->SetValue(Options.AsBool(binds[i].option));
-		}
-
-		// Spin control
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxSpinCtrl))) {
-			wxSpinCtrl *spin = (wxSpinCtrl*) binds[i].ctrl;
-			spin->SetValue(Options.AsInt(binds[i].option));
-		}
-
-		// Text control
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxTextCtrl))) {
-			wxTextCtrl *text = (wxTextCtrl*) binds[i].ctrl;
-			text->SetValue(Options.AsText(binds[i].option));
-		}
-
-		// Combo box
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxComboBox))) {
-			wxComboBox *combo = (wxComboBox*) binds[i].ctrl;
-			int style = combo->GetWindowStyleFlag();
-
-			// Read-only, use as value
-			if (style & wxCB_READONLY && binds[i].param == 0) {
-				combo->SetSelection(Options.AsInt(binds[i].option));
-			}
-
-			// Editable, set text
-			else {
-				wxString value = Options.AsText(binds[i].option);
-				if (!(style & wxCB_READONLY) && combo->FindString(value) == wxNOT_FOUND) combo->Append(value);
-				combo->SetValue(value);
-			}
-		}
-
-		// Colour button
-		if (binds[i].ctrl->IsKindOf(CLASSINFO(wxBitmapButton))) {
-			ColourButton *button = (ColourButton*) binds[i].ctrl;
-			button->SetColour(Options.AsColour(binds[i].option));
-		}
-	}
-}
-
-
-
-/// @brief Edit a hotkey 
-/// @param event 
-/// @return 
-///
-void DialogOptions::OnEditHotkey(wxCommandEvent &event) {
-	// Get selection
-	int sel = Shortcuts->GetFirstSelected();
-	if (sel == wxNOT_FOUND) return;
-
-	// Get key and store old
-	HotkeyType *curKey = (HotkeyType *) wxUIntToPtr(Shortcuts->GetItemData(sel));
-	int oldKeycode = curKey->keycode;
-	int oldFlags = curKey->flags;
-
-	// Open dialog
-	DialogInputHotkey input(this, curKey, Shortcuts->GetItemText(sel), Shortcuts);
-	input.ShowModal();
-
-	// Update stuff if it changed
-	if (oldKeycode != curKey->keycode || oldFlags != curKey->flags) {
-		Shortcuts->SetItem(sel,1,curKey->GetText());
-		hotkeysModified = true;
-	}
-}
-
-
-
-/// @brief Clear a hotkey 
-/// @param event 
-///
-void DialogOptions::OnClearHotkey(wxCommandEvent &event) {
-	for (int item=-1;true;) {
-		item = Shortcuts->GetNextItem(item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
-		if (item == -1) break;
-
-		HotkeyType *curKey = (HotkeyType *) wxUIntToPtr(Shortcuts->GetItemData(item));
-		if (curKey->keycode != 0 || curKey->flags != 0) {
-			hotkeysModified = true;
-			curKey->keycode = 0;
-			curKey->flags = 0;
-			Shortcuts->SetItem(item,1,curKey->GetText());
-		}
-	}
-}
-
-
-
-/// @brief Reset hotkey to default 
-/// @param event 
-///
-void DialogOptions::OnDefaultHotkey(wxCommandEvent &event) {
-	// Load defaults
-	HotkeyManager defs;
-	defs.LoadDefaults();
-
-	// Replace
-	for (int item=-1;true;) {
-		item = Shortcuts->GetNextItem(item,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
-		if (item == -1) break;
-
-		HotkeyType *curKey = (HotkeyType *) wxUIntToPtr(Shortcuts->GetItemData(item));
-		HotkeyType *origKey = &defs.key[curKey->origName.Lower()];
-		if (origKey->keycode != curKey->keycode || origKey->flags != curKey->flags) {
-			hotkeysModified = true;
-			curKey->keycode = origKey->keycode;
-			curKey->flags = origKey->flags;
-			Shortcuts->SetItem(item,1,curKey->GetText());
-
-			// Unmap duplicate
-			HotkeyType *dup = Hotkeys.Find(origKey->keycode,origKey->flags);
-			if (dup) {
-				dup->keycode = 0;
-				dup->flags = 0;
-				int item = Shortcuts->FindItem(-1,wxPtrToUInt(dup));
-				if (item != -1) Shortcuts->SetItem(item,1,dup->GetText());
-			}
-		}
-	}
-}
-
-
-
-/// @brief Reset all hotkeys to default 
-/// @param event 
-///
-void DialogOptions::OnDefaultAllHotkey(wxCommandEvent &event) {
-	Hotkeys.LoadDefaults();
-	Shortcuts->Freeze();
-	Shortcuts->ClearAll();
-	Shortcuts->InsertColumn(0,_("Function"),wxLIST_FORMAT_LEFT,200);
-	Shortcuts->InsertColumn(1,_("Key"),wxLIST_FORMAT_LEFT,120);
-
-	// Populate list
-	std::map<wxString,HotkeyType>::iterator cur;
-	for (cur = Hotkeys.key.end();cur-- != Hotkeys.key.begin();) {
-		wxListItem item;
-		item.SetText(wxGetTranslation(cur->second.origName));
-		item.SetData(&cur->second);
-		int pos = Shortcuts->InsertItem(item);
-		Shortcuts->SetItem(pos,1,cur->second.GetText());
-	}
-	hotkeysModified = true;
-	Shortcuts->Thaw();
-}
-
-
-
-/// @brief Input constructor 
-/// @param _key   
-/// @param name   
-/// @param shorts 
-///
-DialogInputHotkey::DialogInputHotkey(wxWindow *parent, HotkeyType *_key, wxString name, wxListView *shorts)
-: wxDialog(parent, -1, _("Press Key"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxWANTS_CHARS)
-{
-	// Key
-	key = _key;
-	shortcuts = shorts;
-
-	wxButton *cancel_button = new wxButton(this, wxID_CANCEL);
-	cancel_button->Connect(wxEVT_KEY_DOWN, (wxObjectEventFunction)&DialogInputHotkey::OnKeyDown, 0, this);
-
-	// Main sizer
-	wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
-	MainSizer->Add(new wxStaticText(this,-1,wxString::Format(_("Press key to bind to \"%s\" or Esc to cancel."), name.c_str())), 0, wxALL, 12);
-	MainSizer->Add(cancel_button, 0, wxALIGN_CENTER|wxALL&~wxTOP, 12);
-	MainSizer->SetSizeHints(this);
-	SetSizer(MainSizer);
-	CentreOnParent();
-}
-
-
-
-/////////////////////
-// Input event table
-BEGIN_EVENT_TABLE(DialogInputHotkey,wxDialog)
-	EVT_KEY_DOWN(DialogInputHotkey::OnKeyDown)
-END_EVENT_TABLE()
-
-
-
-/// @brief On key down 
-/// @param event 
-/// @return 
-///
-void DialogInputHotkey::OnKeyDown(wxKeyEvent &event) {
-	// Get key
-	int keycode = event.GetKeyCode();
-	if (keycode == WXK_ESCAPE) EndModal(0);
-	else if (keycode != WXK_SHIFT && keycode != WXK_CONTROL && keycode != WXK_ALT) {
-		// Get modifier
-		int mod = 0;
-		if (event.m_altDown) mod |= wxACCEL_ALT;
-#ifdef __APPLE__
-		if (event.m_metaDown) mod |= wxACCEL_CTRL;
-#else
-		if (event.m_controlDown) mod |= wxACCEL_CTRL;
-#endif
-		if (event.m_shiftDown) mod |= wxACCEL_SHIFT;
-
-		// Check if keycode is free
-		HotkeyType *dup = Hotkeys.Find(keycode,mod);
-		if (dup) {
-			int result = wxMessageBox(wxString::Format(_("The hotkey %s is already mapped to %s. If you proceed, that hotkey will be cleared. Proceed?"),dup->GetText().c_str(),dup->origName.c_str()),_("Hotkey conflict"),wxYES_NO | wxICON_EXCLAMATION);
-			if (result == wxNO) {
-				EndModal(0);
-				return;
-			}
-			dup->keycode = 0;
-			dup->flags = 0;
-			int item = shortcuts->FindItem(-1,wxPtrToUInt(dup));
-			if (item != -1) shortcuts->SetItem(item,1,dup->GetText());
-		}
-
-		// Set keycode
-		key->keycode = keycode;
-		key->flags = mod;
-
-		// End dialogue
-		EndModal(0);
-	}
-	else event.Skip();
-}
-
diff --git a/aegisub/src/dialog_options.h b/aegisub/src/dialog_options.h
deleted file mode 100644
index f933ad959..000000000
--- a/aegisub/src/dialog_options.h
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (c) 2006, Rodrigo Braz Monteiro
-// 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/
-//
-// $Id$
-
-/// @file dialog_options.h
-/// @see dialog_options.cpp
-/// @ingroup configuration_ui
-///
-
-
-
-
-////////////
-// Includes
-#ifndef AGI_PRE
-#include <map>
-#include <vector>
-
-#include <wx/dialog.h>
-#include <wx/listctrl.h>
-#endif
-
-#include "hotkeys.h"
-#include "options.h"
-
-
-//////////////
-// Prototypes
-class FrameMain;
-class DialogInputHotkey;
-#if wxUSE_TREEBOOK
-class wxTreebook;
-#else
-#include <wx/choicebk.h>
-
-/// DOCME
-typedef wxChoicebook wxTreebook;
-#endif
-
-
-
-/// DOCME
-/// @class OptionsBind
-/// @brief DOCME
-///
-/// DOCME
-class OptionsBind {
-public:
-
-	/// DOCME
-	wxControl *ctrl;
-
-	/// DOCME
-	wxString option;
-
-	/// DOCME
-	int param;
-};
-
-
-
-/// DOCME
-enum TextType {
-
-	/// DOCME
-	TEXT_TYPE_PLAIN,
-
-	/// DOCME
-	TEXT_TYPE_NUMBER,
-
-	/// DOCME
-	TEXT_TYPE_FILE,
-
-	/// DOCME
-	TEXT_TYPE_FOLDER,
-
-	/// DOCME
-	TEXT_TYPE_FONT
-};
-
-
-/// DOCME
-/// @class DialogOptions
-/// @brief DOCME
-///
-/// DOCME
-class DialogOptions: public wxDialog {
-private:
-
-	/// DOCME
-	bool needsRestart;
-
-
-	/// DOCME
-	wxTreebook *book;
-
-	/// DOCME
-	std::vector<OptionsBind> binds;
-
-
-	/// DOCME
-	std::map<wxString,HotkeyType> origKeys;
-
-	/// DOCME
-	wxListView *Shortcuts;
-
-	/// DOCME
-	bool hotkeysModified;
-
-	void Bind(wxControl *ctrl,wxString option,int param=0);
-	void WriteToOptions(bool justApply=false);
-	void ReadFromOptions();
-
-	void AddTextControl(wxWindow *parent,wxSizer *sizer,wxString label,wxString option,TextType type=TEXT_TYPE_PLAIN);
-	void AddComboControl(wxWindow *parent,wxSizer *sizer,wxString label,wxString option,wxArrayString choices,bool readOnly=true,int bindParam=0);
-	void AddCheckBox(wxWindow *parent,wxSizer *sizer,wxString label,wxString option);
-
-	void OnOK(wxCommandEvent &event);
-	void OnCancel(wxCommandEvent &event);
-	void OnApply(wxCommandEvent &event);
-	void OnDefaults(wxCommandEvent &event);
-
-	void OnEditHotkey(wxCommandEvent &event);
-	void OnClearHotkey(wxCommandEvent &event);
-	void OnDefaultHotkey(wxCommandEvent &event);
-	void OnDefaultAllHotkey(wxCommandEvent &event);
-
-public:
-	DialogOptions(wxWindow *parent);
-	~DialogOptions();
-
-	DECLARE_EVENT_TABLE()
-};
-
-
-
-
-/// DOCME
-/// @class DialogInputHotkey
-/// @brief DOCME
-///
-/// DOCME
-class DialogInputHotkey : public wxDialog {
-
-	/// DOCME
-	HotkeyType *key;
-
-	/// DOCME
-	wxListView *shortcuts;
-
-	void OnKeyDown(wxKeyEvent &event);
-
-public:
-	DialogInputHotkey(wxWindow *parent, HotkeyType *key, wxString name, wxListView *Shortcuts);
-
-	DECLARE_EVENT_TABLE()
-};
-
-
diff --git a/aegisub/src/dialog_paste_over.cpp b/aegisub/src/dialog_paste_over.cpp
index e746d332c..387f883fd 100644
--- a/aegisub/src/dialog_paste_over.cpp
+++ b/aegisub/src/dialog_paste_over.cpp
@@ -48,6 +48,7 @@
 
 #include "dialog_paste_over.h"
 #include "help_button.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -87,7 +88,10 @@ DialogPasteOver::DialogPasteOver (wxWindow *parent)
 	ListSizer->Add(ListBox,0,wxEXPAND|wxTOP,5);
 
 	// Load checked items
-	for (unsigned int i=0;i<choices.Count();i++) ListBox->Check(i,Options.AsBool(wxString::Format(_T("Paste Over #%i"),i)));
+	/// @todo This assumes a static set of fields.
+	std::vector<bool> choice_array;
+	OPT_GET("Tool/Paste Lines Over/Fields")->GetListBool(choice_array);
+	for (unsigned int i=0;i<choices.Count();i++) ListBox->Check(i,choice_array.at(i));
 
 	// Top buttons
 	wxSizer *TopButtonSizer = new wxBoxSizer(wxHORIZONTAL);
@@ -138,15 +142,13 @@ END_EVENT_TABLE()
 /// @param event 
 ///
 void DialogPasteOver::OnOK(wxCommandEvent &event) {
-	// Set options
-	options.SetCount(10);
+
+	std::vector<bool> map;
 	for (int i=0;i<10;i++) {
-		options[i] = ListBox->IsChecked(i) ? 1 : 0;
-		Options.SetBool(wxString::Format(_T("Paste Over #%i"),i),options[i]==1);
+		map[i] = ListBox->IsChecked(i);
 	}
-	Options.Save();
+	OPT_SET("Tool/Paste Lines Over/Fields")->SetListBool(map);
 
-	// End
 	EndModal(1);
 }
 
diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp
index ae68a3e1e..6793041ed 100644
--- a/aegisub/src/dialog_search_replace.cpp
+++ b/aegisub/src/dialog_search_replace.cpp
@@ -44,6 +44,7 @@
 #include <wx/string.h>
 #endif
 
+#include "compat.h"
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "dialog_search_replace.h"
@@ -68,14 +69,14 @@ DialogSearchReplace::DialogSearchReplace (wxWindow *parent,bool _hasReplace,wxSt
 
 	// Find sizer
 	wxSizer *FindSizer = new wxFlexGridSizer(2,2,5,15);
-	wxArrayString FindHistory = Options.GetRecentList(_T("Recent find"));
+	wxArrayString FindHistory = lagi_MRU_wxAS("Find");
 	FindEdit = new wxComboBox(this,-1,_T(""),wxDefaultPosition,wxSize(300,-1),FindHistory,wxCB_DROPDOWN);
 	//if (FindHistory.Count()) FindEdit->SetStringSelection(FindHistory[0]);
 	FindEdit->SetSelection(0);
 	FindSizer->Add(new wxStaticText(this,-1,_("Find what:")),0,wxRIGHT | wxALIGN_CENTER_VERTICAL,0);
 	FindSizer->Add(FindEdit,0,wxRIGHT,0);
 	if (hasReplace) {
-		wxArrayString ReplaceHistory = Options.GetRecentList(_T("Recent replace"));
+		wxArrayString ReplaceHistory = lagi_MRU_wxAS("Replace");
 		ReplaceEdit = new wxComboBox(this,-1,_T(""),wxDefaultPosition,wxSize(300,-1),ReplaceHistory,wxCB_DROPDOWN);
 		FindSizer->Add(new wxStaticText(this,-1,_("Replace with:")),0,wxRIGHT | wxALIGN_CENTER_VERTICAL,0);
 		FindSizer->Add(ReplaceEdit,0,wxRIGHT,0);
@@ -87,10 +88,10 @@ DialogSearchReplace::DialogSearchReplace (wxWindow *parent,bool _hasReplace,wxSt
 	CheckMatchCase = new wxCheckBox(this,CHECK_MATCH_CASE,_("Match case"));
 	CheckRegExp = new wxCheckBox(this,CHECK_MATCH_CASE,_("Use regular expressions"));
 	CheckUpdateVideo = new wxCheckBox(this,CHECK_UPDATE_VIDEO,_("Update Video (slow)"));
-	CheckMatchCase->SetValue(Options.AsBool(_T("Find Match Case")));
-	CheckRegExp->SetValue(Options.AsBool(_T("Find RegExp")));
+	CheckMatchCase->SetValue(OPT_GET("Tool/Search Replace/Match Case")->GetBool());
+	CheckRegExp->SetValue(OPT_GET("Tool/Search Replace/RegExp")->GetBool());
 	//CheckRegExp->Enable(false);
-	CheckUpdateVideo->SetValue(Options.AsBool(_T("Find Update Video")));
+	CheckUpdateVideo->SetValue(OPT_GET("Tool/Search Replace/Video Update")->GetBool());
 //	CheckUpdateVideo->Enable(Search.grid->video->loaded);
 	OptionsSizer->Add(CheckMatchCase,0,wxBOTTOM,5);
 	OptionsSizer->Add(CheckRegExp,0,wxBOTTOM,5);
@@ -110,8 +111,8 @@ DialogSearchReplace::DialogSearchReplace (wxWindow *parent,bool _hasReplace,wxSt
 	wxSizer *LimitSizer = new wxBoxSizer(wxHORIZONTAL);
 	LimitSizer->Add(Field,1,wxEXPAND | wxRIGHT,5);
 	LimitSizer->Add(Affect,0,wxEXPAND | wxRIGHT,0);
-	Field->SetSelection(Options.AsInt(_T("Find Field")));
-	Affect->SetSelection(Options.AsInt(_T("Find Affect")));
+	Field->SetSelection(OPT_GET("Tool/Search Replace/Field")->GetInt());
+	Affect->SetSelection(OPT_GET("Tool/Search Replace/Affect")->GetInt());
 
 	// Left sizer
 	wxSizer *LeftSizer = new wxBoxSizer(wxVERTICAL);
@@ -160,12 +161,11 @@ void DialogSearchReplace::UpdateSettings() {
 	Search.isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
 	Search.matchCase = CheckMatchCase->IsChecked();
 	Search.updateVideo = CheckUpdateVideo->IsChecked() && CheckUpdateVideo->IsEnabled();
-	Options.SetBool(_T("Find Match Case"),CheckMatchCase->IsChecked());
-	Options.SetBool(_T("Find RegExp"),CheckRegExp->IsChecked());
-	Options.SetBool(_T("Find Update Video"),CheckUpdateVideo->IsChecked());
-	Options.SetInt(_T("Find Field"),Field->GetSelection());
-	Options.SetInt(_T("Find Affect"),Affect->GetSelection());
-	Options.Save();
+	OPT_SET("Tool/Search Replace/Match Case")->SetBool(CheckMatchCase->IsChecked());
+	OPT_SET("Tool/Search Replace/RegExp")->SetBool(CheckRegExp->IsChecked());
+	OPT_SET("Tool/Search Replace/Video Update")->SetBool(CheckUpdateVideo->IsChecked());
+	OPT_SET("Tool/Search Replace/Field")->SetInt(Field->GetSelection());
+	OPT_SET("Tool/Search Replace/Affect")->SetInt(Affect->GetSelection());
 }	
 
 
@@ -234,7 +234,7 @@ void DialogSearchReplace::FindReplace(int mode) {
 		if (hasReplace) {
 			wxString ReplaceWith = ReplaceEdit->GetValue();
 			Search.ReplaceWith = ReplaceWith;
-			Options.AddToRecentList(ReplaceWith,_T("Recent replace"));
+			AegisubApp::Get()->mru->Add("Replace", STD_STR(ReplaceWith));
 		}	
 	}
 
@@ -244,11 +244,11 @@ void DialogSearchReplace::FindReplace(int mode) {
 		Search.ReplaceWith = ReplaceWith;
 		if (mode == 1) Search.ReplaceNext();
 		else Search.ReplaceAll();
-		Options.AddToRecentList(ReplaceWith,_T("Recent replace"));
+		AegisubApp::Get()->mru->Add("Replace", STD_STR(ReplaceWith));
 	}
 	
 	// Add to history
-	Options.AddToRecentList(LookFor,_T("Recent find"));
+	AegisubApp::Get()->mru->Add("Find", STD_STR(LookFor));
 	UpdateDropDowns();
 }
 
@@ -287,7 +287,7 @@ void DialogSearchReplace::UpdateDropDowns() {
 	// Find
 	FindEdit->Freeze();
 	FindEdit->Clear();
-	FindEdit->Append(Options.GetRecentList(_T("Recent find")));
+	FindEdit->Append(lagi_MRU_wxAS("Find"));
 	FindEdit->SetSelection(0);
 	FindEdit->Thaw();
 
@@ -295,7 +295,7 @@ void DialogSearchReplace::UpdateDropDowns() {
 	if (hasReplace) {
 		ReplaceEdit->Freeze();
 		ReplaceEdit->Clear();
-		ReplaceEdit->Append(Options.GetRecentList(_T("Recent replace")));
+		ReplaceEdit->Append(lagi_MRU_wxAS("Replace"));
 		ReplaceEdit->SetSelection(0);
 		ReplaceEdit->Thaw();
 	}
diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp
index 7b9a39169..daccbc3b0 100644
--- a/aegisub/src/dialog_selection.cpp
+++ b/aegisub/src/dialog_selection.cpp
@@ -45,8 +45,10 @@
 #endif
 
 #include "ass_dialogue.h"
+#include "compat.h"
 #include "dialog_selection.h"
 #include "help_button.h"
+#include "main.h"
 #include "options.h"
 #include "subs_grid.h"
 #include "subs_edit_box.h"
@@ -70,7 +72,7 @@ wxDialog (parent,-1,_("Select"),wxDefaultPosition,wxDefaultSize,wxCAPTION)
 	// Matches box
 	Matches = new wxRadioButton(this,-1,_("Matches"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
 	DoesntMatch = new wxRadioButton(this,-1,_("Doesn't Match"),wxDefaultPosition,wxDefaultSize,0);
-	Match = new wxTextCtrl(this,-1,Options.AsText(_T("Select Text")),wxDefaultPosition,wxSize(200,-1));
+	Match = new wxTextCtrl(this,-1,lagi_wxString(OPT_GET("Tool/Select Lines/Text")->GetString()),wxDefaultPosition,wxSize(200,-1));
 	MatchCase = new wxCheckBox(this,-1,_("Match case"));
 	Exact = new wxRadioButton(this,-1,_("Exact match"),wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
 	Contains = new wxRadioButton(this,-1,_("Contains"));
@@ -130,13 +132,13 @@ wxDialog (parent,-1,_("Select"),wxDefaultPosition,wxDefaultSize,wxCAPTION)
 	CenterOnParent();
 
 	// Load settings
-	Field->SetSelection(Options.AsInt(_T("Select Field")));
-	Action->SetSelection(Options.AsInt(_T("Select Action")));
-	MatchCase->SetValue(Options.AsBool(_T("Select Match case")));
-	MatchDialogues->SetValue(Options.AsBool(_T("Select Match dialogues")));
-	MatchComments->SetValue(Options.AsBool(_T("Select Match comments")));
-	int condition = Options.AsInt(_T("Select Condition"));
-	int mode = Options.AsInt(_T("Select Mode"));
+	Field->SetSelection(OPT_GET("Tool/Select/Field")->GetInt());
+	Action->SetSelection(OPT_GET("Tool/Select/Action")->GetInt());
+	MatchCase->SetValue(OPT_GET("Tool/Select Lines/Match/Case")->GetBool());
+	MatchDialogues->SetValue(OPT_GET("Tool/Select Lines/Match/Dialogue")->GetBool());
+	MatchComments->SetValue(OPT_GET("Tool/Select Lines/Match/Comment")->GetBool());
+	int condition = OPT_GET("Tool/Select/Condition")->GetInt();
+	int mode = OPT_GET("Tool/Select/Mode")->GetInt();
 	if (condition == 1) DoesntMatch->SetValue(true);
 	if (mode == 1) Contains->SetValue(true);
 	else if (mode == 2) RegExp->SetValue(true);
@@ -303,14 +305,14 @@ void DialogSelection::SaveSettings() {
 	else condition = 1;
 
 	// Store
-	Options.SetText(_T("Select Text"),Match->GetValue());
-	Options.SetInt(_T("Select Condition"),condition);
-	Options.SetInt(_T("Select Field"),field);
-	Options.SetInt(_T("Select Action"),action);
-	Options.SetInt(_T("Select Mode"),mode);
-	Options.SetBool(_T("Select Match case"),MatchCase->IsChecked());
-	Options.SetBool(_T("Select Match dialogues"),MatchDialogues->IsChecked());
-	Options.SetBool(_T("Select Match comments"),MatchComments->IsChecked());
+	OPT_SET("Tool/Select Lines/Text")->SetString(STD_STR(Match->GetValue()));
+	OPT_SET("Tool/Select/Condition")->SetInt(condition);
+	OPT_SET("Tool/Select/Field")->SetInt(field);
+	OPT_SET("Tool/Select/Action")->SetInt(action);
+	OPT_SET("Tool/Select/Mode")->SetInt(mode);
+	OPT_SET("Tool/Select Lines/Match/Case")->SetBool(MatchCase->IsChecked());
+	OPT_SET("Tool/Select Lines/Match/Dialogue")->SetBool(MatchDialogues->IsChecked());
+	OPT_SET("Tool/Select Lines/Match/Comment")->SetBool(MatchComments->IsChecked());
 }
 
 
diff --git a/aegisub/src/dialog_shift_times.cpp b/aegisub/src/dialog_shift_times.cpp
index 2d4be2eda..eea291599 100644
--- a/aegisub/src/dialog_shift_times.cpp
+++ b/aegisub/src/dialog_shift_times.cpp
@@ -55,6 +55,7 @@
 #include "dialog_shift_times.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "subs_edit_box.h"
@@ -154,20 +155,20 @@ DialogShiftTimes::DialogShiftTimes (wxWindow *parent,SubtitlesGrid *_grid)
 	CenterOnParent();
 
 	// Load values from options
-	if (!Options.AsBool(_T("Shift Times ByTime"))) {
+	if (!OPT_GET("Tools/Shift Times/ByTime")->GetBool()) {
 		if (RadioFrames->IsEnabled()) {
 			RadioFrames->SetValue(true);
 			ShiftFrame->Enable(true);
 			ShiftTime->Enable(false);
-			ShiftFrame->SetValue(Options.AsText(_T("Shift Times Length")));
+			ShiftFrame->SetValue(AegiIntegerToString(OPT_GET("Tool/Shift Times/Length")->GetInt()));
 		}
 	}
 	else {
-		ShiftTime->SetTime(Options.AsInt(_T("Shift Times Length")));
+		ShiftTime->SetTime(OPT_GET("Tool/Shift Times/Length")->GetInt());
 	}
-	TimesChoice->SetSelection(Options.AsInt(_T("Shift Times Type")));
-	SelChoice->SetSelection(Options.AsInt(_T("Shift Times Affect")));
-	if (Options.AsBool(_T("Shift Times Direction"))) DirectionBackward->SetValue(true);
+	TimesChoice->SetSelection(OPT_GET("Tool/Shift Times/Type")->GetInt());
+	SelChoice->SetSelection(OPT_GET("Tool/Shift Times/Affect")->GetInt());
+	if (OPT_GET("Tools/Shift Times/Direction")->GetBool()) DirectionBackward->SetValue(true);
 
 	// Has selection?
 	wxArrayInt sel = grid->GetSelection();
@@ -304,12 +305,11 @@ void DialogShiftTimes::OnOK(wxCommandEvent &event) {
 	}
 
 	// Store modifications
-	Options.SetBool(_T("Shift Times ByTime"),byTime);
-	Options.SetInt(_T("Shift Times Type"),type);
-	Options.SetInt(_T("Shift Times Length"),len);
-	Options.SetInt(_T("Shift Times Affect"),affect);
-	Options.SetBool(_T("Shift Times Direction"),backward);
-	Options.Save();
+	OPT_SET("Tools/Shift Times/ByTime")->SetBool(byTime);
+	OPT_SET("Tool/Shift Times/Type")->SetInt(type);
+	OPT_SET("Tool/Shift Times/Length")->SetInt(len);
+	OPT_SET("Tool/Shift Times/Affect")->SetInt(affect);
+	OPT_SET("Tools/Shift Times/Direction")->SetBool(backward);
 
 	// End dialog
 	grid->ass->FlagAsModified(_("shifting"));
diff --git a/aegisub/src/dialog_spellchecker.cpp b/aegisub/src/dialog_spellchecker.cpp
index 6408baab9..b9f5f9281 100644
--- a/aegisub/src/dialog_spellchecker.cpp
+++ b/aegisub/src/dialog_spellchecker.cpp
@@ -44,10 +44,12 @@
 
 #include "ass_dialogue.h"
 #include "ass_file.h"
+#include "compat.h"
 #include "dialog_spellchecker.h"
 #include "frame_main.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "spellchecker_manager.h"
 #include "subs_edit_box.h"
@@ -114,7 +116,7 @@ DialogSpellChecker::DialogSpellChecker(wxFrame *parent)
 	}
 
 	// Get current language
-	wxString curLang = Options.AsText(_T("Spell checker language"));
+	wxString curLang = lagi_wxString(OPT_GET("Tool/Spell Checker/Language")->GetString());
 	int curLangPos = langCodes.Index(curLang);
 	if (curLangPos == wxNOT_FOUND) {
 		curLangPos = langCodes.Index(_T("en"));
@@ -401,8 +403,7 @@ void DialogSpellChecker::OnChangeLanguage(wxCommandEvent &event) {
 	// Change language code
 	wxString code = langCodes[language->GetSelection()];
 	spellchecker->SetLanguage(code);
-	Options.SetText(_T("Spell checker language"),code);
-	Options.Save();
+	OPT_SET("Tool/Spell Checker/Language")->SetString(STD_STR(code));
 
 	// Go back to first match
 	GetFirstMatch();
diff --git a/aegisub/src/dialog_style_editor.cpp b/aegisub/src/dialog_style_editor.cpp
index a29f35056..ef1e0a873 100644
--- a/aegisub/src/dialog_style_editor.cpp
+++ b/aegisub/src/dialog_style_editor.cpp
@@ -50,10 +50,12 @@
 #include "ass_override.h"
 #include "ass_style.h"
 #include "ass_style_storage.h"
+#include "compat.h"
 #include "dialog_colorpicker.h"
 #include "dialog_style_editor.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "subs_grid.h"
 #include "subs_preview.h"
@@ -362,9 +364,9 @@ DialogStyleEditor::DialogStyleEditor (wxWindow *parent, AssStyle *_style, Subtit
 	SubsPreview = NULL;
 	PreviewText = NULL;
 	if (SubtitlesProviderFactoryManager::ProviderAvailable()) {
-		PreviewText = new wxTextCtrl(this,TEXT_PREVIEW,Options.AsText(_T("Style editor preview text")));
-		previewButton = new ColourButton(this,BUTTON_PREVIEW_COLOR,wxSize(45,16),Options.AsColour(_T("Style editor preview background")));
-		SubsPreview = new SubtitlesPreview(this,-1,wxDefaultPosition,wxSize(100,60),wxSUNKEN_BORDER,Options.AsColour(_T("Style editor preview background")));
+		PreviewText = new wxTextCtrl(this,TEXT_PREVIEW,lagi_wxString(OPT_GET("Tool/Style Editor/Preview Text")->GetString()));
+		previewButton = new ColourButton(this,BUTTON_PREVIEW_COLOR,wxSize(45,16),lagi_wxColour(OPT_GET("Colour/Style Editor/Background/Preview")->GetColour()));
+		SubsPreview = new SubtitlesPreview(this,-1,wxDefaultPosition,wxSize(100,60),wxSUNKEN_BORDER,lagi_wxColour(OPT_GET("Colour/Style Editor/Background/Preview")->GetColour()));
 	
 		SubsPreview->SetToolTip(_("Preview of current style."));
 		SubsPreview->SetStyle(style);
@@ -588,8 +590,7 @@ void DialogStyleEditor::Apply (bool apply,bool close) {
 		// Exit
 		if (close) {
 			EndModal(1);
-			if (PreviewText) Options.SetText(_T("Style editor preview text"),PreviewText->GetValue());
-			Options.Save();
+			if (PreviewText) OPT_SET("Tool/Style Editor/Preview Text")->SetString(STD_STR(PreviewText->GetValue()));
 		}
 
 		// Update preview
@@ -600,8 +601,7 @@ void DialogStyleEditor::Apply (bool apply,bool close) {
 	else {
 		if (close) {
 			EndModal(0);
-			if (PreviewText) Options.SetText(_T("Style editor preview text"),PreviewText->GetValue());
-			Options.Save();
+			if (PreviewText) OPT_SET("Tool/Style Editor/Preview Text")->SetString(STD_STR(PreviewText->GetValue()));
 		}
 	}
 }
@@ -722,8 +722,7 @@ void DialogStyleEditor::OnPreviewTextChange (wxCommandEvent &event) {
 ///
 void DialogStyleEditor::OnPreviewColourChange (wxCommandEvent &event) {
 	if (SubsPreview) SubsPreview->SetColour(previewButton->GetColour());
-	Options.SetColour(_T("Style editor preview background"),previewButton->GetColour());
-	Options.Save();
+	OPT_SET("Colour/Style Editor/Background/Preview")->SetColour(STD_STR(previewButton->GetColour().GetAsString(wxC2S_CSS_SYNTAX)));
 }
 
 /// @brief Command event to update preview 
diff --git a/aegisub/src/dialog_style_manager.cpp b/aegisub/src/dialog_style_manager.cpp
index bb40e02bc..555bee639 100644
--- a/aegisub/src/dialog_style_manager.cpp
+++ b/aegisub/src/dialog_style_manager.cpp
@@ -47,10 +47,12 @@
 #include "ass_dialogue.h"
 #include "ass_file.h"
 #include "ass_style.h"
+#include "compat.h"
 #include "dialog_style_editor.h"
 #include "dialog_style_manager.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "subs_grid.h"
@@ -829,14 +831,13 @@ void DialogStyleManager::OnCurrentDelete (wxCommandEvent &) {
 /// @brief Import styles from another script 
 void DialogStyleManager::OnCurrentImport(wxCommandEvent &) {
 	// Get file name
-	wxString path = Options.AsText(_T("Last open subtitles path"));	
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());	
 	wxString filename = wxFileSelector(_("Open subtitles file"),path,_T(""),_T(""),AssFile::GetWildcardList(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 
 	if (!filename.IsEmpty()) {
 		// Save path
 		wxFileName filepath(filename);
-		Options.SetText(_T("Last open subtitles path"), filepath.GetPath());
-		Options.Save();
+		OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(filepath.GetPath()));
 
 		try {
 			// Load file
diff --git a/aegisub/src/dialog_text_import.cpp b/aegisub/src/dialog_text_import.cpp
index eddc6840d..1aeec8af1 100644
--- a/aegisub/src/dialog_text_import.cpp
+++ b/aegisub/src/dialog_text_import.cpp
@@ -44,7 +44,9 @@
 #include <wx/stattext.h>
 #endif
 
+#include "compat.h"
 #include "dialog_text_import.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -56,8 +58,8 @@ DialogTextImport::DialogTextImport()
 	// Main controls
 	wxFlexGridSizer *fg = new wxFlexGridSizer(2, 5, 5);
 	wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
-	edit_separator = new wxTextCtrl(this, EDIT_ACTOR_SEPARATOR, Options.AsText(_T("text actor separator")));
-	edit_comment = new wxTextCtrl(this, EDIT_COMMENT_STARTER, Options.AsText(_T("text comment starter")));
+	edit_separator = new wxTextCtrl(this, EDIT_ACTOR_SEPARATOR, lagi_wxString(OPT_GET("Tool/Import/Text/Actor Separator")->GetString()));
+	edit_comment = new wxTextCtrl(this, EDIT_COMMENT_STARTER, lagi_wxString(OPT_GET("Tool/Import/Text/Comment Starter")->GetString()));
 
 	// Dialog layout
 	fg->Add(new wxStaticText(this, -1, _("Actor separator:")), 0, wxALIGN_CENTRE_VERTICAL);
@@ -85,9 +87,8 @@ DialogTextImport::~DialogTextImport()
 void DialogTextImport::OnOK(wxCommandEvent &event)
 {
 	// Set options
-	Options.SetText(_T("text actor separator"), edit_separator->GetValue());
-	Options.SetText(_T("text comment starter"), edit_comment->GetValue());
-	Options.Save();
+	OPT_SET("Tool/Import/Text/Actor Separator")->SetString(STD_STR(edit_separator->GetValue()));
+	OPT_SET("Tool/Import/Text/Comment Starter")->SetString(STD_STR(edit_comment->GetValue()));
 
 	EndModal(wxID_OK);
 }
diff --git a/aegisub/src/dialog_timing_processor.cpp b/aegisub/src/dialog_timing_processor.cpp
index 2d95f5d2b..83d55a26a 100644
--- a/aegisub/src/dialog_timing_processor.cpp
+++ b/aegisub/src/dialog_timing_processor.cpp
@@ -44,6 +44,7 @@
 #include "dialog_timing_processor.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "subs_grid.h"
 #include "utils.h"
@@ -66,13 +67,14 @@ DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_gr
 
 	// Set variables
 	grid = _grid;
-	leadInTime = Options.AsText(_T("Audio lead in"));
-	leadOutTime = Options.AsText(_T("Audio lead out"));
-	thresStartBefore = Options.AsText(_T("Timing processor key start before thres"));
-	thresStartAfter = Options.AsText(_T("Timing processor key start after thres"));
-	thresEndBefore = Options.AsText(_T("Timing processor key end before thres"));
-	thresEndAfter = Options.AsText(_T("Timing processor key end after thres"));
-	adjsThresTime = Options.AsText(_T("Timing processor adjacent thres"));
+	leadInTime = AegiIntegerToString(OPT_GET("Audio/Lead/IN")->GetInt());
+	leadOutTime = AegiIntegerToString(OPT_GET("Audio/Lead/OUT")->GetInt());
+	thresStartBefore = AegiIntegerToString(OPT_GET("Tool/Timing Post Processor/Threshold/Key Start Before")->GetInt());
+	thresStartAfter = AegiIntegerToString(OPT_GET("Tool/Timing Post Processor/Threshold/Key Start After")->GetInt());
+	thresEndBefore = AegiIntegerToString(OPT_GET("Tool/Timing Post Processor/Threshold/Key End Before")->GetInt());
+	thresEndAfter = AegiIntegerToString(OPT_GET("Tool/Timing Post Processor/Threshold/Key End After")->GetInt());
+	adjsThresTime = AegiIntegerToString(OPT_GET("Tool/Timing Post Processor/Threshold/Adjacent")->GetInt());
+
 
 	// Styles box
 	wxSizer *LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles"));
@@ -87,19 +89,19 @@ DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_gr
 	// Options box
 	wxSizer *optionsSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Options"));
 	onlySelection = new wxCheckBox(this,-1,_("Affect selection only"));
-	onlySelection->SetValue(Options.AsBool(_T("Timing processor Only Selection")));
+	onlySelection->SetValue(OPT_GET("Tool/Timing Post Processor/Only Selection")->GetBool());
 	optionsSizer->Add(onlySelection,1,wxALL,0);
 
 	// Lead-in/out box
 	wxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Lead-in/Lead-out"));
 	hasLeadIn = new wxCheckBox(this,CHECK_ENABLE_LEADIN,_("Add lead in:"));
 	hasLeadIn->SetToolTip(_("Enable adding of lead-ins to lines."));
-	hasLeadIn->SetValue(Options.AsBool(_T("Timing processor Enable lead-in")));
+	hasLeadIn->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Lead/IN")->GetBool());
 	leadIn = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(80,-1),0,NumValidator(&leadInTime));
 	leadIn->SetToolTip(_("Lead in to be added, in milliseconds."));
 	hasLeadOut = new wxCheckBox(this,CHECK_ENABLE_LEADOUT,_("Add lead out:"));
 	hasLeadOut->SetToolTip(_("Enable adding of lead-outs to lines."));
-	hasLeadOut->SetValue(Options.AsBool(_T("Timing processor Enable lead-out")));
+	hasLeadOut->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Lead/OUT")->GetBool());
 	leadOut = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(80,-1),0,NumValidator(&leadOutTime));
 	leadOut->SetToolTip(_("Lead out to be added, in milliseconds."));
 	LeadSizer->Add(hasLeadIn,0,wxRIGHT|wxEXPAND,5);
@@ -112,11 +114,11 @@ DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_gr
 	wxSizer *AdjacentSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Make adjacent subtitles continuous"));
 	adjsEnable = new wxCheckBox(this,CHECK_ENABLE_ADJASCENT,_("Enable"));
 	adjsEnable->SetToolTip(_("Enable snapping of subtitles together if they are within a certain distance of each other."));
-	adjsEnable->SetValue(Options.AsBool(_T("Timing processor Enable adjacent")));
+	adjsEnable->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Adjacent")->GetBool());
 	wxStaticText *adjsThresText = new wxStaticText(this,-1,_("Threshold:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE);
 	adjacentThres = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(60,-1),0,NumValidator(&adjsThresTime));
 	adjacentThres->SetToolTip(_("Maximum difference between start and end time for two subtitles to be made continuous, in milliseconds."));
-	adjacentBias = new wxSlider(this,-1,MID(0,int(Options.AsFloat(_T("Timing processor adjacent bias"))*100),100),0,100,wxDefaultPosition,wxSize(-1,20));
+	adjacentBias = new wxSlider(this,-1,MID(0,int(OPT_GET("Tool/Timing Post Processor/Adjacent Bias")->GetDouble()*100),100),0,100,wxDefaultPosition,wxSize(-1,20));
 	adjacentBias->SetToolTip(_("Sets how to set the adjoining of lines. If set totally to left, it will extend start time of the second line; if totally to right, it will extend the end time of the first line."));
 	AdjacentSizer->Add(adjsEnable,0,wxRIGHT|wxEXPAND,10);
 	AdjacentSizer->Add(adjsThresText,0,wxRIGHT|wxALIGN_CENTER,5);
@@ -130,7 +132,7 @@ DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_gr
 	wxSizer *KeyframesFlexSizer = new wxFlexGridSizer(2,5,5,0);
 	keysEnable = new wxCheckBox(this,CHECK_ENABLE_KEYFRAME,_("Enable"));
 	keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold."));
-	keysEnable->SetValue(Options.AsBool(_T("Timing processor Enable keyframe")));
+	keysEnable->SetValue(OPT_GET("Tool/Timing Post Processor/Enable/Keyframe")->GetBool());
 	wxStaticText *textStartBefore = new wxStaticText(this,-1,_("Starts before thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE);
 	keysStartBefore = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(60,-1),0,NumValidator(&thresStartBefore));
 	keysStartBefore->SetToolTip(_("Threshold for 'before start' distance, that is, how many frames a subtitle must start before a keyframe to snap to it."));
@@ -293,26 +295,25 @@ void DialogTimingProcessor::OnApply(wxCommandEvent &event) {
 	// Save settings
 	long temp = 0;
 	leadIn->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Audio lead in"),temp);
+	OPT_SET("Audio/Lead/IN")->SetInt(temp);
 	leadOut->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Audio lead out"),temp);
+	OPT_SET("Audio/Lead/OUT")->SetInt(temp);
 	keysStartBefore->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Timing processor key start before thres"),temp);
+	OPT_SET("Tool/Timing Post Processor/Threshold/Key Start Before")->SetInt(temp);
 	keysStartAfter->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Timing processor key start after thres"),temp);
+	OPT_SET("Tool/Timing Post Processor/Threshold/Key Start After")->SetInt(temp);
 	keysEndBefore->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Timing processor key end before thres"),temp);
+	OPT_SET("Tool/Timing Post Processor/Threshold/Key End Before")->SetInt(temp);
 	keysEndAfter->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Timing processor key end after thres"),temp);
+	OPT_SET("Tool/Timing Post Processor/Threshold/Key End After")->SetInt(temp);
 	adjacentThres->GetValue().ToLong(&temp);
-	Options.SetInt(_T("Timing processor adjacent thres"),temp);
-	Options.SetFloat(_T("Timing processor adjacent bias"),adjacentBias->GetValue() / 100.0);
-	Options.SetBool(_T("Timing processor Enable lead-in"),hasLeadIn->IsChecked());
-	Options.SetBool(_T("Timing processor Enable lead-out"),hasLeadOut->IsChecked());
-	if (keysEnable->IsEnabled()) Options.SetBool(_T("Timing processor Enable keyframe"),keysEnable->IsChecked());
-	Options.SetBool(_T("Timing processor Enable adjacent"),adjsEnable->IsChecked());
-	Options.SetBool(_T("Timing processor Only Selection"),onlySelection->IsChecked());
-	Options.Save();
+	OPT_SET("Tool/Timing Post Processor/Threshold/Adjacent")->SetInt(temp);
+	OPT_SET("Tool/Timing Post Processor/Adjacent Bias")->SetDouble(adjacentBias->GetValue() / 100.0);
+	OPT_SET("Tool/Timing Post Processor/Enable/Lead/IN")->SetBool(hasLeadIn->IsChecked());
+	OPT_SET("Tool/Timing Post Processor/Enable/Lead/OUT")->SetBool(hasLeadOut->IsChecked());
+	if (keysEnable->IsEnabled()) OPT_SET("Tool/Timing Post Processor/Enable/Keyframe")->SetBool(keysEnable->IsChecked());
+	OPT_SET("Tool/Timing Post Processor/Enable/Adjacent")->SetBool(adjsEnable->IsChecked());
+	OPT_SET("Tool/Timing Post Processor/Only Selection")->SetBool(onlySelection->IsChecked());
 
 	// Check if rows are valid
 	bool valid = true;
diff --git a/aegisub/src/dialog_tip.cpp b/aegisub/src/dialog_tip.cpp
index 478b40084..060bf324c 100644
--- a/aegisub/src/dialog_tip.cpp
+++ b/aegisub/src/dialog_tip.cpp
@@ -45,6 +45,7 @@
 #endif
 
 #include "dialog_tip.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -99,12 +100,11 @@ wxString TipOfTheDay::GetTip() {
 ///
 void TipOfTheDay::Show(wxWindow *parent) {
 	try {
-		if (Options.AsBool(_T("Tips enabled"))) {
-			TipOfTheDay *tip = new TipOfTheDay(Options.AsInt(_T("Tips current")));
+		if (OPT_GET("App/Tips")->GetBool()) {
+			TipOfTheDay *tip = new TipOfTheDay(OPT_GET("Tool/Tip of the Day/Current")->GetInt());
 			bool show = wxShowTip(parent, tip, true);
-			if (!show) Options.SetBool(_T("Tips enabled"),false);
-			Options.SetInt(_T("Tips current"),tip->curTip);
-			Options.Save();
+			if (!show) OPT_SET("App/Tips")->SetBool(false);
+			OPT_SET("Tool/Tip of the Day/Current")->SetInt(tip->curTip);
 			delete tip;
 		}
 	}
diff --git a/aegisub/src/dialog_version_check.cpp b/aegisub/src/dialog_version_check.cpp
index 56c6ed580..f32af9780 100644
--- a/aegisub/src/dialog_version_check.cpp
+++ b/aegisub/src/dialog_version_check.cpp
@@ -64,12 +64,14 @@
 #include <vector>
 #endif
 
+#include "compat.h"
 #include "dialog_version_check.h"
+#include "main.h"
 #include "options.h"
-#include "include/aegisub/exception.h"
 #include "string_codec.h"
 #include "version.h"
 
+#include <libaegisub/exception.h>
 
 /* *** Public API is implemented here *** */
 
@@ -138,7 +140,7 @@ public:
 
 };
 
-DEFINE_SIMPLE_EXCEPTION_NOINNER(VersionCheckError, Aegisub::Exception, "versioncheck")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(VersionCheckError, agi::Exception, "versioncheck")
 
 
 class AegisubVersionCheckEventHandler : public wxEvtHandler {
@@ -169,11 +171,11 @@ wxThread::ExitCode AegisubVersionCheckerThread::Entry()
 	if (!interactive)
 	{
 		// Automatic checking enabled?
-		if (!Options.AsBool(_T("auto check for updates")))
+		if (!OPT_GET("App/Auto/Check For Updates")->GetInt())
 			return 0;
 
 		// Is it actually time for a check?
-		time_t next_check = Options.AsInt(_T("Updates Next Check Time"));
+		time_t next_check = OPT_GET("Version/Next Check")->GetInt();
 		if ((time_t)next_check > wxDateTime::GetTimeNow())
 			return 0;
 	}
@@ -183,10 +185,10 @@ wxThread::ExitCode AegisubVersionCheckerThread::Entry()
 	try {
 		DoCheck();
 	}
-	catch (const Aegisub::Exception &e) {
+	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().c_str()));
+			e.GetMessage()));
 	}
 	catch (...) {
 		PostErrorEvent(_("An unknown error occurred while checking for updates to Aegisub."));
@@ -201,7 +203,7 @@ wxThread::ExitCode AegisubVersionCheckerThread::Entry()
 	// 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
-	Options.SetInt(_T("Updates Next Check Time"), (int)new_next_check_time);
+	OPT_SET("Version/Next Check")->SetInt((int)new_next_check_time);
 
 	return 0;
 }
@@ -338,12 +340,14 @@ void AegisubVersionCheckerThread::DoCheck()
 	http.SetFlags(wxSOCKET_WAITALL|wxSOCKET_BLOCK);
 
 	if (!http.Connect(servername))
-		throw VersionCheckError(_("Could not connect to updates server."));
+		throw VersionCheckError(STD_STR(_("Could not connect to updates server.")));
 
 	std::auto_ptr<wxInputStream> stream(http.GetInputStream(path));
 
-	if (http.GetResponse() < 200 || http.GetResponse() >= 300)
-		throw VersionCheckError(wxString::Format(_("HTTP request failed, got HTTP response %d."), http.GetResponse()));
+	if (http.GetResponse() < 200 || http.GetResponse() >= 300) {
+		const std::string str_err = STD_STR(wxString::Format(_("HTTP request failed, got HTTP response %d."), http.GetResponse()));
+		throw VersionCheckError(str_err);
+	}
 
 	wxTextInputStream text(*stream);
 
@@ -485,7 +489,7 @@ VersionCheckerResultDialog::VersionCheckerResultDialog(const wxString &main_text
 	}
 
 	automatic_check_checkbox = new wxCheckBox(this, -1, _("Auto Check for Updates"));
-	automatic_check_checkbox->SetValue(Options.AsBool(_T("Auto check for updates")));
+	automatic_check_checkbox->SetValue(!!OPT_GET("App/Auto/Check For Updates")->GetInt());
 
 	wxButton *remind_later_button = 0;
 	if (updates.size() > 0)
@@ -523,7 +527,7 @@ void VersionCheckerResultDialog::OnRemindMeLater(wxCommandEvent &evt)
 {
 	// In one week
 	time_t new_next_check_time = wxDateTime::Today().GetTicks() + 7*24*60*60;
-	Options.SetInt(_T("Updates Next Check Time"), (int)new_next_check_time);
+	OPT_SET("Version/Next Check")->SetInt((int)new_next_check_time);
 
 	Close();
 }
@@ -531,7 +535,7 @@ void VersionCheckerResultDialog::OnRemindMeLater(wxCommandEvent &evt)
 
 void VersionCheckerResultDialog::OnClose(wxCloseEvent &evt)
 {
-	Options.SetBool(_T("Auto check for updates"), automatic_check_checkbox->GetValue());
+	OPT_SET("App/Auto/Check For Updates")->SetBool(automatic_check_checkbox->GetValue());
 	Destroy();
 }
 
diff --git a/aegisub/src/ffmpegsource_common.cpp b/aegisub/src/ffmpegsource_common.cpp
index 4313b08ce..eced81f7f 100644
--- a/aegisub/src/ffmpegsource_common.cpp
+++ b/aegisub/src/ffmpegsource_common.cpp
@@ -46,6 +46,7 @@
 #include <wx/choicdlg.h> // Keep this last so wxUSE_CHOICEDLG is set.
 #endif
 
+#include "compat.h"
 #include "ffmpegsource_common.h"
 #include "frame_main.h"
 #include "main.h"
@@ -176,7 +177,7 @@ 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 = Options.AsText(_T("FFmpegSource log level"));
+	wxString LogLevel = lagi_wxString(OPT_GET("Provider/FFmpegSource/Log Level")->GetString());
 
 	if (!LogLevel.CmpNoCase(_T("panic")))
 		FFMS_SetLogLevel(FFMS_LOG_PANIC);
@@ -198,7 +199,7 @@ void FFmpegSourceProvider::SetLogLevel() {
 
 
 FFMS_IndexErrorHandling FFmpegSourceProvider::GetErrorHandlingMode() {
-	wxString Mode = Options.AsText(_T("FFmpegSource audio decoding error handling"));
+	wxString Mode = lagi_wxString(OPT_GET("Provider/Audio/FFMpegSource/Decode Error Handling")->GetString());
 
 	if (!Mode.CmpNoCase(_T("ignore")))
 		return FFMS_IEH_IGNORE;
@@ -310,9 +311,9 @@ wxThread::ExitCode FFmpegSourceCacheCleaner::Entry() {
 
 	// the option is in megabytes, we need bytes
 	// shift left by 20 is CLEARLY more efficient than multiplying by 1048576
-	int64_t maxsize = Options.AsInt(_T("FFmpegSource max cache size")) << 20;
+	int64_t maxsize = OPT_GET("Provider/FFmpegSource/Cache/Size")->GetInt() << 20;
 	int64_t cursize = wxDir::GetTotalSize(cachedirname).GetValue();
-	int maxfiles	= Options.AsInt(_T("FFmpegSource max cache files"));
+	int maxfiles	= OPT_GET("Provider/FFmpegSource/Cache/Files")->GetInt();
 
 	if (!cachedir.HasFiles(_T("*.ffindex"))) {
 		wxLogDebug(_T("FFmpegSourceCacheCleaner: no index files in cache folder, exiting"));
diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp
index e7afabf8e..b4e8227ca 100644
--- a/aegisub/src/frame_main.cpp
+++ b/aegisub/src/frame_main.cpp
@@ -62,6 +62,7 @@
 #include "avisynth_wrap.h"
 #endif
 #include "charset_conv.h"
+#include "compat.h"
 #include "dialog_detached_video.h"
 #include "dialog_search_replace.h"
 #include "dialog_splash.h"
@@ -145,7 +146,7 @@ FrameMain::FrameMain (wxArrayString args)
 
 	// Create menu and tool bars
 	StartupLog(_T("Apply saved Maximized state"));
-	if (Options.AsBool(_T("Maximized"))) Maximize(true);
+	if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
 	StartupLog(_T("Initialize toolbar"));
 	InitToolbar();
 	StartupLog(_T("Initialize menu bar"));
@@ -179,7 +180,7 @@ FrameMain::FrameMain (wxArrayString args)
 	// It doesn't work properly on wxMac, and the jumping dock icon
 	// signals the same as the splash screen either way.
 #if !_DEBUG && !__WXMAC__
-	if (Options.AsBool(_T("Show Splash"))) {
+	if (OPT_GET("App/Splash")->GetBool()) {
 		SplashScreen *splash = new SplashScreen(this);
 		splash->Show(true);
 		splash->Update();
@@ -192,7 +193,7 @@ FrameMain::FrameMain (wxArrayString args)
 	// Set autosave timer
 	StartupLog(_T("Set up Auto Save"));
 	AutoSave.SetOwner(this,AutoSave_Timer);
-	int time = Options.AsInt(_T("Auto save every seconds"));
+	int time = OPT_GET("App/Auto/Save Every Seconds")->GetInt();
 	if (time > 0) {
 		AutoSave.Start(time*1000);
 	}
@@ -214,12 +215,11 @@ FrameMain::FrameMain (wxArrayString args)
 
 	// Version checker
 	StartupLog(_T("Possibly perform automatic updates check"));
-	int option = Options.AsInt(_T("Auto check for updates"));
+	int option = OPT_GET("App/Auto/Check For Updates")->GetInt();
 	if (option == -1) {
 		int result = wxMessageBox(_("Do you want Aegisub to check for updates whenever it starts? You can still do it manually via the Help menu."),_("Check for updates?"),wxYES_NO);
 		option = (result == wxYES);
-		Options.SetInt(_T("Auto check for updates"),option);
-		Options.Save();
+		OPT_SET("App/Auto/Check For Updates")->SetInt(option);
 	}
 
 	PerformVersionCheck(false);
@@ -609,7 +609,7 @@ void FrameMain::InitContents() {
 	videoBox->videoSlider->grid = SubsBox;
 	VideoContext::Get()->grid = SubsBox;
 	StartupLog(_T("Reset video zoom"));
-	videoBox->videoDisplay->SetZoom(Options.AsInt(_T("Video Default Zoom")) * .125 + .125);
+	videoBox->videoDisplay->SetZoom(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125);
 	Search.grid = SubsBox;
 
 	// Audio area
@@ -718,7 +718,7 @@ void FrameMain::LoadSubtitles (wxString filename,wxString charset) {
 					wxString cur = testSubs.ReadLineFromFile();
 					if (cur.Left(10) == _T("# timecode")) {
 						LoadVFR(filename);
-						Options.SetText(_T("Last open timecodes path"), fileCheck.GetPath());
+						OPT_SET("Path/Last/Timecodes")->SetString(STD_STR(fileCheck.GetPath()));
 						return;
 					}
 				}
@@ -737,7 +737,7 @@ void FrameMain::LoadSubtitles (wxString filename,wxString charset) {
 			SubsBox->LoadFromAss(AssFile::top,false,true);
 			wxFileName fn(filename);
 			StandardPaths::SetPathValue(_T("?script"),fn.GetPath());
-			Options.SetText(_T("Last open subtitles path"), fn.GetPath());
+			OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(fn.GetPath()));
 		}
 		else {
 			SubsBox->LoadDefault(AssFile::top);
@@ -759,9 +759,9 @@ void FrameMain::LoadSubtitles (wxString filename,wxString charset) {
 
 	// Save copy
 	wxFileName origfile(filename);
-	if (!isBinary && Options.AsBool(_T("Auto backup")) && origfile.FileExists()) {
+	if (!isBinary && OPT_GET("App/Auto/Backup")->GetBool() && origfile.FileExists()) {
 		// Get path
-		wxString path = Options.AsText(_T("Auto backup path"));
+		wxString path = lagi_wxString(OPT_GET("Path/Auto/Backup")->GetString());
 		if (path.IsEmpty()) path = origfile.GetPath();
 		wxFileName dstpath(path);
 		if (!dstpath.IsAbsolute()) path = StandardPaths::DecodePathMaybeRelative(path, _T("?user/"));
@@ -796,7 +796,7 @@ bool FrameMain::SaveSubtitles(bool saveas,bool withCharset) {
 	// Failed, ask user
 	if (filename.IsEmpty()) {
 		VideoContext::Get()->Stop();
-		wxString path = Options.AsText(_T("Last open subtitles path"));
+		wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());
 		wxFileName origPath(AssFile::top->filename);
 		filename = 	wxFileSelector(_("Save subtitles file"),path,origPath.GetName() + _T(".ass"),_T("ass"),AssFile::GetWildcardList(1),wxFD_SAVE | wxFD_OVERWRITE_PROMPT,this);
 	}
@@ -805,7 +805,7 @@ bool FrameMain::SaveSubtitles(bool saveas,bool withCharset) {
 	if (!filename.empty()) {
 		// Store path
 		wxFileName filepath(filename);
-		Options.SetText(_T("Last open subtitles path"), filepath.GetPath());
+		OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(filepath.GetPath()));
 
 		// Fix me, ghetto hack for correct relative path generation in SynchronizeProject()
 		AssFile::top->filename = filename;
@@ -979,7 +979,7 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
 		wxString AutoScriptString = subs->GetScriptInfo(_T("Automation Scripts"));
 
 		// Check if there is anything to change
-		int autoLoadMode = Options.AsInt(_T("Autoload linked files"));
+		int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
 		bool hasToLoad = false;
 		if (curSubsAudio != audioBox->audioName ||
 			curSubsVFR != VFR_Output.GetFilename() ||
@@ -1034,7 +1034,7 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
 			local_scripts->RemoveAll();
 			wxStringTokenizer tok(AutoScriptString, _T("|"), wxTOKEN_STRTOK);
 			wxFileName subsfn(subs->filename);
-			wxString autobasefn(Options.AsText(_T("Automation Base Path")));
+			wxString autobasefn(lagi_wxString(OPT_GET("Path/Automation/Base")->GetString()));
 			while (tok.HasMoreTokens()) {
 				wxString sfnames = tok.GetNextToken().Trim(true).Trim(false);
 				wxString sfnamel = sfnames.Left(1);
@@ -1102,7 +1102,7 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
 		// 4. Otherwise, use path relative to subs ("~")
 #ifdef WITH_AUTOMATION
 		wxString scripts_string;
-		wxString autobasefn(Options.AsText(_T("Automation Base Path")));
+		wxString autobasefn(lagi_wxString(OPT_GET("Path/Automation/Base")->GetString()));
 
 		const std::vector<Automation4::Script*> &scripts = local_scripts->GetScripts();
 		for (unsigned int i = 0; i < scripts.size(); i++) {
@@ -1182,7 +1182,7 @@ void FrameMain::LoadVideo(wxString file,bool autoload) {
 		int scriptx = SubsBox->ass->GetScriptInfoAsInt(_T("PlayResX"));
 		int scripty = SubsBox->ass->GetScriptInfoAsInt(_T("PlayResY"));
 		if (scriptx != vidx || scripty != vidy) {
-			switch (Options.AsInt(_T("Video Check Script Res"))) {
+			switch (OPT_GET("Video/Check Script Res")->GetInt()) {
 				case 1:
 					// Ask to change on mismatch
 					if (wxMessageBox(wxString::Format(_("The resolution of the loaded video and the resolution specified for the subtitles don't match.\n\nVideo resolution:\t%d x %d\nScript resolution:\t%d x %d\n\nChange subtitles resolution to match video?"), vidx, vidy, scriptx, scripty), _("Resolution mismatch"), wxYES_NO, this) != wxYES)
@@ -1207,7 +1207,7 @@ void FrameMain::LoadVideo(wxString file,bool autoload) {
 	SetDisplayMode(1,-1);
 	EditBox->UpdateFrameTiming();
 
-	DetachVideo(VideoContext::Get()->IsLoaded() && Options.AsBool(_T("Detached Video")));
+	DetachVideo(VideoContext::Get()->IsLoaded() && OPT_GET("Video/Detached/Enabled")->GetBool());
 	Thaw();
 }
 
@@ -1345,7 +1345,7 @@ void FrameMain::SetAccelerators() {
 	entry.push_back(Hotkeys.GetAccelerator(_T("Edit box commit"),Edit_Box_Commit));
 
 	// Medusa
-	bool medusaPlay = Options.AsBool(_T("Audio Medusa Timing Hotkeys"));
+	bool medusaPlay = OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool();
 	if (medusaPlay && audioBox->audioDisplay->loaded) {
 		entry.push_back(Hotkeys.GetAccelerator(_T("Audio Medusa Play"),Medusa_Play));
 		entry.push_back(Hotkeys.GetAccelerator(_T("Audio Medusa Stop"),Medusa_Stop));
diff --git a/aegisub/src/frame_main.h b/aegisub/src/frame_main.h
index bb029c3a7..fb4974585 100644
--- a/aegisub/src/frame_main.h
+++ b/aegisub/src/frame_main.h
@@ -295,7 +295,7 @@ private:
 	void OnOpenVideoDetails (wxCommandEvent &event);
 	void OnOpenASSDraw (wxCommandEvent &event);
 
-	void OnOpenOptions (wxCommandEvent &event);
+	void OnOpenPreferences (wxCommandEvent &event);
 	void OnGridEvent (wxCommandEvent &event);
 
 	void OnOpenAutomation (wxCommandEvent &event);
diff --git a/aegisub/src/frame_main_events.cpp b/aegisub/src/frame_main_events.cpp
index 5dbf9e8e9..e61edb241 100644
--- a/aegisub/src/frame_main_events.cpp
+++ b/aegisub/src/frame_main_events.cpp
@@ -57,6 +57,7 @@
 #include "auto4_base.h"
 #endif
 #include "charset_conv.h"
+#include "compat.h"
 #include "dialog_about.h"
 #include "dialog_attachments.h"
 #include "dialog_automation.h"
@@ -65,7 +66,6 @@
 #include "dialog_fonts_collector.h"
 #include "dialog_jumpto.h"
 #include "dialog_kara_timing_copy.h"
-#include "dialog_options.h"
 #include "dialog_progress.h"
 #include "dialog_properties.h"
 #include "dialog_resample.h"
@@ -80,10 +80,12 @@
 #include "dialog_version_check.h"
 #include "dialog_video_details.h"
 #include "frame_main.h"
+#include "hotkeys.h"
 #include "keyframe.h"
 #include "libresrc/libresrc.h"
 #include "main.h"
 #include "options.h"
+#include "preferences.h"
 #include "standard_paths.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -192,7 +194,7 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame)
 	EVT_MENU(Menu_Tools_Resample, FrameMain::OnOpenResample)
 	EVT_MENU(Menu_Tools_Timing_Processor, FrameMain::OnOpenTimingProcessor)
 	EVT_MENU(Menu_Tools_Kanji_Timer, FrameMain::OnOpenKanjiTimer)
-	EVT_MENU(Menu_Tools_Options, FrameMain::OnOpenOptions)
+	EVT_MENU(Menu_Tools_Options, FrameMain::OnOpenPreferences)
 	EVT_MENU(Menu_Tools_ASSDraw, FrameMain::OnOpenASSDraw)
 	
 	EVT_MENU(Menu_Subs_Snap_Start_To_Video, FrameMain::OnSnapSubsStartToVid)
@@ -272,7 +274,7 @@ void FrameMain::RebuildRecentList(wxString listName,wxMenu *menu,int startID) {
 	// Rebuild
 	int added = 0;
 	wxString n;
-	wxArrayString entries = Options.GetRecentList(listName);
+	wxArrayString entries = lagi_MRU_wxAS(listName);
 	for (size_t i=0;i<entries.Count();i++) {
 		n = wxString::Format(_T("%i"),i+1);
 		if (i < 9) n = _T("&") + n;
@@ -299,7 +301,7 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
 	// File menu
 	if (curMenu == fileMenu) {
 		// Rebuild recent
-		RebuildRecentList(_T("Recent sub"),RecentSubs,Menu_File_Recent);
+		RebuildRecentList(_T("Subtitle"),RecentSubs,Menu_File_Recent);
 
 		MenuBar->Enable(Menu_File_Open_Subtitles_From_Video,VideoContext::Get()->HasSubtitles());
 	}
@@ -321,7 +323,7 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
 		else if (showAudio && showVideo) MenuBar->Check(Menu_View_Standard,true);
 		else MenuBar->Check(Menu_View_Audio,true);
 
-		MenuBar->Check(Options.AsInt(L"Grid hide overrides") + Menu_View_FullTags, true);
+		MenuBar->Check(OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt() + Menu_View_FullTags, true);
 	}
 
 	// Video menu
@@ -368,12 +370,12 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
 		}
 
 		// Set overscan mask
-		MenuBar->Check(Menu_Video_Overscan,Options.AsBool(_T("Show Overscan Mask")));
+		MenuBar->Check(Menu_Video_Overscan,OPT_GET("Video/Overscan Mask")->GetBool());
 
 		// Rebuild recent lists
-		RebuildRecentList(_T("Recent vid"),RecentVids,Menu_Video_Recent);
-		RebuildRecentList(_T("Recent timecodes"),RecentTimecodes,Menu_Timecodes_Recent);
-		RebuildRecentList(_T("Recent Keyframes"),RecentKeyframes,Menu_Keyframes_Recent);
+		RebuildRecentList(_T("Video"),RecentVids,Menu_Video_Recent);
+		RebuildRecentList(_T("Timecodes"),RecentTimecodes,Menu_Timecodes_Recent);
+		RebuildRecentList(_T("Keyframes"),RecentKeyframes,Menu_Keyframes_Recent);
 	}
 
 	// Audio menu
@@ -385,7 +387,7 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
 		MenuBar->Enable(Menu_Audio_Close,state);
 
 		// Rebuild recent
-		RebuildRecentList(_T("Recent aud"),RecentAuds,Menu_Audio_Recent);
+		RebuildRecentList(_T("Audio"),RecentAuds,Menu_Audio_Recent);
 	}
 
 	// Subtitles menu
@@ -534,8 +536,7 @@ int FrameMain::AddMacroMenuItems(wxMenu *menu, const std::vector<Automation4::Fe
 ///
 void FrameMain::OnOpenRecentSubs(wxCommandEvent &event) {
 	int number = event.GetId()-Menu_File_Recent;
-	wxString key = _T("Recent sub #") + wxString::Format(_T("%i"),number+1);
-	LoadSubtitles(Options.AsText(key));
+	LoadSubtitles(AegisubApp::Get()->mru->GetEntry("Subtitle", number));
 }
 
 
@@ -545,8 +546,7 @@ void FrameMain::OnOpenRecentSubs(wxCommandEvent &event) {
 ///
 void FrameMain::OnOpenRecentVideo(wxCommandEvent &event) {
 	int number = event.GetId()-Menu_Video_Recent;
-	wxString key = _T("Recent vid #") + wxString::Format(_T("%i"),number+1);
-	LoadVideo(Options.AsText(key));
+	LoadSubtitles(AegisubApp::Get()->mru->GetEntry("Video", number));
 }
 
 
@@ -556,8 +556,7 @@ void FrameMain::OnOpenRecentVideo(wxCommandEvent &event) {
 ///
 void FrameMain::OnOpenRecentTimecodes(wxCommandEvent &event) {
 	int number = event.GetId()-Menu_Timecodes_Recent;
-	wxString key = _T("Recent timecodes #") + wxString::Format(_T("%i"),number+1);
-	LoadVFR(Options.AsText(key));
+	LoadSubtitles(AegisubApp::Get()->mru->GetEntry("Timecodes", number));
 }
 
 
@@ -567,8 +566,7 @@ void FrameMain::OnOpenRecentTimecodes(wxCommandEvent &event) {
 ///
 void FrameMain::OnOpenRecentKeyframes(wxCommandEvent &event) {
 	int number = event.GetId()-Menu_Keyframes_Recent;
-	wxString key = _T("Recent Keyframes #") + wxString::Format(_T("%i"),number+1);
-	KeyFrameFile::Load(Options.AsText(key));
+	LoadSubtitles(AegisubApp::Get()->mru->GetEntry("Keyframes", number));
 	videoBox->videoSlider->Refresh();
 	audioBox->audioDisplay->Update();
 	Refresh();
@@ -581,8 +579,7 @@ void FrameMain::OnOpenRecentKeyframes(wxCommandEvent &event) {
 ///
 void FrameMain::OnOpenRecentAudio(wxCommandEvent &event) {
 	int number = event.GetId()-Menu_Audio_Recent;
-	wxString key = _T("Recent aud #") + wxString::Format(_T("%i"),number+1);
-	LoadAudio(Options.AsText(key));
+	LoadSubtitles(AegisubApp::Get()->mru->GetEntry("Audio", number));
 }
 
 
@@ -706,14 +703,13 @@ void FrameMain::OnVideoPlay(wxCommandEvent &event) {
 /// @param event 
 ///
 void FrameMain::OnOpenVideo(wxCommandEvent& WXUNUSED(event)) {
-	wxString path = Options.AsText(_T("Last open video path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Video")->GetString());
 	wxString str = wxString(_("Video Formats")) + _T(" (*.avi,*.mkv,*.mp4,*.avs,*.d2v,*.ogm,*.mpeg,*.mpg,*.vob,*.mov)|*.avi;*.avs;*.d2v;*.mkv;*.ogm;*.mp4;*.mpeg;*.mpg;*.vob;*.mov|")
 				 + _("All Files") + _T(" (*.*)|*.*");
 	wxString filename = wxFileSelector(_("Open video file"),path,_T(""),_T(""),str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (!filename.empty()) {
 		LoadVideo(filename);
-		Options.SetText(_T("Last open video path"), filename);
-		Options.Save();
+		OPT_SET("Path/Last/Video")->SetString(STD_STR(filename));
 	}
 }
 
@@ -732,15 +728,14 @@ void FrameMain::OnCloseVideo(wxCommandEvent& WXUNUSED(event)) {
 /// @param event 
 ///
 void FrameMain::OnOpenAudio (wxCommandEvent& WXUNUSED(event)) {
-	wxString path = Options.AsText(_T("Last open audio path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Audio")->GetString());
 	wxString str = wxString(_("Audio Formats")) + _T(" (*.wav,*.mp3,*.ogg,*.flac,*.mp4,*.ac3,*.aac,*.mka,*.m4a,*.w64)|*.wav;*.mp3;*.ogg;*.flac;*.mp4;*.ac3;*.aac;*.mka;*.m4a;*.w64|")
 		         + _("Video Formats") + _T(" (*.avi,*.mkv,*.ogm,*.mpg,*.mpeg)|*.avi;*.mkv;*.ogm;*.mp4;*.mpeg;*.mpg|")
 				 + _("All files") + _T(" (*.*)|*.*");
 	wxString filename = wxFileSelector(_("Open audio file"),path,_T(""),_T(""),str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (!filename.empty()) {
 		LoadAudio(filename);
-		Options.SetText(_T("Last open audio path"), filename);
-		Options.Save();
+		OPT_SET("Path/Last/Audio")->SetString(STD_STR(filename));
 	}
 }
 
@@ -785,13 +780,12 @@ void FrameMain::OnOpenDummyNoiseAudio (wxCommandEvent& WXUNUSED(event)) {
 /// @param event 
 ///
 void FrameMain::OnOpenSubtitles(wxCommandEvent& WXUNUSED(event)) {
-	wxString path = Options.AsText(_T("Last open subtitles path"));	
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());	
 	wxString filename = wxFileSelector(_("Open subtitles file"),path,_T(""),_T(""),AssFile::GetWildcardList(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (!filename.empty()) {
 		LoadSubtitles(filename);
 		wxFileName filepath(filename);
-		Options.SetText(_T("Last open subtitles path"), filepath.GetPath());
-		Options.Save();
+		OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(filepath.GetPath()));
 	}
 }
 
@@ -803,7 +797,7 @@ void FrameMain::OnOpenSubtitles(wxCommandEvent& WXUNUSED(event)) {
 void FrameMain::OnOpenSubtitlesCharset(wxCommandEvent& WXUNUSED(event)) {
 	// Initialize charsets
 	wxArrayString choices = AegisubCSConv::GetEncodingsList();
-	wxString path = Options.AsText(_T("Last open subtitles path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Subtitles")->GetString());
 
 	// Get options and load
 	wxString filename = wxFileSelector(_("Open subtitles file"),path,_T(""),_T(""),AssFile::GetWildcardList(0),wxFD_OPEN | wxFD_FILE_MUST_EXIST);
@@ -812,8 +806,7 @@ void FrameMain::OnOpenSubtitlesCharset(wxCommandEvent& WXUNUSED(event)) {
 		if (!charset.empty()) {
 			LoadSubtitles(filename,charset);
 		}
-		Options.SetText(_T("Last open subtitles path"), filename);
-		Options.Save();
+		OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(filename));
 	}
 }
 
@@ -865,7 +858,7 @@ void FrameMain::OnNewSubtitles(wxCommandEvent& WXUNUSED(event)) {
 ///
 void FrameMain::OnExportSubtitles(wxCommandEvent & WXUNUSED(event)) {
 #ifdef WITH_AUTOMATION
-	int autoreload = Options.AsInt(_T("Automation Autoreload Mode"));
+	int autoreload = OPT_GET("Automation/Autoreload Mode")->GetInt();
 	if (autoreload & 1) {
 		// Local scripts
 		const std::vector<Automation4::Script*> scripts = local_scripts->GetScripts();
@@ -897,14 +890,13 @@ void FrameMain::OnExportSubtitles(wxCommandEvent & WXUNUSED(event)) {
 /// @param event 
 ///
 void FrameMain::OnOpenVFR(wxCommandEvent &event) {
-	wxString path = Options.AsText(_T("Last open timecodes path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Timecodes")->GetString());
 	wxString str = wxString(_("All Supported Types")) + _T("(*.txt)|*.txt|")
 		           + _("All Files") + _T(" (*.*)|*.*");
 	wxString filename = wxFileSelector(_("Open timecodes file"),path,_T(""),_T(""),str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 	if (!filename.empty()) {
 		LoadVFR(filename);
-		Options.SetText(_T("Last open timecodes path"), filename);
-		Options.Save();
+		OPT_SET("Path/Last/Timecodes")->SetString(STD_STR(filename));
 	}
 }
 
@@ -914,14 +906,13 @@ void FrameMain::OnOpenVFR(wxCommandEvent &event) {
 /// @param event 
 ///
 void FrameMain::OnSaveVFR(wxCommandEvent &event) {
-	wxString path = Options.AsText(_T("Last open timecodes path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Timecodes")->GetString());
 	wxString str = wxString(_("All Supported Types")) + _T("(*.txt)|*.txt|")
 		           + _("All Files") + _T(" (*.*)|*.*");
 	wxString filename = wxFileSelector(_("Save timecodes file"),path,_T(""),_T(""),str,wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
 	if (!filename.empty()) {
 		SaveVFR(filename);
-		Options.SetText(_T("Last open timecodes path"), filename);
-		Options.Save();
+		OPT_SET("Path/Last/Timecodes")->SetString(STD_STR(filename));
 	}
 }
 
@@ -943,11 +934,10 @@ void FrameMain::OnCloseVFR(wxCommandEvent &event) {
 ///
 void FrameMain::OnOpenKeyframes (wxCommandEvent &event) {
 	// Pick file
-	wxString path = Options.AsText(_T("Last open keyframes path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Keyframes")->GetString());
 	wxString filename = wxFileSelector(_T("Select the keyframes file to open"),path,_T(""),_T(".txt"),_T("All supported formats (*.txt, *.pass, *.stats, *.log)|*.txt;*.pass;*.stats;*.log|All files (*.*)|*.*"),wxFD_FILE_MUST_EXIST | wxFD_OPEN);
 	if (filename.IsEmpty()) return;
-	Options.SetText(_T("Last open keyframes path"),filename);
-	Options.Save();
+	OPT_SET("Path/Last/Keyframes")->SetString(STD_STR(filename));
 
 	// Load
 	KeyFrameFile::Load(filename);
@@ -976,11 +966,10 @@ void FrameMain::OnCloseKeyframes (wxCommandEvent &event) {
 ///
 void FrameMain::OnSaveKeyframes (wxCommandEvent &event) {
 	// Pick file
-	wxString path = Options.AsText(_T("Last open keyframes path"));
+	wxString path = lagi_wxString(OPT_GET("Path/Last/Keyframes")->GetString());
 	wxString filename = wxFileSelector(_T("Select the Keyframes file to open"),path,_T(""),_T("*.key.txt"),_T("Text files (*.txt)|*.txt"),wxFD_OVERWRITE_PROMPT | wxFD_SAVE);
 	if (filename.IsEmpty()) return;
-	Options.SetText(_T("Last open keyframes path"),filename);
-	Options.Save();
+	OPT_SET("Path/Last/Keyframes")->SetString(STD_STR(filename));
 
 	// Save
 	KeyFrameFile::Save(filename);
@@ -1067,8 +1056,7 @@ void FrameMain::OnDummyVideo (wxCommandEvent &event) {
 /// @param event 
 ///
 void FrameMain::OnOverscan (wxCommandEvent &event) {
-	Options.SetBool(_T("Show overscan mask"),event.IsChecked());
-	Options.Save();
+	OPT_SET("Video/Overscan Mask")->SetBool(event.IsChecked());
 	VideoContext::Get()->Stop();
 	videoBox->videoDisplay->Render();
 }
@@ -1217,13 +1205,13 @@ void FrameMain::OnOpenKanjiTimer (wxCommandEvent &event) {
 /// @brief Open Options dialog 
 /// @param event 
 ///
-void FrameMain::OnOpenOptions (wxCommandEvent &event) {
+void FrameMain::OnOpenPreferences (wxCommandEvent &event) {
 	try {
-		DialogOptions options(this);
-		options.ShowModal();
-	}
-	catch (const wxChar *e) {
-		wxLogError(e);
+		Preferences pref(this);
+		pref.ShowModal();
+
+	} catch (agi::Exception& e) {
+		wxPrintf("Caught agi::Exception: %s -> %s\n", e.GetName(), e.GetMessage());
 	}
 }
 
@@ -1615,8 +1603,7 @@ void FrameMain::OnCloseWindow (wxCloseEvent &event) {
 	int result = TryToCloseSubs(canVeto);
 
 	// Store maximization state
-	Options.SetBool(_T("Maximized"),IsMaximized());
-	Options.Save();
+	OPT_SET("App/Maximized")->SetBool(IsMaximized());
 
 	// Abort/destroy
 	if (canVeto) {
@@ -1738,7 +1725,7 @@ void FrameMain::OnAutoSave(wxTimerEvent &event) {
 		if (AssFile::top->loaded) {
 			// Set path
 			wxFileName origfile(AssFile::top->filename);
-			wxString path = Options.AsText(_T("Auto save path"));
+			wxString path = lagi_wxString(OPT_GET("Path/Auto/Save")->GetString());
 			if (path.IsEmpty()) path = origfile.GetPath();
 			wxFileName dstpath(path);
 			if (!dstpath.IsAbsolute()) path = StandardPaths::DecodePathMaybeRelative(path, _T("?user/"));
@@ -1848,7 +1835,7 @@ void FrameMain::OnNextLine(wxCommandEvent &event) {
 
 /// @brief Cycle through tag hiding modes 
 void FrameMain::OnToggleTags(wxCommandEvent &) {
-	int tagMode = Options.AsInt(_T("Grid hide overrides"));
+	int tagMode = OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt();
 
 	// Cycle to next
 	tagMode = (tagMode+1)%3;
@@ -1861,15 +1848,13 @@ void FrameMain::OnToggleTags(wxCommandEvent &) {
 	StatusTimeout(message,10000);
 
 	// Set option
-	Options.SetInt(_T("Grid hide overrides"),tagMode);
-	Options.Save();
+	OPT_SET("Subtitle/Grid/Hide Overrides")->SetInt(tagMode);
 
 	// Refresh grid
 	SubsBox->Refresh(false);
 }
 void FrameMain::OnSetTags(wxCommandEvent &event) {
-	Options.SetInt(_T("Grid hide overrides"), event.GetId() - Menu_View_FullTags);
-	Options.Save();
+	OPT_SET("Subtitle/Grid/Hide Overrides")->SetInt(event.GetId() - Menu_View_FullTags);
 	SubsBox->Refresh(false);
 }
 
@@ -1914,8 +1899,7 @@ void FrameMain::OnChooseLanguage (wxCommandEvent &event) {
 	// Is OK?
 	if (newCode != -1) {
 		// Set code
-		Options.SetInt(_T("Locale Code"),newCode);
-		Options.Save();
+		OPT_SET("App/Locale")->SetInt(newCode);
 
 		// Language actually changed?
 		if (newCode != old) {
diff --git a/aegisub/src/hilimod_textctrl.cpp b/aegisub/src/hilimod_textctrl.cpp
index b2fcf8f65..0d841317e 100644
--- a/aegisub/src/hilimod_textctrl.cpp
+++ b/aegisub/src/hilimod_textctrl.cpp
@@ -39,7 +39,9 @@
 // Includes
 #include "config.h"
 
+#include "compat.h"
 #include "hilimod_textctrl.h"
+#include "main.h"
 #include "options.h"
 
 
@@ -111,7 +113,7 @@ void HiliModTextCtrl::Modified() {
 	// Different from original
 	if (!isModified && !match) {
 		isModified = true;
-		SetBackgroundColour(Options.AsColour(_T("Edit Box Need Enter Background")));
+		SetBackgroundColour(lagi_wxColour(OPT_GET("Colour/Background/Modified")->GetColour()));
 		Refresh(false);
 	}
 
diff --git a/aegisub/src/keyframe.cpp b/aegisub/src/keyframe.cpp
index f4f4d2f27..300d81c12 100644
--- a/aegisub/src/keyframe.cpp
+++ b/aegisub/src/keyframe.cpp
@@ -42,7 +42,9 @@
 #include <wx/msgdlg.h>
 #endif
 
+#include "compat.h"
 #include "keyframe.h"
+#include "main.h"
 #include "options.h"
 #include "text_file_reader.h"
 #include "text_file_writer.h"
@@ -74,7 +76,7 @@ void KeyFrameFile::Load(wxString filename) {
 		VideoContext::Get()->SetKeyFramesName(filename);
 
 		// Add to recent
-		Options.AddToRecentList(filename,_T("Recent keyframes"));
+		AegisubApp::Get()->mru->Add("Keyframes", STD_STR(filename));
 	}
 	// Fail
 	catch (const wchar_t *error) {
@@ -106,7 +108,7 @@ void KeyFrameFile::Save(wxString filename) {
 	}
 
 	// Add to recent
-	Options.AddToRecentList(filename,_T("Recent keyframes"));
+	AegisubApp::Get()->mru->Add("Keyframes", STD_STR(filename));
 }
 
 
diff --git a/aegisub/src/libresrc/Makefile.am b/aegisub/src/libresrc/Makefile.am
index 67602640d..3b3711589 100644
--- a/aegisub/src/libresrc/Makefile.am
+++ b/aegisub/src/libresrc/Makefile.am
@@ -9,10 +9,10 @@ BUILT_SOURCES = bitmap.cpp default_config.cpp
 bitmap.cpp: ../../tools/common-respack
 	../../tools/common-respack bitmap.cpp ../bitmaps/16 ../bitmaps/24 ../bitmaps/misc/splash.png ../bitmaps/misc/wxicon.png
 
-default_config.cpp: ../../tools/common-respack
-	../../tools/common-respack default_config.cpp ./default_mru.json
+default_config.cpp: ../../tools/common-respack *.json
+	../../tools/common-respack default_config.cpp ./default_mru.json ./default_config.json
 
-EXTRA_DIST = default_mru.json
+EXTRA_DIST = *.json
 
 CLEANFILES= \
 	bitmap.cpp \
diff --git a/aegisub/src/libresrc/OLD_TO_NEW_OPTION_MAP.txt b/aegisub/src/libresrc/OLD_TO_NEW_OPTION_MAP.txt
new file mode 100644
index 000000000..ff2edeac3
--- /dev/null
+++ b/aegisub/src/libresrc/OLD_TO_NEW_OPTION_MAP.txt
@@ -0,0 +1,191 @@
+Allow Ancient Avisynth				Provider/Avisynth/Allow Ancient
+Audio Alsa Device					Player/Audio/ALSA/Device
+Audio Autocommit					Audio/Auto/Commit
+Audio Autofocus						Audio/Auto/Focus
+Audio Autoscroll					Audio/Auto/Scroll
+Audio Background					Colour/Audio Display/Background/Background
+Audio Cache							Audio/Cache/Type
+Audio Disable PCM Provider			Provider/Audio/PCM/Disable
+Audio Display Height				Audio/Display Height
+Audio Downmixer						Audio/Downmixer
+Audio Draw Cursor Time				Audio/Display/Draw/Cursor Time
+Audio Draw Keyframes				Audio/Display/Draw/Keyframes
+Audio Draw Secondary Lines			Audio/Display/Draw/Secondary Lines
+Audio Draw Selection Background		Audio/Display/Draw/Selection Background
+Audio Draw Timeline					Audio/Display/Draw/Timeline
+Audio Draw Video Position			Audio/Display/Draw/Video Position
+Audio dsound buffer latency			Player/Audio/DirectSound/Buffer Latency
+Audio dsound buffer length			Player/Audio/DirectSound/Buffer Length
+Audio grab times on select			Audio/Grab Times on Select
+Audio HD Cache Location				Audio/Cache/HD/Location
+Audio HD Cache Name					Audio/Cache/HD/Name
+Audio Inactive Lines Display Mode	Audio/Inactive Lines Display Mode
+Audio Lead in						Audio/Lead/IN
+Audio Lead out						Audio/Lead/OUT
+Audio Line boundaries Thickness		Audio/Line Boundaries Thickness
+Audio Line boundary end				Colour/Audio Display/Line boundary End
+Audio Line boundary inactive line	Colour/Audio Display/Line Boundary Inactive Line
+Audio Line boundary start			Colour/Audio Display/Line boundary Start
+Audio Link							Audio/Link
+Audio lock scroll on cursor			Audio/Lock Scroll on Cursor
+Audio Medusa Timing Hotkeys			Audio/Medusa Timing Hotkeys
+Audio Next Line on Commit			Audio/Next Line on Commit
+Audio OSS Device					Audio/OSS/Device
+Audio Play Cursor					Colour/Audio Display/Play Cursor
+Audio player						Audio/Player
+Audio Plays When Stepping Video		Audio/Plays When Stepping
+Audio PortAudio Device				Player/Audio/PortAudio/Device
+Audio provider						Audio/Provider
+Audio Sample Rate					Provider/Audio/AVS/Sample Rate
+Audio Seconds Boundaries			Colour/Audio Display/Seconds Boundaries
+Audio Selection Background			Colour/Audio Display/Background/Background
+Audio Selection Background Modified	Colour/Audio Display/Background/Selection Modified
+Audio snap to keyframes				Audio/Display/Snap/Keyframes
+Audio snap to other lines			Audio/Display/Snap/Other Lines
+Audio Spectrum						Audio/Spectrum
+Audio Spectrum Cutoff				Audio/Renderer/Spectrum/Cutoff
+Audio Spectrum Memory Max			Audio/Renderer/Spectrum/Memory Max
+Audio Spectrum Quality				Audio/Renderer/Spectrum/Quality
+Audio Start Drag Sensitivity		Audio/Start Drag Sensitivity
+Audio Syllable boundaries			Colour/Audio Display/Syllable Boundaries
+Audio Syllable text					Colour/Audio Display/Syllable Text
+Audio Waveform						Colour/Audio Display/Waveform
+Audio Waveform Inactive				Colour/Audio Display/Waveform Inactive
+Audio Waveform Modified				Colour/Audio Display/Waveform Modified
+Audio Waveform Selected				Colour/Audio Display/Waveform Selected
+Audio Wheel Default To Zoom			Audio/Wheel Default to Zoom
+Auto backup							App/Auto/Backup
+Auto backup path					Path/Auto/Backup
+Auto check for updates				App/Auto/Check For Updates
+Auto save every seconds				App/Auto/Save Every Seconds
+Auto Save on Every Change			App/Auto/Save on Every Change
+Auto save path						Path/Auto/Save
+Autoload linked files				App/Auto/Load Linked Files
+Automation Autoload Path			Path/Automation/Autoload
+Automation Autoreload Mode			Automation/Autoreload Mode
+Automation Base Path				Path/Automation/Base
+Automation Include Path				Path/Automation/Include
+Automation Thread Priority			Automation/Lua/Thread Priority
+Automation Trace Level				Automation/Trace Level
+Avisynth MemoryMax					Provider/Avisynth/Memory Max
+Call tips enabled					App/Call Tips
+Color Picker Mode					Tool/Colour Picker/Mode
+Color Picker Recent					Tool/Colour Picker/Recent
+Colour/Subtitle/Syntax/Error		Colour/Subtitle/Syntax/Background/Error
+Detached Video						Video/Detached/Enabled
+Detached video last x				Video/Detached/Last/X
+Detached video last y				Video/Detached/Last/Y
+Detached video maximized			Video/Detached/Maximized
+Dictionaries path					Path/Dictionary
+Disable Dragging Times				Audio/Display/Dragging Times
+Edit Box Need Enter Background		Color/Background/Modified
+Edit Font Face						Subtitle/Edit Box/Font Face
+Edit Font Size						Subtitle/Edit Box/Font Size
+FFmpeg allow unsafe seeking			Provider/Video/FFMpegSource/Unsafe Seeking
+FFmpegSource always index all tracks		Provider/FFmpegSource/Index All Tracks
+FFmpegSource audio decoding error handling	Provider/Audio/FFMpegSource/Decode Error Handling
+FFmpegSource decoding threads		Provider/Video/FFmpegSource/Decoding Threads
+FFmpegSource log level				Provider/FFmpegSource/Log Level
+FFmpegSource max cache files		Provider/FFmpegSource/Cache/Files
+FFmpegSource max cache size			Provider/FFmpegSource/Cache/Size
+Find Affect							Tool/Search Replace/Affect
+Find Field							Tool/Search Replace/Field
+Find Match Case						Tool/Search Replace/Match Case
+Find RegExp							Tool/Search Replace/RegExp
+Find Update Video					Tool/Search Replace/Video Update
+Fonts Collector Action				Tool/Fonts Collector/Action
+Fonts Collector Destination			Path/Fonts Collector Destination
+Grid Active border					Colour/Subtitle Grid/Active Border
+Grid Allow Focus					Subtitle/Grid/Focus Allow
+Grid Background						Colour/Subtitle Grid/Background/Background
+Grid collision foreground			Colour/Subtitle Grid/Collision
+Grid Font Face						Subtitle/Grid/Font Face
+Grid font size						Subtitle/Grid/Font Size
+Grid Hide Overrides					Subtitle/Grid/Hide Overrides
+Grid hide overrides char			Subtitle/Grid/Hide Overrides Char
+Grid left column					Colour/Subtitle Grid/Left Column
+Grid lines							Colour/Subtitle Grid/Lines
+Grid selection background			Colour/Subtitle Grid/Background/Selection
+Grid selection foreground			Colour/Subtitle Grid/Selection
+Grid standard foreground			Colour/Subtitle Grid/Standard
+Highlight subs in frame				Subtitle/Grid/Highlight Subtitles in Frame
+Insert Mode on Time Boxes			Subtitle/Time Edit/Insert Mode
+kanji timer interpolation			Tool/Kanji Timer/Interpolation
+Last open audio path				Path/Last/Audio
+Last open automation path			Path/Last/Automation
+Last open keyframes path			Path/Last/Keyframes
+Last open subtitles path			Path/Last/Subtitles
+Last open timecodes path			Path/Last/Timecodes
+Last open video path				Path/Last/Video
+Link Time Boxes Commit				Subtitle/Edit Box/Link Time Boxes Commit
+Local config						App/Local Config
+Locale Code							App/Locale
+Maximized							App/Maximized
+Recent timecodes					Timecodes
+RGBAdjust Tool						Tool/Colour Picker/RGBAdjust Tool
+Save Charset						App/Save Charset
+Select Action						Tool/Select/Action
+Select Condition					Tool/Select/Condition
+Select Field						Tool/Select/Field
+Select Match case					Tool/Select Lines/Match/Case
+Select Match comments				Tool/Select Lines/Match/Comment
+Select Match dialogues				Tool/Select Lines/Match/Dialogue
+Select Mode							Tool/Select/Mode
+Select Text							Tool/Select Lines/Text
+Shift Times Affect					Tool/Shift Times/Affect
+Shift Times ByTime					Tools/Shift Times/ByTime
+Shift Times Direction				Tools/Shift Times/Direction
+Shift Times Length					Tool/Shift Times/Length
+Shift Times Type					Tool/Shift Times/Type
+Show keyframes on video slider		Video/Slider/Show Keyframes
+Show Overscan Mask					Video/Overscan Mask
+Show Splash							App/Splash
+Spell Checker						Tool/Spell Checker/Backend
+Spell checker Language				Tool/Spell Checker/Language
+Style editor preview background		Colour/Style Editor/Background/Preview
+Style editor preview text			Tool/Style Editor/Preview Text
+Subtitles provider					Subtitle/Provider
+Sync video with subs				Video/Subtitle Sync
+
+Syntax Highlight Brackets			Colour/Subtitle/Syntax/Brackets
+Syntax Highlight Enabled			Subtitle/Highlight/Syntax
+Syntax Highlight Error				Syntax Highlight Error Background
+Syntax Highlight Karaoke Template	Colour/Subtitle/Syntax/Karaoke Template
+Syntax Highlight Line Break			Colour/Subtitle/Syntax/Line Break
+Syntax Highlight Normal				Colour/Subtitle/Syntax/Normal
+Syntax Highlight Parameters			Colour/Subtitle/Syntax/Parameters
+Syntax Highlight Slashes			Colour/Subtitle/Syntax/Slashes
+Syntax Highlight Tags				Colour/Subtitle/Syntax/Highlight Tags
+Text actor separator				Tool/Import/Text/Actor Separator
+
+Text comment starter				Tool/Import/Text/Comment Starter
+Thesaurus Language					Tool/Thesaurus/Language
+Timing Default Duration				Timing/Default Duration
+Timing processor adjacent bias			Tool/Timing Post Processor/Adjacent Bias
+Timing processor adjacent thres			Tool/Timing Post Processor/Threshold/Adjacent
+Timing processor Enable adjacent		Tool/Timing Post Processor/Enable/Adjacent
+Timing processor Enable keyframe		Tool/Timing Post Processor/Enable/Keyframe
+Timing processor Enable lead-in			Tool/Timing Post Processor/Enable/Lead/IN
+Timing processor Enable lead-out		Tool/Timing Post Processor/Enable/Lead/OUT
+Timing processor key end after thres	Tool/Timing Post Processor/Threshold/Key End After
+Timing processor key end before thres	Tool/Timing Post Processor/Threshold/Key End Before
+Timing processor key start after thres	Tool/Timing Post Processor/Threshold/Key Start After
+Timing processor key start before thres	Tool/Timing Post Processor/Threshold/Key Start Before
+Timing processor Only Selection			Tool/Timing Post Processor/Only Selection
+Tips current						Tool/Tip of the Day/Current
+Tips enabled						App/Tips
+Undo levels							Subtitle/Undo Levels
+Updates Next Check Time				Version/Next Check
+Use nonstandard Milisecond Times	App/Nonstandard Milisecond Times
+Video Check Script Res				Video/Check Script Res
+Video Default Zoom					Video/Default Zoom
+Video Dummy Last Colour				Colour/Video Dummy/Last Colour
+Video Dummy Last FPS				Video/Dummy/FPS
+Video Dummy Last Height				Video/Dummy/Last/Height
+Video Dummy Last Length				Video/Dummy/Last/Length
+Video Dummy Last Width				Video/Dummy/Last/Width
+Video Dummy Pattern					Video/Dummy/Pattern
+Video Fast Jump Step				Video/Slider/Fast Jump Step
+Video provider						Video/Provider
+Video Screenshot Path				Path/Screenshot
+Video Visual Realtime				Video/Visual Realtime
diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json
new file mode 100644
index 000000000..b53ee7834
--- /dev/null
+++ b/aegisub/src/libresrc/default_config.json
@@ -0,0 +1,415 @@
+{
+	"App" : {
+		"Auto" : {
+			"Backup" : true,
+			"Check For Updates" : -1,
+			"Load Linked Files" : 2,
+			"Save Every Seconds" : 60,
+			"Save on Every Change" : false
+		},
+		"Call Tips" : false,
+		"Local Config" : false,
+		"Locale" : -1,
+		"Maximized" : false,
+		"Nonstandard Milisecond Times" : false,
+		"Save Charset" : "UTF-8",
+		"Splash" : true,
+		"Tips" : false
+	},
+
+
+	"Audio" : {
+		"Auto" : {
+			"Commit" : false,
+			"Focus" : false,
+			"Scroll" : true
+		},
+		"Cache" : {
+			"HD" : {
+				"Location" : "default",
+				"Name" : "audio%02i.tmp"
+			},
+			"Type" : 1
+		},
+		"Display Height" : 200,
+		"Display" : {
+			"Dragging Times" : false,
+			"Draw" : {
+				"Cursor Time" : true,
+				"Keyframes" : true,
+				"Secondary Lines" : true,
+				"Selection Background" : true,
+				"Timeline" : true,
+				"Video Position" : false
+			},
+			"Snap" : {
+				"Keyframes" : false,
+				"Other Lines" : false
+			}
+		},
+		"Downmixer" : "ConvertToMono",
+		"Grab Times on Select" : true,
+		"Inactive Lines Display Mode" : 1,
+		"Lead" : {
+			"IN" : 200,
+			"OUT" : 300
+		},
+		"Line Boundaries Thickness" : 2,
+		"Line Boundary Inactive Line" : "grey",
+		"Link" : true,
+		"Lock Scroll on Cursor" : false,
+		"Medusa Timing Hotkeys" : false,
+		"Next Line on Commit" : true,
+		"Player" : "portaudio",
+		"Plays When Stepping Video" : false,
+		"Provider" : "ffmpegsource",
+		"Renderer" : {
+			"Spectrum" : {
+				"Cutoff" : 0,
+				"Memory Max" : 128,
+				"Quality" : 1
+			}
+		},
+		"Spectrum" : true,
+		"Start Drag Sensitivity" : 2,
+		"Wheel Default to Zoom" : false
+	},
+
+
+	"Automation" : {
+		"Autoreload Mode" : 1,
+		"Lua" : {
+			"Thread Priority" : 1
+		},
+		"Trace Level" : 3
+	},
+
+
+	"Colour" : {
+		"Audio Display" : {
+			"Background" : {
+				"Background" : "rgb(0,0,0)",
+				"Selection" : "rgb(64, 64, 64)",
+				"Selection Modified" : "rgb(92, 0, 0)"
+			},
+			"Line Boundary Inactive Line" : "rgb(190,190,190)",
+			"Line boundary End" : "rgb(230, 125, 0)",
+			"Line boundary Start" : "rgb(216, 0, 0)",
+			"Play Cursor" : "rgb(255,255,255)",
+			"Seconds Boundaries" : "rgb(0, 100, 255)",
+			"Syllable Boundaries" : "rgb(255,255,0)",
+			"Syllable Text" : "rgb(255,0,0)",
+			"Waveform" : "rgb(0, 200, 0)",
+			"Waveform Inactive" : "rgb(0, 80, 0)",
+			"Waveform Modified" : "rgb(255, 230, 230)",
+			"Waveform Selected" : "rgb(255,255,255)"
+		},
+		"Background" : {
+			"Modified" : "rgb(192, 192, 255)"
+		},
+		"Style Editor" : {
+			"Background" : {
+				"Preview" : "rgb(125, 153, 176)"
+			}
+		},
+		"Subtitle Grid" : {
+			"Active Border" : "rgb(255, 91, 239)",
+			"Background" : {
+				"Background" : "rgb(255,255,255)",
+				"Comment" : "rgb(216, 222, 245)",
+				"Inframe" : "rgb(255, 253, 234)",
+				"Selected Comment" : "rgb(211, 238, 238)",
+				"Selection" : "rgb(206, 255, 231)"
+			},
+			"Collision" : "rgb(255,0,0)",
+			"Header" : "rgb(165, 207, 231)",
+			"Left Column" : "rgb(196, 236, 201)",
+			"Lines" : "rgb(190,190,190)",
+			"Selection" : "rgb(0,0,0)",
+			"Standard" : "rgb(0,0,0)"
+		},
+		"Subtitle" : {
+			"Syntax" : {
+				"Background" : {
+					"Error" : "rgb(255, 200, 200)"
+				},
+				"Brackets" : "rgb(20, 50, 255)",
+				"Error" : "rgb(200, 0, 0)",
+				"Highlight Tags" : "rgb(90, 90, 90)",
+				"Karaoke Template" : "rgb(128, 0, 192)",
+				"Line Break" : "rgb(160, 160, 160)",
+				"Normal" : "rgb(0,0,0)",
+				"Parameters" : "rgb(40, 90, 40)",
+				"Slashes" : "rgb(255, 0, 200)"
+			}
+		},
+		"Video Dummy" : {
+			"Last Colour" : "rgb(47, 163, 254)"
+		}
+	},
+
+	"Limits" : {
+		"Find Replace" : 16,
+		"MRU" : 16,
+		"Undo Levels" : 8
+	},
+
+	"Path" : {
+		"Auto" : {
+			"Backup" : "?user/autoback",
+			"Save" : "?user/autosave"
+		},
+		"Automation" : {
+			"Autoload" : "?user/automation/autoload/|?data/automation/autoload/",
+			"Base" : "?data/automation/",
+			"Include" : "?user/automation/include/|?data/automation/include/"
+		},
+		"Dictionary" : "?user/dictionary",
+		"Fonts Collector Destination" : "?script",
+		"Last" : {
+			"Audio" : "",
+			"Automation" : "",
+			"Keyframes" : "",
+			"Subtitles" : "",
+			"Timecodes" : "",
+			"Video" : ""
+		},
+		"Screenshot" : "?video"
+	},
+
+
+	"Player" : {
+		"Audio" : {
+			"ALSA" : {
+				"Device" : "default"
+			},
+			"DirectSound" : {
+				"Buffer Latency" : 100,
+				"Buffer Length" : 5
+			},
+			"OSS" : {
+				"Device" : "/dev/dsp"
+			},
+			"PortAudio" : {
+				"Device" : -1
+			}
+		}
+	},
+
+
+	"Provider" : {
+		"Audio" : {
+			"AVS" : {
+				"Sample Rate" : 0
+			},
+			"FFMpegSource" : {
+				"Decode Error Handling" : "stop"
+			},
+			"PCM" : {
+				"Disable" : false
+			}
+		},
+		"Avisynth" : {
+			"Allow Ancient" : false,
+			"Memory Max" : 64
+		},
+		"FFmpegSource" : {
+			"Cache" : {
+				"Files" : 20,
+				"Size" : 42
+			},
+			"Index All Tracks" : true,
+			"Log Level" : "quiet"
+		},
+		"Video" : {
+			"Cache" : {
+				"Size" : 32
+			},
+			"FFMpegSource" : {
+				"Decoding Threads" : 1,
+				"Unsafe Seeking" : false
+			}
+		}
+	},
+
+
+	"Subtitle" : {
+		"Edit Box" : {
+			"Font Face" : "",
+			"Font Size" : 10,
+			"Link Time Boxes Commit" : true
+		},
+		"Grid" : {
+			"Column" : [
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true},
+				{"bool" : true}
+			],
+			"Focus Allow" : true,
+			"Font Face" : "",
+			"Font Size" : 10,
+			"Hide Overrides" : 1,
+			"Hide Overrides Char" : "☀",
+			"Highlight Subtitles in Frame" : true
+		},
+		"Highlight" : {
+			"Syntax" : true
+		},
+		"Provider" : "libass",
+		"Time Edit" : {
+			"Insert Mode" : true
+		}
+	},
+
+
+	"Timing" : {
+		"Default Duration" : 2000
+	},
+
+
+	"Tool" : {
+		"Colour Picker" : {
+			"Mode" : 4,
+			"RGBAdjust Tool" : false,
+			"Recent" : "&H000000& &H0000FF& &H00FFFF& &H00FF00& &HFFFF00& &HFF0000& &HFF00FF& &HFFFFFF&"
+		},
+		"Fonts Collector" : {
+			"Action" : 0
+		},
+		"Import" : {
+			"Text" : {
+				"Actor Separator" : ":",
+				"Comment Starter" : "#"
+			}
+		},
+		"Kanji Timer" : {
+			"Interpolation" : true
+		},
+		"Paste Lines Over" : {
+			"Fields" : [
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : false},
+				{"bool" : true}
+			]
+		},
+		"Preferences" : {
+			"Page" : 0
+		},
+		"Search Replace" : {
+			"Affect" : 0,
+			"Field" : 0,
+			"Match Case" : false,
+			"RegExp" : false,
+			"Video Update" : false
+		},
+		"Select Lines" : {
+			"Match" : {
+				"Case" : false,
+				"Comment" : false,
+				"Dialogue" : true
+			},
+			"Text" : ""
+		},
+		"Select" : {
+			"Action" : 0,
+			"Condition" : 0,
+			"Field" : 0,
+			"Mode" : 1
+		},
+		"Shift Times" : {
+			"Affect" : 0,
+			"ByTime" : true,
+			"Direction" : true,
+			"Length" : 0,
+			"Type" : 0
+		},
+		"Spell Checker" : {
+			"Backend" : "hunspell",
+			"Language" : "en_US"
+		},
+		"Style Editor" : {
+			"Preview Text" : "Aegisub\\N0123 月語"
+		},
+		"Thesaurus" : {
+			"Language" : "en_US"
+		},
+		"Timing Post Processor" : {
+			"Adjacent Bias" : 0.9000000000000000222,
+			"Enable" : {
+				"Adjacent" : true,
+				"Keyframe" : true,
+				"Lead" : {
+					"IN" : true,
+					"OUT" : true
+				}
+			},
+			"Only Selection" : false,
+			"Threshold" : {
+				"Adjacent" : 300,
+				"Key End After" : 6,
+				"Key End Before" : 5,
+				"Key Start After" : 4,
+				"Key Start Before" : 5
+			}
+		},
+		"Tip of the Day" : {
+			"Current" : 0
+		},
+		"Visual" : {
+			"Always Show": true
+		}
+	},
+
+
+	"Version" : {
+		"Id" : "$Id$",
+		"Last Version" : 4040,
+		"Next Check" : 0
+	},
+
+
+	"Video" : {
+		"Check Script Res" : 0,
+		"Default Zoom" : 7,
+		"Detached" : {
+			"Enabled" : false,
+			"Last" : {
+				"X" : -1,
+				"Y" : -1
+			},
+			"Maximized" : false
+		},
+		"Dummy" : {
+			"FPS" : 23.975999999999999091,
+			"Last" : {
+				"Height" : 480,
+				"Length" : 40000,
+				"Width" : 640
+			},
+			"Pattern" : false
+		},
+		"Overscan Mask" : false,
+		"Provider" : "ffmpegsource",
+		"Slider" : {
+			"Fast Jump Step" : 10,
+			"Show Keyframes" : true
+		},
+		"Subtitle Sync" : true,
+		"Threaded" : false,
+		"Visual Realtime" : true
+	}
+}
diff --git a/aegisub/src/libresrc/default_mru.json b/aegisub/src/libresrc/default_mru.json
index 10bb3e2e2..96166d7b0 100644
--- a/aegisub/src/libresrc/default_mru.json
+++ b/aegisub/src/libresrc/default_mru.json
@@ -1,5 +1,9 @@
 {
 	"Audio" : [],
-	"Video" : [],
-	"Scripts" : []
+	"Find" : [],
+	"Keyframes" : [],
+	"Replace" : [],
+	"Subtitle" : [],
+	"Timecodes" : [],
+	"Video" : []
 }
diff --git a/aegisub/src/main.cpp b/aegisub/src/main.cpp
index 086cc085d..4df53dc6c 100644
--- a/aegisub/src/main.cpp
+++ b/aegisub/src/main.cpp
@@ -60,11 +60,12 @@
 #include "auto4_base.h"
 #endif
 #include "charset_conv.h"
+#include "compat.h"
 #include "export_framerate.h"
 #include "frame_main.h"
 #include "hotkeys.h"
 #include "main.h"
-#include "options.h"
+#include "libresrc/libresrc.h"
 #include "plugin_manager.h"
 #include "standard_paths.h"
 #include "subs_grid.h"
@@ -72,6 +73,7 @@
 #include "version.h"
 #include "video_context.h"
 
+#include <libaegisub/io.h>
 
 ///////////////////
 // wxWidgets macro
@@ -140,6 +142,32 @@ bool AegisubApp::OnInit() {
 	StartupLog(_T("Inside OnInit"));
 	frame = NULL;
 	try {
+
+		const std::string conf_mru(StandardPaths::DecodePath(_T("?user/mru.json")));
+		mru = new agi::MRUManager(conf_mru, GET_DEFAULT_CONFIG(default_mru));
+
+		// Set config file
+		StartupLog(_T("Load configuration"));
+		try {
+			const std::string conf_user(StandardPaths::DecodePath(_T("?user/config.json")));
+			opt = new agi::Options(conf_user, GET_DEFAULT_CONFIG(default_config));
+			opt->ConfigUser();
+/*
+#ifdef _DEBUG
+			const std::string conf_default("default_config.json");
+			std::istream *stream = agi::io::Open(conf_default);
+			opt->ConfigDefault(*stream);
+			delete stream;
+#else
+			opt->ConfigDefault(GET_DEFAULT_CONFIG(default_config));
+#endif
+*/
+//			opt->ConfigDefault(GET_DEFAULT_CONFIG(default_config));
+
+		} catch (agi::Exception& e) {
+			wxPrintf("Caught agi::Exception: %s -> %s\n", e.GetName(), e.GetMessage());
+		}
+
 		// Initialize randomizer
 		StartupLog(_T("Initialize random generator"));
 		srand(time(NULL));
@@ -166,36 +194,9 @@ bool AegisubApp::OnInit() {
 		wxHandleFatalExceptions(true);
 #endif
 
-		// Set config file
-		StartupLog(_T("Load configuration"));
-		Options.LoadDefaults();
-#ifdef __WXMSW__
-		// Try loading configuration from the install dir if one exists there
-		if (wxFileName::FileExists(StandardPaths::DecodePath(_T("?data/config.dat")))) {
-			Options.SetFile(StandardPaths::DecodePath(_T("?data/config.dat")));
-			Options.Load();
-
-			if (Options.AsBool(_T("Local config"))) {
-				// Local config, make ?user mean ?data so all user settings are placed in install dir
-				StandardPaths::SetPathValue(_T("?user"), StandardPaths::DecodePath(_T("?data")));
-			}
-			else {
-				// Not local config, we don't want that config.dat file here any more
-				// It might be a leftover from a really old install
-				wxRemoveFile(StandardPaths::DecodePath(_T("?data/config.dat")));
-			}
-		}
-#endif
-		// TODO: Check if we can write to config.dat and warn the user if we can't
-		// If we had local config, ?user now means ?data so this will still be loaded from the correct location
-		Options.SetFile(StandardPaths::DecodePath(_T("?user/config.dat")));
-		Options.Load();
-
 		StartupLog(_T("Store options back"));
-		Options.SetInt(_T("Last Version"),GetSVNRevision());
-		Options.LoadDefaults(false,true);	// Override options based on version number
-		Options.Save();
-		AssTime::UseMSPrecision = Options.AsBool(_T("Use nonstandard Milisecond Times"));
+		OPT_SET("Version/Last Version")->SetInt(GetSVNRevision());
+		AssTime::UseMSPrecision = OPT_GET("App/Nonstandard Milisecond Times")->GetBool();
 
 		// Set hotkeys file
 		StartupLog(_T("Load hotkeys"));
@@ -205,11 +206,10 @@ bool AegisubApp::OnInit() {
 		StartupLog(_T("Initialize final locale"));
 
 		// Set locale
-		int lang = Options.AsInt(_T("Locale Code"));
+		int lang = OPT_GET("App/Locale")->GetInt();
 		if (lang == -1) {
 			lang = locale.PickLanguage();
-			Options.SetInt(_T("Locale Code"),lang);
-			Options.Save();
+			OPT_SET("App/Locale")->SetInt(lang);
 		}
 		locale.Init(lang);
 
@@ -220,7 +220,7 @@ bool AegisubApp::OnInit() {
 		// Load Automation scripts
 #ifdef WITH_AUTOMATION
 		StartupLog(_T("Load global Automation scripts"));
-		global_scripts = new Automation4::AutoloadScriptManager(Options.AsText(_T("Automation Autoload Path")));
+		global_scripts = new Automation4::AutoloadScriptManager(lagi_wxString(OPT_GET("Path/Automation/Autoload")->GetString()));
 #endif
 
 		// Load export filters
@@ -263,7 +263,8 @@ int AegisubApp::OnExit() {
 	SubtitleFormat::DestroyFormats();
 	VideoContext::Clear();
 	delete plugins;
-	Options.Clear();
+	delete opt;
+	delete mru;
 #ifdef WITH_AUTOMATION
 	delete global_scripts;
 #endif
@@ -461,7 +462,7 @@ void AegisubApp::MacOpenFile(const wxString &filename) {
 	if (frame != NULL && !filename.empty()) {
 		frame->LoadSubtitles(filename);
 		wxFileName filepath(filename);
-		Options.SetText(_T("Last open subtitles path"), filepath.GetPath());
+		OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(filepath.GetPath()));
 	}
 }
 #endif
diff --git a/aegisub/src/main.h b/aegisub/src/main.h
index eef8b70d2..496a82fe6 100644
--- a/aegisub/src/main.h
+++ b/aegisub/src/main.h
@@ -44,7 +44,8 @@
 #endif
 
 #include "aegisublocale.h"
-
+#include <libaegisub/mru.h>
+#include <libaegisub/option.h>
 
 //////////////
 // Prototypes
@@ -54,7 +55,11 @@ class PluginManager;
 /// DOCME
 namespace Automation4 { class AutoloadScriptManager; }
 
+/// Macro to get OptionValue object.
+#define OPT_GET(x) AegisubApp::Get()->opt->Get(x)
 
+/// Macro to set OptionValue object.
+#define OPT_SET(x) AegisubApp::Get()->opt->Get(x)
 
 /// DOCME
 /// @class AegisubApp
@@ -75,6 +80,9 @@ public:
 	/// DOCME
 	AegisubLocale locale;
 
+	agi::MRUManager *mru;
+	agi::Options *opt;
+
 	/// DOCME
 	FrameMain *frame;
 #ifdef WITH_AUTOMATION
diff --git a/aegisub/src/options.cpp b/aegisub/src/options.cpp
index b7b8dfe27..09215c9a7 100644
--- a/aegisub/src/options.cpp
+++ b/aegisub/src/options.cpp
@@ -35,818 +35,3 @@
 ///
 
 
-////////////
-// Includes
-#include "config.h"
-
-#ifndef AGI_PRE
-#include <fstream>
-#include <string>
-
-#include <wx/filefn.h>
-#include <wx/filename.h>
-#include <wx/intl.h>
-#include <wx/msgdlg.h>
-#include <wx/settings.h>
-#include <wx/stopwatch.h>
-#include <wx/utils.h>
-#endif
-
-#include "colorspace.h"
-#include "options.h"
-#include "text_file_reader.h"
-#include "text_file_writer.h"
-#include "utils.h"
-
-
-/// @brief Constructor 
-///
-OptionsManager::OptionsManager() {
-	modified = false;
-	overriding = false;
-	lastVersion = -1;
-}
-
-
-
-/// @brief Destructor 
-///
-OptionsManager::~OptionsManager() {
-	Clear();
-}
-
-
-
-/// @brief Clear 
-///
-void OptionsManager::Clear() {
-	opt.clear();
-	optType.clear();
-}
-
-
-
-/// @brief Load default values 
-/// @param onlyDefaults 
-/// @param doOverride   
-///
-void OptionsManager::LoadDefaults(bool onlyDefaults,bool doOverride) {
-	///// PUBLIC //////
-	// Here go the options that can be edited by the options menu
-
-	if (doOverride) overriding = true;
-	if (onlyDefaults) lastVersion = -1;
-	
-	// General
-	SetModificationType(MOD_AUTOMATIC);
-
-// Broken on OS X during startup only.
-	SetBool(_T("Tips enabled"),false);
-	SetBool(_T("Show splash"),true);
-	SetBool(_T("Local config"),false);
-	SetInt(_T("Undo levels"),8);
-	SetInt(_T("Recent timecodes max"),16);
-	SetInt(_T("Recent keyframes max"),16);
-	SetInt(_T("Recent sub max"),16);
-	SetInt(_T("Recent vid max"),16);
-	SetInt(_T("Recent aud max"),16);
-	SetInt(_T("Recent find max"),16);
-	SetInt(_T("Recent replace max"),16);
-	SetInt(_T("Auto check for updates"),-1);
-
-	// File Save/Load
-	SetModificationType(MOD_RESTART);
-	SetInt(_T("Auto save every seconds"),60); // FIXME: this shouldn't need to require a restart
-	SetModificationType(MOD_AUTOMATIC);
-	SetText(_T("Auto save path"),_T("?user/autosave"));
-	SetBool(_T("Auto backup"),true);
-	SetText(_T("Auto backup path"),_T("?user/autoback"));
-	SetInt(_T("Autoload linked files"),2);
-	SetText(_T("Text actor separator"),_T(":"));
-	SetText(_T("Text comment starter"),_T("#"));
-	SetText(_T("Save charset"),_T("UTF-8"));
-	SetBool(_T("Use nonstandard milisecond times"),false);
-	SetBool(_T("Auto save on every change"),false);
-
-	// Edit Box
-	SetModificationType(MOD_RESTART);
-#if defined(__WINDOWS__) || defined(__APPLE__)
-	SetText(_T("Dictionaries path"),_T("?data/dictionaries"));
-#else
-	SetText(_T("Dictionaries path"),wxString::Format(_T("%s/%s"), _T(INSTALL_PREFIX),_T("/share/myspell")));
-#endif
-	SetText(_T("Spell Checker"),_T("hunspell"));
-	SetModificationType(MOD_AUTOMATIC);
-	SetBool(_T("Link time boxes commit"),true);
-	SetBool(_T("Insert mode on time boxes"),true);
-	SetModificationType(MOD_EDIT_BOX);
-	SetBool(_T("Call tips enabled"),false,1700);
-	SetBool(_T("Syntax highlight enabled"),true);
-
-	// Edit box cosmetic
-	SetColour(_T("Syntax Highlight Normal"),wxColour(0,0,0));
-	SetColour(_T("Syntax Highlight Brackets"),wxColour(20,50,255));
-	SetColour(_T("Syntax Highlight Slashes"),wxColour(255,0,200));
-	SetColour(_T("Syntax Highlight Tags"),wxColour(90,90,90));
-	SetColour(_T("Syntax Highlight Parameters"),wxColour(40,90,40));
-	SetColour(_T("Syntax Highlight Error"),wxColour(200,0,0));
-	SetColour(_T("Syntax Highlight Error Background"),wxColour(255,200,200));
-	SetColour(_T("Syntax Highlight Line Break"),wxColour(160,160,160));
-	SetColour(_T("Syntax Highlight Karaoke Template"), wxColour(128,0,192));
-	SetColour(_T("Edit Box Need Enter Background"),wxColour(192,192,255));
-#if defined(__WINDOWS__)
-	SetInt(_T("Edit Font Size"),9);
-#else
-	SetInt(_T("Edit Font Size"),11);
-#endif
-	SetText(_T("Edit Font Face"),_T(""));
-
-	// Video Options
-	SetModificationType(MOD_AUTOMATIC);
-	SetInt(_T("Video Check Script Res"), 0);
-	SetInt(_T("Video Default Zoom"), 7);
-	SetInt(_T("Video Fast Jump Step"), 10);
-	SetText(_T("Video Screenshot Path"),_T("?video"),1700);
-	SetModificationType(MOD_VIDEO);
-	SetBool(_T("Show keyframes on video slider"),true);
-	SetBool(_T("Show overscan mask"),false);
-
-	// Video Provider (Advanced)
-	SetModificationType(MOD_VIDEO_RELOAD);
-	SetInt(_T("Avisynth MemoryMax"),64,1700);
-	SetBool(_T("Threaded Video"),false,1700);
-	#ifdef __WINDOWS__
-	SetText(_T("Video Provider"),_T("ffmpegsource"),2416);
-	#else
-	SetText(_T("Video Provider"),_T(DEFAULT_PROVIDER_VIDEO),1945);
-	#endif
-	SetBool(_T("FFmpeg allow unsafe seeking"),false);
-	SetInt(_T("FFmpegSource decoding threads"),1);
-	SetBool(_T("Allow Ancient Avisynth"),false,1700);
-	#ifdef __WINDOWS__
-	SetText(_T("Subtitles Provider"),_T("csri/vsfilter_textsub"),1700);
-	#else
-	SetText(_T("Subtitles Provider"),_T(DEFAULT_PROVIDER_SUBTITLE));
-	#endif
-	SetInt(_T("Video cache size"), 32);
-	SetInt(_T("FFmpegSource max cache size"),42);
-	SetInt(_T("FFmpegSource max cache files"),20);
-	SetInt(_T("FFmpegSource always index all tracks"), true);
-	SetText(_T("FFmpegSource log level"), _T("quiet"));
-	SetText(_T("FFmpegSource audio decoding error handling"), _T("stop"));
-
-	// Audio Options
-	SetModificationType(MOD_AUTOMATIC);
-	SetBool(_T("Audio grab times on select"),true);
-	SetBool(_T("Audio Autofocus"),false);
-	SetBool(_T("Audio Plays When Stepping Video"),false);
-	SetBool(_T("Audio Wheel Default To Zoom"),false);
-	SetBool(_T("Audio lock scroll on cursor"),false);
-	SetBool(_T("Audio snap to keyframes"),false);
-	SetBool(_T("Audio snap to other lines"),false);
-	SetInt(_T("Timing Default Duration"), 2000);
-	SetInt(_T("Audio lead in"),200);
-	SetInt(_T("Audio lead out"),300);
-	SetModificationType(MOD_AUDIO);
-	SetInt(_T("Audio Inactive Lines Display Mode"),1);
-	SetBool(_T("Disable Dragging Times"), false);
-	SetInt(_T("Audio Start Drag Sensitivity"), 2);
-
-	// Audio Advanced
-	SetModificationType(MOD_AUDIO_RELOAD);
-	SetInt(_T("Audio Cache"),1,1700);
-	#if defined(__WINDOWS__)
-	SetText(_T("Audio Player"),_T("DirectSound"),1945);
-	SetText(_T("Audio Provider"),_T("ffmpegsource"),2416);
-	#else
-	SetText(_T("Audio Player"),_T(DEFAULT_PLAYER_AUDIO));
-	SetText(_T("Audio Provider"),_T(DEFAULT_PROVIDER_AUDIO),1945);
-	#endif
-	SetText(_T("Audio Downmixer"),_T("ConvertToMono"),1700);
-	SetText(_T("Audio Alsa Device"), _T("default"));
-	SetInt(_T("Audio PortAudio Device"), -1);
-	SetText(_T("Audio OSS Device"), _T("/dev/dsp"));
-	SetText(_T("Audio HD Cache Location"),_T("default"),1700);
-	SetText(_T("Audio HD Cache Name"),_T("audio%02i.tmp"),1700);
-	SetBool(_T("Audio Disable PCM Provider"), false);
-	// Technically these can do with just the spectrum object being re-created
-	SetInt(_T("Audio Spectrum Cutoff"),0);
-	SetInt(_T("Audio Spectrum Quality"),1);
-	SetInt(_T("Audio Spectrum Memory Max"),128); // megabytes
-	// and this one could do with just reiniting the audio player
-	SetInt(_T("Audio dsound buffer latency"), 100);
-	SetInt(_T("Audio dsound buffer length"), 5);
-
-	// Automation
-	// The path changes only take effect when a script is (re)loaded but Automatic should be good enough, it certainly doesn't warrart a restart
-	SetModificationType(MOD_AUTOMATIC);
-	SetText(_T("Automation Base Path"), _T("?data/automation/"),1700);
-	SetText(_T("Automation Include Path"), _T("?user/automation/include/|?data/automation/include/"),1700);
-	SetText(_T("Automation Autoload Path"), _T("?user/automation/autoload/|?data/automation/autoload/"),1700);
-	SetInt(_T("Automation Trace Level"), 3);
-	SetInt(_T("Automation Thread Priority"), 1); // "below normal"
-	SetInt(_T("Automation Autoreload Mode"), 1); // local only
-
-	// Generate colors
-	// FIXME: Can't reliably store the system colour-based ones in the config file, the user might change colour scheme
-	wxColour tempCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
-	wxColour textCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
-	wxColour background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
-	wxColour comment = wxColour(216,222,245);
-	wxColour selection = wxColour(206,255,231);
-	wxColour selComment = wxColour(211,238,238);
-	wxColour header = wxColour(165,207,231);
-	wxColour labels = wxColour(196,236,201);
-	wxColour inframe = wxColour(255,253,234);
-	wxColour active = wxColour(255,91,239);
-	wxColour grid = wxColour(128,128,128);
-	wxColour collision = wxColour(255,0,0);
-
-	// Grid
-	SetModificationType(MOD_GRID);
-	SetBool(_T("Grid allow focus"),true);
-	SetBool(_T("Highlight subs in frame"),true);
-
-	// Grid cosmetic
-	SetColour(_T("Grid standard foreground"),textCol);
-	SetColour(_T("Grid selection background"),selection);
-	SetColour(_T("Grid selection foreground"),textCol);
-	SetColour(_T("Grid comment background"),comment);
-	SetColour(_T("Grid collision foreground"),collision);
-	SetColour(_T("Grid selected comment background"),selComment);
-	SetColour(_T("Grid inframe background"),inframe);
-	SetColour(_T("Grid background"),background);
-	SetColour(_T("Grid header"),header);
-	SetColour(_T("Grid left column"),labels);
-	SetColour(_T("Grid active border"),active);
-	SetColour(_T("Grid lines"),grid);
-#if defined(__WINDOWS__)
-	SetInt(_T("Grid font size"),8);
-#else
-	SetInt(_T("Grid font size"),10);
-#endif
-	SetText(_T("Grid Font Face"),_T(""));
-	wchar_t temp = 0x2600;
-	SetText(_T("Grid hide overrides char"),temp);
-	SetModificationType(MOD_AUTOMATIC);
-
-	// Audio Cosmetic
-	SetModificationType(MOD_AUDIO);
-	SetInt(_T("Audio Line boundaries Thickness"), 2);
-	SetBool(_T("Audio Draw Secondary Lines"), true);
-	SetBool(_T("Audio Draw Selection Background"), true);
-	SetBool(_T("Audio Draw Keyframes"), true);
-	SetBool(_T("Audio Draw Timeline"),true);
-	SetBool(_T("Audio Draw Cursor Time"),true);
-	SetBool(_T("Audio Draw Video Position"),false,1700);
-	SetColour(_T("Audio Selection Background Modified"),wxColour(92,0,0));
-	SetColour(_T("Audio Selection Background"),wxColour(64,64,64));
-	SetColour(_T("Audio Seconds Boundaries"),wxColour(0,100,255));
-	SetColour(_T("Audio Waveform Modified"),wxColour(255,230,230));
-	SetColour(_T("Audio Waveform Selected"),wxColour(255,255,255));
-	SetColour(_T("Audio Waveform Inactive"),wxColour(0,80,0));
-	SetColour(_T("Audio Waveform"),wxColour(0,200,0));
-	SetColour(_T("Audio Line boundary start"),wxColour(216,0,0),1700);
-	SetColour(_T("Audio Line boundary end"),wxColour(230,125,0),1700);
-	SetColour(_T("Audio Line boundary inactive line"),wxColour(128,128,128));
-	SetColour(_T("Audio Syllable boundaries"),wxColour(255,255,0));
-	SetColour(_T("Audio Syllable text"),wxColour(255,0,0));
-	SetColour(_T("Audio Play cursor"),wxColour(255,255,255));
-	SetColour(_T("Audio Background"),wxColour(0,0,0));
-	SetModificationType(MOD_OFF);
-
-	// RGB Adjust tool
-	SetBool(_T("RGBAdjust Tool"),false);
-
-
-	// Only defaults?
-	if (!onlyDefaults) {
-
-		///// INTERNAL //////
-		// Options that are set by the program itself
-		SetInt(_T("Video Dummy Last Width"), 640);
-		SetInt(_T("Video Dummy Last Height"), 480);
-		SetColour(_T("Video Dummy Last Colour"), wxColour(47, 163, 254));
-		SetFloat(_T("Video Dummy Last FPS"), 23.976);
-		SetInt(_T("Video Dummy Last Length"), 40000);
-		SetBool(_T("Video Dummy Pattern"), false);
-
-// Broken on OS X during startup, so default to en_US
-#ifdef __APPLE__
-		SetInt(_T("Locale Code"),56,1700);
-#else
-		SetInt(_T("Locale Code"),-1,1700);
-#endif
-		SetBool(_T("Sync video with subs"),true);
-		SetText(_T("Spell checker language"),_T("en_US"));
-		SetText(_T("Thesaurus language"),_T("en_US"));
-		SetInt(_T("Options Page"),0);
-
-		SetBool(_T("Audio Link"),true);
-		SetBool(_T("Audio Autocommit"),false);
-		SetBool(_T("Audio Autoscroll"),true);
-		SetBool(_T("Audio Medusa Timing Hotkeys"),false);
-		SetBool(_T("Audio Next Line on Commit"),true);
-
-		SetBool(_T("Shift Times ByTime"),true);
-		SetInt(_T("Shift Times Type"),0);
-		SetInt(_T("Shift Times Length"),0);
-		SetInt(_T("Shift Times Affect"),0);
-		SetBool(_T("Shift Times Direction"),true);
-
-		SetInt(_T("Tips current"),0);
-		SetBool(_T("Maximized"),false);
-
-		SetBool(_T("Find Match Case"),false);
-		SetBool(_T("Find RegExp"),false);
-		SetBool(_T("Find Update Video"),false);
-		SetInt(_T("Find Affect"),0);
-		SetInt(_T("Find Field"),0);
-
-		SetInt(_T("Grid hide overrides"),1);
-		for (int i=0;i<10;i++) SetBool(_T("Grid show column ") + AegiIntegerToString(i),true);
-
-		for (int i=0;i<9;i++) SetBool(wxString::Format(_T("Paste Over #%i"),i),false);
-		SetBool(_T("Paste Over #9"),true);
-
-		SetText(_T("Fonts Collector Destination"),_T("?script"));
-		SetInt(_T("Fonts Collector Action"),0);
-
-		SetInt(_T("Audio Display Height"),200,1784);
-		SetBool(_T("Audio Spectrum"),true);
-		SetInt(_T("Audio Sample Rate"),0);
-
-		SetBool(_T("Video Visual Realtime"),true);
-		SetBool(_T("Always show visual tools"),true);
-		SetBool(_T("Detached video"),false);
-		SetInt(_T("Detached video last x"),-1);
-		SetInt(_T("Detached video last y"),-1);
-		SetBool(_T("Detached video maximized"),false);
-
-		SetInt(_T("Timing processor key start before thres"),5);
-		SetInt(_T("Timing processor key start after thres"),4);
-		SetInt(_T("Timing processor key end before thres"),5);
-		SetInt(_T("Timing processor key end after thres"),6);
-		SetInt(_T("Timing processor adjacent thres"),300);
-		SetBool(_T("Timing processor Enable lead-in"),true);
-		SetBool(_T("Timing processor Enable lead-out"),true);
-		SetBool(_T("Timing processor Enable keyframe"),true);
-		SetBool(_T("Timing processor Enable adjacent"),true);
-		SetBool(_T("Timing processor Only Selection"),false);
-		SetFloat(_T("Timing processor adjacent bias"),1.0);
-
-		SetText(_T("Select Text"),_T(""));
-		SetInt(_T("Select Condition"),0);
-		SetInt(_T("Select Field"),0);
-		SetInt(_T("Select Action"),0);
-		SetInt(_T("Select Mode"),1);
-		SetBool(_T("Select Match case"),false);
-		SetBool(_T("Select Match dialogues"),true);
-		SetBool(_T("Select Match comments"),false);
-
-		SetText(_T("Color Picker Recent"), _T("&H000000& &H0000FF& &H00FFFF& &H00FF00& &HFFFF00& &HFF0000& &HFF00FF& &HFFFFFF&"));
-		SetInt(_T("Color Picker Mode"), 4, 1700);
-
-		SetText(_T("Last open subtitles path"),_T(""));
-		SetText(_T("Last open video path"),_T(""));
-		SetText(_T("Last open audio path"),_T(""));
-		SetText(_T("Last open timecodes path"),_T(""));
-		SetText(_T("Last open keyframes path"),_T(""));
-		SetText(_T("Last open automation path"),_T(""));
-
-		SetBool(_T("kanji timer interpolation"),true);
-
-		wxString previewText = _T("Aegisub\\N0123 ");
-		previewText += 0x6708; // kanji "moon"
-		previewText += 0x8a9e; // kanji "speak"
-		SetText(_T("Style editor preview text"),previewText);
-		SetColour(_T("Style editor preview background"),wxColour(125,153,176));
-
-		SetInt(_T("Updates Next Check Time"), 0);
-	}
-
-	lastVersion = -1;
-	overriding = false;
-}
-
-
-
-/// @brief Set filename 
-/// @param file 
-///
-void OptionsManager::SetFile(wxString file) {
-	filename = file;
-}
-
-
-
-/// @brief Get filename 
-/// @return 
-///
-wxString OptionsManager::GetFile() const
-{
-	return filename;
-}
-
-
-
-/// @brief Save 
-/// @return 
-///
-void OptionsManager::Save() {
-	// Check if it's actually modified
-	if (!modified) return;
-
-	// Open file
-	TextFileWriter file(filename,_T("UTF-8"));
-	file.WriteLineToFile(_T("[Config]"));
-
-	// Put variables in it
-	for (std::map<wxString,VariableData>::iterator cur=opt.begin();cur!=opt.end();cur++) {
-		file.WriteLineToFile((*cur).first + _T("=") + (*cur).second.AsText());
-	}
-
-	// Close
-	modified = false;
-}
-
-
-
-/// @brief Load 
-/// @return 
-///
-void OptionsManager::Load() {
-	// Check if file exists
-	wxFileName path(filename);
-	if (!path.FileExists()) {
-		modified = true;
-		return;
-	}
-
-	// Read header
-	TextFileReader file(filename);
-	wxString header;
-	try {
-		if (file.GetCurrentEncoding() != _T("binary"))
-			header = file.ReadLineFromFile();
-	}
-	catch (wxString e) {
-		header = _T("");
-	}
-	if (header != _T("[Config]")) {
-		wxMessageBox(_("Configuration file is either invalid or corrupt. The current file will be backed up and replaced with a default file."),_("Error"),wxCENTRE|wxICON_WARNING|wxOK);
-		wxRenameFile(filename,filename + wxString::Format(_T(".%i.backup"),wxGetUTCTime()));
-		modified = true;
-		return;
-	}
-
-	// Get variables
-	std::map<wxString,VariableData>::iterator cur;
-	wxString curLine;
-	while (file.HasMoreLines()) {
-		// Parse line
-		try {
-			curLine = file.ReadLineFromFile();
-		}
-		catch (wxString e) {
-			wxMessageBox(_("Configuration file is either invalid or corrupt. The current file will be backed up and replaced with a default file."),_("Error"),wxCENTRE|wxICON_WARNING|wxOK);
-			wxRenameFile(filename,filename + wxString::Format(_T(".%i.backup"),wxGetUTCTime()));
-			modified = true;
-			return;
-		}
-		if (curLine.IsEmpty()) continue;
-		size_t pos = curLine.Find(_T("="));
-		if (pos == wxString::npos) continue;
-		wxString key = curLine.Left(pos);
-		wxString value = curLine.Mid(pos+1);
-
-		// Find it
-		cur = opt.find(key);
-		if (cur != opt.end()) {
-			(*cur).second.ResetWith(value);
-		}
-		else SetText(key,value);
-	}
-
-	// Get last version
-	if (IsDefined(_T("Last Version"))) {
-		long temp;
-		AsText(_T("Last Version")).ToLong(&temp);
-		lastVersion = temp;
-	}
-	else lastVersion = 1; // This was implemented in 1784, assume that anything before that is 1.
-}
-
-
-
-/// @brief Write int 
-/// @param key           
-/// @param param         
-/// @param ifLastVersion 
-/// @return 
-///
-void OptionsManager::SetInt(wxString key,int param,int ifLastVersion) {
-	if (ifLastVersion == -1) {
-		if (overriding) ifLastVersion = 0;
-		else ifLastVersion = 0x7FFFFFFF;
-	}
-	if (lastVersion >= ifLastVersion) return;
-	opt[key.Lower()].SetInt(param);
-	if (curModType != MOD_OFF) optType[key.Lower()] = curModType;
-	modified = true;
-}
-
-
-
-/// @brief Write float 
-/// @param key           
-/// @param param         
-/// @param ifLastVersion 
-/// @return 
-///
-void OptionsManager::SetFloat(wxString key,double param,int ifLastVersion) {
-	if (ifLastVersion == -1) {
-		if (overriding) ifLastVersion = 0;
-		else ifLastVersion = 0x7FFFFFFF;
-	}
-	if (lastVersion >= ifLastVersion) return;
-	opt[key.Lower()].SetFloat(param);
-	if (curModType != MOD_OFF) optType[key.Lower()] = curModType;
-	modified = true;
-}
-
-
-
-/// @brief Write string 
-/// @param key           
-/// @param param         
-/// @param ifLastVersion 
-/// @return 
-///
-void OptionsManager::SetText(wxString key,wxString param,int ifLastVersion) {
-	if (ifLastVersion == -1) {
-		if (overriding) ifLastVersion = 0;
-		else ifLastVersion = 0x7FFFFFFF;
-	}
-	if (lastVersion >= ifLastVersion) return;
-	opt[key.Lower()].SetText(param);
-	if (curModType != MOD_OFF) optType[key.Lower()] = curModType;
-	modified = true;
-}
-
-
-
-/// @brief Write boolean 
-/// @param key           
-/// @param param         
-/// @param ifLastVersion 
-/// @return 
-///
-void OptionsManager::SetBool(wxString key,bool param,int ifLastVersion) {
-	if (ifLastVersion == -1) {
-		if (overriding) ifLastVersion = 0;
-		else ifLastVersion = 0x7FFFFFFF;
-	}
-	if (lastVersion >= ifLastVersion) return;
-	opt[key.Lower()].SetBool(param);
-	if (curModType != MOD_OFF) optType[key.Lower()] = curModType;
-	modified = true;
-}
-
-
-
-/// @brief Write colour 
-/// @param key           
-/// @param param         
-/// @param ifLastVersion 
-/// @return 
-///
-void OptionsManager::SetColour(wxString key,wxColour param,int ifLastVersion) {
-	if (ifLastVersion == -1) {
-		if (overriding) ifLastVersion = 0;
-		else ifLastVersion = 0x7FFFFFFF;
-	}
-	if (lastVersion >= ifLastVersion) return;
-	opt[key.Lower()].SetColour(param);
-	if (curModType != MOD_OFF) optType[key.Lower()] = curModType;
-	modified = true;
-}
-
-
-
-/// @brief Reset with 
-/// @param key   
-/// @param param 
-///
-void OptionsManager::ResetWith(wxString key,wxString param) {
-	opt[key.Lower()].ResetWith(param);
-	modified = true;
-}
-
-
-
-/// @brief As int 
-/// @param key 
-/// @return 
-///
-int OptionsManager::AsInt(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	if (cur != opt.end()) {
-		return (*cur).second.AsInt();
-	}
-	else throw key.c_str();//_T("Internal error: Attempted getting undefined configuration setting");
-}
-
-
-
-/// @brief As boolean 
-/// @param key 
-/// @return 
-///
-bool OptionsManager::AsBool(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	if (cur != opt.end()) {
-		return (*cur).second.AsBool();
-	}
-	else throw key.c_str();//_T("Internal error: Attempted getting undefined configuration setting");
-}
-
-
-
-/// @brief As float 
-/// @param key 
-/// @return 
-///
-double OptionsManager::AsFloat(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	if (cur != opt.end()) {
-		return (*cur).second.AsFloat();
-	}
-	else throw key.c_str();//_T("Internal error: Attempted getting undefined configuration setting");
-}
-
-
-
-/// @brief As string 
-/// @param key 
-/// @return 
-///
-wxString OptionsManager::AsText(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	if (cur != opt.end()) {
-		return (*cur).second.AsText();
-	}
-	else throw key.c_str();//_T("Internal error: Attempted getting undefined configuration setting");
-}
-
-
-
-/// @brief As colour 
-/// @param key 
-/// @return 
-///
-wxColour OptionsManager::AsColour(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	if (cur != opt.end()) {
-		return (*cur).second.AsColour();
-	}
-	else throw key.c_str();//_T("Internal error: Attempted getting undefined configuration setting");
-}
-
-
-
-/// @brief Modification type 
-/// @param key 
-/// @return 
-///
-ModType OptionsManager::GetModType(wxString key) {
-	std::map<wxString,ModType>::iterator cur;
-	cur = (optType.find(key.Lower()));
-	if (cur != optType.end()) {
-		return (*cur).second;
-	}
-	else return MOD_AUTOMATIC;
-}
-
-
-
-/// @brief Is defined? 
-/// @param key 
-/// @return 
-///
-bool OptionsManager::IsDefined(wxString key) {
-	std::map<wxString,VariableData>::iterator cur;
-	cur = (opt.find(key.Lower()));
-	return (cur != opt.end());
-}
-
-
-
-/// @brief Adds an item to a list of recents 
-/// @param entry 
-/// @param list  
-///
-void OptionsManager::AddToRecentList (wxString entry,wxString list) {
-	// Find strings already in recent list
-	wxArrayString orig;
-	wxString cur;
-	int recentMax = AsInt(list + _T(" max"));
-	int n = 0;
-	for (int i=0;i<recentMax;i++) {
-		wxString key = wxString::Format(_T("%s #%i"), list.c_str(), i+1);
-		if (IsDefined(key)) {
-			cur = AsText(key);
-			if (cur != entry) {
-				orig.Add(cur);
-				n++;
-			}
-		}
-		else break;
-	}
-
-	// Write back to options
-	SetText(list + _T(" #1"),entry);
-	if (n > recentMax-1) n = recentMax-1;
-	for (int i=0;i<n;i++) {
-		wxString key = wxString::Format(_T("%s #%i"), list.c_str(), i+2);
-		SetText(key,orig[i]);
-	}
-
-	// Save options
-	Save();
-}
-
-
-
-/// @brief Removes an item from a list of recents, if it's in the list 
-/// @param entry 
-/// @param list  
-///
-void OptionsManager::RemoveFromRecentList (wxString entry,wxString list) {
-	// Find strings already in recent list
-	wxArrayString cleaned;
-	wxString cur;
-	int recentMax = AsInt(list + _T(" max"));
-	int n = 0;
-	for (int i=0;i<recentMax;i++) {
-		wxString key = wxString::Format(_T("%s #%i"), list.c_str(), i+1);
-		if (IsDefined(key)) {
-			cur = AsText(key);
-			if (cur != entry) {
-				cleaned.Add(cur);
-				n++;
-			}
-		}
-		else break;
-	}
-
-	// Write back to options
-	if (n > recentMax-1) n = recentMax-1;
-	for (int i=0;i<n;i++) {
-		wxString key = wxString::Format(_T("%s #%i"), list.c_str(), i+1);
-		SetText(key,cleaned[i]);
-	}
-
-	// Save options
-	Save();
-}
-
-
-
-/// @brief Get recent list 
-/// @param list 
-/// @return 
-///
-wxArrayString OptionsManager::GetRecentList (wxString list) {
-	wxArrayString work;
-	int recentMax = AsInt(list + _T(" max"));
-	for (int i=0;i<recentMax;i++) {
-		wxString key = wxString::Format(_T("%s #%i"), list.c_str(), i+1);
-		if (IsDefined(key)) {
-			work.Add(Options.AsText(key));
-		}
-		else break;
-	}
-	return work;
-}
-
-
-
-/// @brief Set modification type 
-/// @param type 
-///
-void OptionsManager::SetModificationType(ModType type) {
-	curModType = type;
-}
-
-
-
-/// DOCME
-OptionsManager Options;
-
-
diff --git a/aegisub/src/options.h b/aegisub/src/options.h
index e75696bcd..69faef31f 100644
--- a/aegisub/src/options.h
+++ b/aegisub/src/options.h
@@ -35,115 +35,5 @@
 ///
 
 
-#pragma once
-
-
-///////////
-// Headers
-#ifndef AGI_PRE
-#include <map>
-#endif
-
-#include "variable_data.h"
-
-/// DOCME
-enum ModType {
-
-	/// DOCME
-	MOD_OFF = -1,
-
-	/// DOCME
-	MOD_AUTOMATIC,
-
-	/// DOCME
-	MOD_RESTART,
-
-	/// DOCME
-	MOD_EDIT_BOX,
-
-	/// DOCME
-	MOD_GRID,
-
-	/// DOCME
-	MOD_VIDEO,
-
-	/// DOCME
-	MOD_VIDEO_RELOAD,
-
-	/// DOCME
-	MOD_AUDIO,
-
-	/// DOCME
-	MOD_AUDIO_RELOAD
-};
-
-
-
-/// DOCME
-/// @class OptionsManager
-/// @brief DOCME
-///
-/// DOCME
-class OptionsManager {
-private:
-
-	/// DOCME
-	ModType curModType;
-
-	/// DOCME
-	bool modified;
-
-	/// DOCME
-	bool overriding;
-
-	/// DOCME
-	wxString filename;
-
-	/// DOCME
-	std::map<wxString,VariableData> opt;
-
-	/// DOCME
-	std::map<wxString,ModType> optType;
-
-	/// DOCME
-	int lastVersion;
-
-	void SetModificationType(ModType type);
-
-public:
-	OptionsManager();
-	~OptionsManager();
-
-	void Clear();
-	void SetFile(wxString file);
-	wxString GetFile() const;
-	void Save();
-	void Load();
-	void LoadDefaults(bool onlyDefaults=false,bool versionOverride=false);
-
-	void AddToRecentList (wxString entry,wxString list);
-	void RemoveFromRecentList (wxString entry,wxString list);
-	wxArrayString GetRecentList (wxString list);
-
-	void SetInt(wxString key,int param,int ifLastVersion=-1);
-	void SetFloat(wxString key,double param,int ifLastVersion=-1);
-	void SetBool(wxString key,bool param,int ifLastVersion=-1);
-	void SetText(wxString key,wxString param,int ifLastVersion=-1);
-	void SetColour(wxString key,wxColour param,int ifLastVersion=-1);
-	void ResetWith(wxString key,wxString param);
-
-	bool IsDefined(wxString key);
-	int AsInt(wxString key);
-	double AsFloat(wxString key);
-	bool AsBool(wxString key);
-	wxString AsText(wxString key);
-	wxColour AsColour(wxString key);
-	ModType GetModType(wxString key);
-};
-
-
-///////////////////
-// Global instance
-extern OptionsManager Options;
 
 
diff --git a/aegisub/src/preferences.cpp b/aegisub/src/preferences.cpp
new file mode 100644
index 000000000..9f6bc0d8f
--- /dev/null
+++ b/aegisub/src/preferences.cpp
@@ -0,0 +1,566 @@
+// 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.
+//
+// $Id$
+
+/// @file preferences.cpp
+/// @brief Preferences dialogue
+/// @ingroup configuration_ui
+
+
+#ifndef AGI_PRE
+#include <wx/filefn.h>
+#include <wx/spinctrl.h>
+#include <wx/stdpaths.h>
+#include <wx/treebook.h>
+#endif
+
+#include <libaegisub/exception.h>
+
+#include "colour_button.h"
+#include "libresrc/libresrc.h"
+#include "preferences.h"
+#include "main.h"
+#include "subtitles_provider_manager.h"
+#include "video_provider_manager.h"
+#include "audio_player_manager.h"
+#include "audio_provider_manager.h"
+
+/// Define make all platform-specific options visible in a single view.
+#define SHOW_ALL 1
+
+DEFINE_BASE_EXCEPTION_NOINNER(PreferencesError, agi::Exception)
+DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceIncorrectType, PreferencesError, "preferences/incorrect_type")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceNotSupported, PreferencesError, "preferences/not_supported")
+
+Preferences::Preferences(wxWindow *parent): wxDialog(parent, -1, _("Preferences"), wxDefaultPosition, wxSize(-1, 500)) {
+//	SetIcon(BitmapToIcon(GETIMAGE(options_button_24)));
+
+	book = new wxTreebook(this, -1, wxDefaultPosition, wxDefaultSize);
+
+	General(book);
+	Subtitles(book);
+	Audio(book);
+	Video(book);
+	Interface(book);
+	Interface_Colours(book);
+	Interface_Hotkeys(book);
+	Paths(book);
+	File_Associations(book);
+	Backup(book);
+	Automation(book);
+	Advanced(book);
+	Advanced_Interface(book);
+	Advanced_Audio(book);
+	Advanced_Video(book);
+
+	book->Fit();
+
+	/// @todo Save the last page and start with that page on next launch.
+	book->ChangeSelection(5);
+
+	// Bottom Buttons
+	wxStdDialogButtonSizer *stdButtonSizer = new wxStdDialogButtonSizer();
+	stdButtonSizer->AddButton(new wxButton(this,wxID_OK));
+	stdButtonSizer->AddButton(new wxButton(this,wxID_CANCEL));
+	stdButtonSizer->AddButton(new wxButton(this,wxID_APPLY));
+	stdButtonSizer->Realize();
+	wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
+	wxButton *defaultButton = new wxButton(this,2342,_("Restore Defaults"));
+	buttonSizer->Add(defaultButton,0,wxEXPAND);
+	buttonSizer->AddStretchSpacer(1);
+	buttonSizer->Add(stdButtonSizer,0,wxEXPAND);
+
+
+	// Main Sizer
+	wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
+	mainSizer->Add(book, 1 ,wxEXPAND | wxALL, 5);
+	mainSizer->Add(buttonSizer,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,5);
+	SetSizerAndFit(mainSizer);
+	this->SetMinSize(wxSize(-1, 500));
+	this->SetMaxSize(wxSize(-1, 500));
+	CenterOnParent();
+
+
+
+}
+
+void Preferences::OptionChoice(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, const wxArrayString &choices, const char *opt_name) {
+	agi::OptionValue *opt = OPT_GET(opt_name);
+
+	int type = opt->GetType();
+	wxString selection;
+
+	switch (type) {
+		case agi::OptionValue::Type_Int: {
+			selection = choices.Item(opt->GetInt());
+			break;
+		}
+		case agi::OptionValue::Type_String: {
+			selection.assign(opt->GetString());
+			break;
+		}
+
+		default:
+			throw PreferenceNotSupported("Unsupported type");
+	}
+
+	flex->Add(new wxStaticText(parent, wxID_ANY, name), 1, wxALIGN_CENTRE_VERTICAL);
+	wxComboBox *cb = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY | wxCB_DROPDOWN);
+	cb->SetValue(selection);
+	flex->Add(cb, 1, wxEXPAND, 0);
+}
+
+
+void Preferences::OptionBrowse(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, BrowseType browse_type, const char *opt_name) {
+
+	agi::OptionValue *opt = OPT_GET(opt_name);
+
+	if (opt->GetType() != agi::OptionValue::Type_String)
+		throw PreferenceIncorrectType("Option must be agi::OptionValue::Type_String for BrowseButton.");
+
+	flex->Add(new wxStaticText(parent, wxID_ANY, name), 1, wxALIGN_CENTRE_VERTICAL);
+
+	wxFlexGridSizer *button_flex = new wxFlexGridSizer(2,5,5);
+	button_flex->AddGrowableCol(0,1);
+	flex->Add(button_flex, 1, wxEXPAND, 5);
+
+	wxTextCtrl *text = new wxTextCtrl(parent, wxID_ANY , opt->GetString(), wxDefaultPosition, wxDefaultSize);
+	button_flex->Add(text, 1, wxEXPAND);
+	BrowseButton *browse = new BrowseButton(parent, wxID_ANY, wxEmptyString, browse_type);
+	browse->Bind(text);
+	button_flex->Add(browse, 1, wxEXPAND);
+
+}
+
+
+
+void Preferences::OptionAdd(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, const char *opt_name, double min, double max, double inc) {
+
+	agi::OptionValue *opt = OPT_GET(opt_name);
+
+	int type = opt->GetType();
+
+	switch (type) {
+
+		case agi::OptionValue::Type_Bool: {
+			wxCheckBox *cb = new wxCheckBox(parent, wxID_ANY, name);
+			flex->Add(cb, 1, wxEXPAND, 0);
+			cb->SetValue(opt->GetBool());
+			break;
+		}
+
+		case agi::OptionValue::Type_Int:
+		case agi::OptionValue::Type_Double: {
+			flex->Add(new wxStaticText(parent, wxID_ANY, name), 1, wxALIGN_CENTRE_VERTICAL);
+			wxSpinCtrlDouble *scd = new wxSpinCtrlDouble(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, opt->GetInt(), inc);
+			flex->Add(scd);
+
+			break;
+		}
+
+		case agi::OptionValue::Type_String: {
+			flex->Add(new wxStaticText(parent, wxID_ANY, name), 1, wxALIGN_CENTRE_VERTICAL);
+			wxTextCtrl *text = new wxTextCtrl(parent, wxID_ANY , opt->GetString(), wxDefaultPosition, wxDefaultSize);
+			flex->Add(text, 1, wxEXPAND);
+			break;
+		}
+
+		case agi::OptionValue::Type_Colour: {
+			flex->Add(new wxStaticText(parent, wxID_ANY, name), 1, wxALIGN_CENTRE_VERTICAL);
+			ColourButton *colour = new ColourButton(parent, wxID_ANY, wxSize(40,10));
+			wxColour current(opt->GetColour());
+			colour->SetColour(current);
+			flex->Add(colour);
+			break;
+		}
+
+		default:
+			throw PreferenceNotSupported("Unsupported type");
+	}
+
+}
+
+Preferences::~Preferences() {
+
+}
+
+
+void Preferences::OnOK(wxCommandEvent &event) {
+	EndModal(0);
+}
+
+
+void Preferences::OnApply(wxCommandEvent &event) {
+}
+
+
+void Preferences::OnCancel(wxCommandEvent &event) {
+	EndModal(0);
+}
+
+
+#define PAGE_CREATE(name)                           \
+	wxPanel *panel = new wxPanel(book, -1);         \
+	book->AddPage(panel, name, true);               \
+	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);    \
+
+#define SUBPAGE_CREATE(name)                        \
+	wxPanel *panel = new wxPanel(book, -1);         \
+	book->AddSubPage(panel, name, true);            \
+	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);    \
+
+
+#define PAGE_SIZER(name, name_value)                                                           \
+	wxSizer *name_value##_sizer = new wxStaticBoxSizer(wxHORIZONTAL, panel, name);             \
+	sizer->Add(name_value##_sizer, 0,wxEXPAND, 5);                                             \
+	wxFlexGridSizer *name_value##_flex = new wxFlexGridSizer(2,5,5);                           \
+	name_value##_flex->AddGrowableCol(0,1);                                                    \
+	name_value##_sizer->Add(name_value##_flex, 1, wxEXPAND, 5);                                \
+	sizer->AddSpacer(8);
+
+// name_value##_flex->SetFlexibleDirection(wxVERTICAL); \
+
+#define PAGE_END() \
+	panel->SetSizerAndFit(sizer);
+
+/// Skip a cell in a FlexGridSizer -- there's probably a better way to do this.
+#define CELL_SKIP(flex) \
+	flex->Add(new wxStaticText(panel, wxID_ANY , wxEmptyString), 0, wxALL, 5);
+
+
+
+void Preferences::Subtitles(wxTreebook *book) {
+	PAGE_CREATE(_("Subtitles"))
+
+	PAGE_SIZER(_("Options"), general)
+
+	OptionAdd(panel, general_flex, _("Enable call tips"), "App/Call Tips");
+	OptionAdd(panel, general_flex, _("Enable syntax highlighting"), "Subtitle/Highlight/Syntax");
+	OptionAdd(panel, general_flex, _("Link commiting of times"), "Subtitle/Edit Box/Link Time Boxes Commit");
+	OptionAdd(panel, general_flex, _("Overwrite-Insertion in time boxes"), "Subtitle/Time Edit/Insert Mode");
+
+	PAGE_SIZER(_("Grid"), grid)
+	OptionAdd(panel, grid_flex, _("Allow grid to take focus"), "Subtitle/Grid/Focus Allow");
+	OptionAdd(panel, grid_flex, _("Highlight visible subtitles"), "Subtitle/Grid/Highlight Subtitles in Frame");
+
+	PAGE_END()
+}
+
+
+void Preferences::General(wxTreebook *book) {
+
+	PAGE_CREATE(_("General"))
+
+	PAGE_SIZER(_("Startup"), startup)
+	OptionAdd(panel, startup_flex, _("Check for updates"), "App/Splash");
+	OptionAdd(panel, startup_flex, _("Show Splash Screen"), "App/Splash");
+
+	PAGE_SIZER(_("Recently Used Lists"), recent)
+	OptionAdd(panel, recent_flex, _("Files"), "Limits/MRU");
+	OptionAdd(panel, recent_flex, _("Find/Replace"), "Limits/Find Replace");
+	sizer->AddSpacer(15);
+
+	PAGE_SIZER(_("Undo / Redo Settings"), undo)
+	OptionAdd(panel, undo_flex, _("Undo Levels"), "Limits/MRU");
+
+	PAGE_END()
+}
+
+
+void Preferences::Audio(wxTreebook *book) {
+	PAGE_CREATE(_("Audio"))
+
+	PAGE_SIZER(_("Options"), general)
+	OptionAdd(panel, general_flex, _("Grab times from line upon selection"), "Audio/Grab Times on Select");
+	OptionAdd(panel, general_flex, _("Default mouse wheel to zoom"), "Audio/Wheel Default to Zoom");
+	OptionAdd(panel, general_flex, _("Lock scroll on cursor"), "Audio/Lock Scroll on Cursor");
+	OptionAdd(panel, general_flex, _("Snap to keyframes"), "Audio/Display/Snap/Keyframes");
+	OptionAdd(panel, general_flex, _("Snap to adjacent lines"), "Audio/Display/Snap/Other Lines");
+	OptionAdd(panel, general_flex, _("Auto-focus on mouse over"), "Audio/Auto/Focus");
+	OptionAdd(panel, general_flex, _("Play audio when stepping in video"), "Audio/Plays When Stepping Video");
+
+	CELL_SKIP(general_flex)
+
+	OptionAdd(panel, general_flex, _("Default timing length"), "Timing/Default Duration", 0, 36000);
+	OptionAdd(panel, general_flex, _("Default lead-in length"), "Audio/Lead/IN", 0, 36000);
+	OptionAdd(panel, general_flex, _("Default lead-out length"), "Audio/Lead/OUT", 0, 36000);
+
+	const wxString dtl_arr[3] = { _("Don't show"), _("Show previous"), _("Show all") };
+	wxArrayString choice_dtl(3, dtl_arr);
+	OptionChoice(panel, general_flex, _("Show inactive lines"), choice_dtl, "Audio/Inactive Lines Display Mode");
+
+	OptionAdd(panel, general_flex, _("Start-marker drag sensitivity"), "Audio/Start Drag Sensitivity", 1, 15);
+
+	PAGE_SIZER(_("Display Visual Options"), display)
+	OptionAdd(panel, display_flex, _("Secondary lines"), "Audio/Display/Draw/Secondary Lines");
+	OptionAdd(panel, display_flex, _("Selection background"), "Audio/Display/Draw/Selection Background");
+	OptionAdd(panel, display_flex, _("Timeline"), "Audio/Display/Draw/Timeline");
+	OptionAdd(panel, display_flex, _("Cursor time"), "Audio/Display/Draw/Cursor Time");
+	OptionAdd(panel, display_flex, _("Keyframes"), "Audio/Display/Draw/Keyframes");
+	OptionAdd(panel, display_flex, _("Video position"), "Audio/Display/Draw/Video Position");
+
+	PAGE_END()
+}
+
+
+void Preferences::Video(wxTreebook *book) {
+
+	PAGE_CREATE(_("Video"))
+
+	PAGE_SIZER(_("Options"), general)
+
+	OptionAdd(panel, general_flex, _("Show keyframes in slider"), "Video/Slider/Show Keyframes");
+	OptionAdd(panel, general_flex, _("Always show visual tools"), "Tool/Visual/Always Show");
+
+	const wxString cres_arr[3] = { _("Never"), _("Ask"), _("Always") };
+	wxArrayString choice_res(3, cres_arr);
+	OptionChoice(panel, general_flex, _("Match video resolution on open"), choice_res, "Video/Check Script Res");
+
+	const wxString czoom_arr[24] = { _T("12.5%"), _T("25%"), _T("37.5%"), _T("50%"), _T("62.5%"), _T("75%"), _T("87.5%"), _T("100%"), _T("112.5%"), _T("125%"), _T("137.5%"), _T("150%"), _T("162.5%"), _T("175%"), _T("187.5%"), _T("200%"), _T("212.5%"), _T("225%"), _T("237.5%"), _T("250%"), _T("262.5%"), _T("275%"), _T("287.5%"), _T("300%") };
+	wxArrayString choice_zoom(24, czoom_arr);
+	OptionChoice(panel, general_flex, _("Default Zoom"), choice_zoom, "Video/Default Zoom");
+
+	OptionAdd(panel, general_flex, _("Fast jump step in frames"), "Video/Slider/Fast Jump Step");
+
+	const wxString cscr_arr[3] = { _("?video"), _("?script"), _(".") };
+	wxArrayString scr_res(3, cscr_arr);
+	OptionChoice(panel, general_flex, _("Screenshot save path"), scr_res, "Path/Screenshot");
+
+
+
+	panel->SetSizerAndFit(sizer);
+
+
+}
+
+
+void Preferences::Interface(wxTreebook *book) {
+	PAGE_CREATE(_("Interface"))
+
+	PAGE_SIZER(_("Subtitle Grid"), grid)
+	OptionBrowse(panel, grid_flex, _("Font face"), BROWSE_FONT, "Subtitle/Grid/Font Face");
+	OptionAdd(panel, grid_flex, _("Font size"), "Subtitle/Grid/Font Size", 3, 42);
+
+	OptionAdd(panel, grid_flex, _("Hide overrides symbol"), "Subtitle/Grid/Hide Overrides Char");
+
+	PAGE_END()
+}
+
+void Preferences::Interface_Colours(wxTreebook *book) {
+
+	wxScrolled<wxPanel> *panel = new wxScrolled<wxPanel>(book, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
+	panel->SetScrollbars(0, 20, 0, 50);
+	book->AddSubPage(panel, _("Colours"), true);
+	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
+
+	PAGE_SIZER(_("General"), general)
+	OptionAdd(panel, general_flex, _("Modified Background"), "Colour/Background/Modified");
+
+	PAGE_SIZER(_("Audio Display"), audio)
+	OptionAdd(panel, audio_flex, _("Play cursor"), "Colour/Audio Display/Play Cursor");
+	OptionAdd(panel, audio_flex, _("Background"), "Colour/Audio Display/Background/Background");
+	OptionAdd(panel, audio_flex, _("Selection background"), "Colour/Audio Display/Background/Selection");
+	OptionAdd(panel, audio_flex, _("Selection background modified"), "Colour/Audio Display/Background/Selection Modified");
+	OptionAdd(panel, audio_flex, _("Seconds boundaries"), "Colour/Audio Display/Seconds Boundaries");
+	OptionAdd(panel, audio_flex, _("Waveform"), "Colour/Audio Display/Waveform");
+	OptionAdd(panel, audio_flex, _("Waveform selected"), "Colour/Audio Display/Waveform Selected");
+	OptionAdd(panel, audio_flex, _("Waveform Modified"), "Colour/Audio Display/Waveform Modified");
+	OptionAdd(panel, audio_flex, _("Waveform Inactive"), "Colour/Audio Display/Waveform Inactive");
+	OptionAdd(panel, audio_flex, _("Line boundary start"), "Colour/Audio Display/Line boundary Start");
+	OptionAdd(panel, audio_flex, _("Line boundary end"), "Colour/Audio Display/Line boundary End");
+	OptionAdd(panel, audio_flex, _("Line boundary inactive line"), "Colour/Audio Display/Line Boundary Inactive Line");
+	OptionAdd(panel, audio_flex, _("Syllable text"), "Colour/Audio Display/Syllable Text");
+	OptionAdd(panel, audio_flex, _("Syllable boundaries"), "Colour/Audio Display/Syllable Boundaries");
+
+	PAGE_SIZER(_("Syntax Highlighting"), syntax)
+	OptionAdd(panel, syntax_flex, _("Normal"), "Colour/Subtitle/Syntax/Normal");
+	OptionAdd(panel, syntax_flex, _("Brackets"), "Colour/Subtitle/Syntax/Brackets");
+	OptionAdd(panel, syntax_flex, _("Slashes and Parentheses"), "Colour/Subtitle/Syntax/Slashes");
+	OptionAdd(panel, syntax_flex, _("Tags"), "Colour/Subtitle/Syntax/Highlight Tags");
+	OptionAdd(panel, syntax_flex, _("Parameters"), "Colour/Subtitle/Syntax/Parameters");
+	OptionAdd(panel, syntax_flex, _("Error"), "Colour/Subtitle/Syntax/Error");
+	OptionAdd(panel, syntax_flex, _("Error Background"), "Colour/Subtitle/Syntax/Background/Error");
+	OptionAdd(panel, syntax_flex, _("Line Break"), "Colour/Subtitle/Syntax/Line Break");
+	OptionAdd(panel, syntax_flex, _("Karaoke templates"), "Colour/Subtitle/Syntax/Karaoke Template");
+
+	PAGE_SIZER(_("Subtitle Grid"), grid)
+	OptionAdd(panel, grid_flex, _("Standard foreground"), "Colour/Subtitle Grid/Standard");
+	OptionAdd(panel, grid_flex, _("Standard background"), "Colour/Subtitle Grid/Background/Background");
+	OptionAdd(panel, grid_flex, _("Selection foreground"), "Colour/Subtitle Grid/Selection");
+	OptionAdd(panel, grid_flex, _("Selection background"), "Colour/Subtitle Grid/Background/Selection");
+	OptionAdd(panel, grid_flex, _("Comment background"), "Colour/Subtitle Grid/Background/Comment");
+	OptionAdd(panel, grid_flex, _("Selected comment background"), "Colour/Subtitle Grid/Background/Selected Comment");
+	OptionAdd(panel, grid_flex, _("Left Column"), "Colour/Subtitle Grid/Left Column");
+	OptionAdd(panel, grid_flex, _("Active Line Border"), "Colour/Subtitle Grid/Active Border");
+	OptionAdd(panel, grid_flex, _("Lines"), "Colour/Subtitle Grid/Lines");
+	PAGE_END()
+}
+
+void Preferences::Interface_Hotkeys(wxTreebook *book) {
+	SUBPAGE_CREATE(_("Hotkeys"))
+
+	PAGE_SIZER(_("Hotkeys"), hotkey)
+	hotkey_flex->Add(new wxStaticText(panel, wxID_ANY , _T("To be added after hotkey rewrite.")), 0, wxALL, 5);
+
+	PAGE_END()
+}
+
+void Preferences::Paths(wxTreebook *book) {
+	PAGE_CREATE(_("Paths"))
+
+//	OptionBrowse(panel, general_flex, _("Dictionaries path"), BROWSE_FOLDER, "Path/Dictionary")
+
+	PAGE_END()
+}
+
+void Preferences::File_Associations(wxTreebook *book) {
+	PAGE_CREATE(_("File Assoc."))
+	PAGE_END()
+}
+
+void Preferences::Backup(wxTreebook *book) {
+	PAGE_CREATE(_("Backup"))
+
+
+	PAGE_SIZER(_("Automatic Save"), save)
+	OptionAdd(panel, save_flex, _("Enable"), "App/Auto/Backup");
+	CELL_SKIP(save_flex)
+	OptionAdd(panel, save_flex, _("Interval in seconds."), "App/Auto/Save Every Seconds");
+	OptionBrowse(panel, save_flex, _("Path"), BROWSE_FOLDER, "Path/Auto/Save");
+
+	PAGE_SIZER(_("Automatic Backup"), backup)
+	CELL_SKIP(backup_flex)
+	OptionAdd(panel, backup_flex, _("Enable"), "App/Auto/Backup");
+	OptionBrowse(panel, backup_flex, _("Path"), BROWSE_FOLDER, "Path/Auto/Backup");
+
+	PAGE_END()
+}
+
+void Preferences::Automation(wxTreebook *book) {
+	PAGE_CREATE(_("Automation"))
+
+	PAGE_SIZER(_("Options"), general)
+
+	OptionAdd(panel, general_flex, _("Base path"), "Path/Automation/Base");
+	OptionAdd(panel, general_flex, _("Include path"), "Path/Automation/Include");
+	OptionAdd(panel, general_flex, _("Auto-load path"), "Path/Automation/Autoload");
+
+	const wxString tl_arr[6] = { _("Fatal"), _("Error"), _("Warning"), _("Hint"), _("Debug"), _("Trace") };
+	wxArrayString tl_choice(6, tl_arr);
+	OptionChoice(panel, general_flex, _("Trace level"), tl_choice, "Automation/Trace Level");
+
+	const wxString tp_arr[3] = { _("Normal"), _("Below Normal (recommended)"), _("Lowest") };
+	wxArrayString tp_choice(3, tp_arr);
+	OptionChoice(panel, general_flex, _("Thread priority"), tp_choice, "Automation/Lua/Thread Priority");
+
+	const wxString ar_arr[4] = { _("No scripts"), _("Subtitle-local scripts"), _("Global autoload scripts"), _("All scripts") };
+	wxArrayString ar_choice(4, ar_arr);
+	OptionChoice(panel, general_flex, _("Autoreload on Export"), ar_choice, "Automation/Autoreload Mode");
+
+
+	PAGE_END()
+}
+
+
+void Preferences::Advanced(wxTreebook *book) {
+	PAGE_CREATE(_("Advanced"))
+
+	wxStaticText *warning = new wxStaticText(panel, wxID_ANY ,_("Changing these settings might result in bugs and/or crashes.  Do not touch these unless you know what you're doing."));
+	warning->SetFont(wxFont(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
+	sizer->Fit(panel);
+	warning->Wrap(400);
+	sizer->Add(warning, 0, wxALL, 5);
+
+	PAGE_END()
+}
+
+void Preferences::Advanced_Interface(wxTreebook *book) {
+	SUBPAGE_CREATE(_("Interface"))
+	PAGE_END()
+}
+
+
+void Preferences::Advanced_Audio(wxTreebook *book) {
+	SUBPAGE_CREATE(_("Audio"))
+
+	PAGE_SIZER(_("Options"), expert)
+
+	wxArrayString ap_choice = AudioProviderFactoryManager::GetFactoryList();
+	OptionChoice(panel, expert_flex, _("Audio provider"), ap_choice, "Audio/Provider");
+
+	wxArrayString apl_choice = AudioPlayerFactoryManager::GetFactoryList();
+	OptionChoice(panel, expert_flex, _("Audio player"), apl_choice, "Audio/Player");
+
+	PAGE_SIZER(_("Cache"), cache)
+	const wxString ct_arr[3] = { _("None (NOT RECOMMENDED)"), _("RAM"), _("Hard Disk") };
+	wxArrayString ct_choice(3, ct_arr);
+	OptionChoice(panel, cache_flex, _("Cache type"), ct_choice, "Audio/Cache/Type");
+
+	OptionBrowse(panel, cache_flex, _("Path"), BROWSE_FOLDER, "Audio/Cache/HD/Location");
+	OptionAdd(panel, cache_flex, _("File name"), "Audio/Cache/HD/Name");
+
+
+	PAGE_SIZER(_("Spectrum"), spectrum)
+
+	OptionAdd(panel, spectrum_flex, _("Cutoff"), "Audio/Renderer/Spectrum/Cutoff");
+
+	const wxString sq_arr[4] = { _("Regular quality"), _("Better quality"), _("High quality"), _("Insane quality") };
+	wxArrayString sq_choice(4, sq_arr);
+	OptionChoice(panel, spectrum_flex, _("Quality"), sq_choice, "Audio/Renderer/Spectrum/Quality");
+	OptionAdd(panel, spectrum_flex, _("Cache memory max (MB)"), "Audio/Renderer/Spectrum/Memory Max", 2, 1024);
+
+#if defined(WIN32) || defined(SHOW_ALL)
+	PAGE_SIZER(_("Windows Only"), windows);
+	const wxString adm_arr[3] = { _T("ConvertToMono"), _T("GetLeftChannel"), _T("GetRightChannel") };
+	wxArrayString adm_choice(3, adm_arr);
+	OptionChoice(panel, windows_flex, _("Avisynth down-mixer"), adm_choice, "Audio/Downmixer");
+#endif
+
+	PAGE_END()
+}
+
+
+void Preferences::Advanced_Video(wxTreebook *book) {
+	SUBPAGE_CREATE(_("Video"))
+
+	PAGE_SIZER(_("Options"), expert)
+	wxArrayString vp_choice = VideoProviderFactoryManager::GetFactoryList();
+	OptionChoice(panel, expert_flex, _("Video provider"), vp_choice, "Video/Provider");
+
+	wxArrayString sp_choice = SubtitlesProviderFactoryManager::GetFactoryList();
+	OptionChoice(panel, expert_flex, _("Subtitle provider"), sp_choice, "Subtitle/Provider");
+
+
+
+#if defined(WIN32) || defined(SHOW_ALL)
+	PAGE_SIZER(_("Windows Only"), windows);
+
+	OptionAdd(panel, windows_flex, _("Allow pre-2.56a Avisynth"), "Provider/Avisynth/Allow Ancient");
+	CELL_SKIP(windows_flex)
+	OptionAdd(panel, windows_flex, _("Avisynth memory limit"), "Provider/Avisynth/Memory Max");
+#endif
+
+	PAGE_END()
+}
+
+
+
+
+BEGIN_EVENT_TABLE(Preferences, wxDialog)
+    EVT_BUTTON(wxID_OK, Preferences::OnOK)
+    EVT_BUTTON(wxID_CANCEL, Preferences::OnCancel)
+    EVT_BUTTON(wxID_APPLY, Preferences::OnApply)
+END_EVENT_TABLE()
+
diff --git a/aegisub/src/preferences.h b/aegisub/src/preferences.h
new file mode 100644
index 000000000..ef1dd7ec1
--- /dev/null
+++ b/aegisub/src/preferences.h
@@ -0,0 +1,67 @@
+// 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.
+//
+// $Id$
+
+/// @file preferences.h
+/// @brief Preferences dialogue
+/// @see preferences.cpp
+/// @ingroup configuration_ui
+
+#ifndef AGI_PRE
+#include <wx/dialog.h>
+#include <wx/event.h>
+#include <wx/treebook.h>
+#include <wx/listctrl.h>
+#endif
+
+#include "browse_button.h"
+
+class Preferences: public wxDialog {
+	wxTreebook *book;
+
+	void OnOK(wxCommandEvent &event);
+	void OnCancel(wxCommandEvent &event);
+	void OnApply(wxCommandEvent &event);
+
+//	wxPanel *general;
+
+//	void OptionAdd(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, const char *opt_name);
+	void OptionAdd(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, const char *opt_name, double min=0, double max=100, double inc=1);
+	void OptionChoice(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, const wxArrayString &choices, const char *opt_name);
+	void OptionBrowse(wxPanel *parent, wxFlexGridSizer *flex, const wxString &name, BrowseType browse_type, const char *opt_name);
+
+	void General(wxTreebook *book);
+	void Subtitles(wxTreebook *book);
+	void Audio(wxTreebook *book);
+	void Video(wxTreebook *book);
+	void Interface(wxTreebook *book);
+	void Interface_Colours(wxTreebook *book);
+	void Interface_Hotkeys(wxTreebook *book);
+	void Paths(wxTreebook *book);
+	void File_Associations(wxTreebook *book);
+	void Backup(wxTreebook *book);
+	void Automation(wxTreebook *book);
+	void Advanced(wxTreebook *book);
+	void Advanced_Interface(wxTreebook *book);
+	void Advanced_Audio(wxTreebook *book);
+	void Advanced_Video(wxTreebook *book);
+
+public:
+	Preferences(wxWindow *parent);
+	~Preferences();
+
+	DECLARE_EVENT_TABLE()
+};
+
diff --git a/aegisub/src/spellchecker.cpp b/aegisub/src/spellchecker.cpp
index 898072657..a2723ac74 100644
--- a/aegisub/src/spellchecker.cpp
+++ b/aegisub/src/spellchecker.cpp
@@ -43,6 +43,8 @@
 #include "spellchecker_hunspell.h"
 #endif
 
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "spellchecker_manager.h"
 
@@ -53,7 +55,7 @@
 ///
 SpellChecker *SpellCheckerFactoryManager::GetSpellChecker() {
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Spell Checker")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Tool/Spell Checker/Backend")->GetString()));
 
 	// None available
 	if (list.Count() == 0) return 0; //throw _T("No spell checkers are available.");
diff --git a/aegisub/src/spellchecker_hunspell.cpp b/aegisub/src/spellchecker_hunspell.cpp
index d62c6a24c..4845cc386 100644
--- a/aegisub/src/spellchecker_hunspell.cpp
+++ b/aegisub/src/spellchecker_hunspell.cpp
@@ -53,6 +53,8 @@
 #include <hunspell/hunspell.hxx>
 
 #include "charset_conv.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "spellchecker_hunspell.h"
 #include "standard_paths.h"
@@ -63,7 +65,7 @@
 HunspellSpellChecker::HunspellSpellChecker() {
 	hunspell = NULL;
 	conv = NULL;
-	SetLanguage(Options.AsText(_T("Spell checker Language")));
+	SetLanguage(lagi_wxString(OPT_GET("Tool/Spell Checker/Language")->GetString()));
 }
 
 
@@ -219,7 +221,7 @@ wxArrayString HunspellSpellChecker::GetSuggestions(wxString word) {
 ///
 wxArrayString HunspellSpellChecker::GetLanguageList() {
 	// Get dir name
-	wxString path = StandardPaths::DecodePathMaybeRelative(Options.AsText(_T("Dictionaries path")), _T("?data")) + _T("/");
+	wxString path = StandardPaths::DecodePathMaybeRelative(lagi_wxString(OPT_GET("Path/Dictionary")->GetString()), _T("?data")) + _T("/");
 	wxArrayString list;
 	wxFileName folder(path);
 	if (!folder.DirExists()) return list;
@@ -259,7 +261,7 @@ void HunspellSpellChecker::SetLanguage(wxString language) {
 
 	// Get dir name
 	//FIXME: this should use ?user instead of ?data; however, since it apparently works already on win32, I'm not gonna mess with it right now :p
-	wxString path = StandardPaths::DecodePathMaybeRelative(Options.AsText(_T("Dictionaries path")), _T("?data")) + _T("/");
+	wxString path = StandardPaths::DecodePathMaybeRelative(lagi_wxString(OPT_GET("Path/Dictionary")->GetString()), _T("?data")) + _T("/");
 	wxString userPath = StandardPaths::DecodePath(_T("?user/dictionaries/user_"));
 
 	// Get affix and dictionary paths
diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp
index 1825891c1..edb8786c4 100644
--- a/aegisub/src/subs_edit_box.cpp
+++ b/aegisub/src/subs_edit_box.cpp
@@ -373,11 +373,11 @@ void SubsEditBox::SetToLine(int n,bool weak) {
 
 	// Set video
 	if (VideoContext::Get()->IsLoaded() && !weak) {
-		wxString sync;
-		if (Search.hasFocus) sync = _T("Find update video");
-		else sync = _T("Sync video with subs");
-		
-		if (Options.AsBool(sync)) {
+		bool sync;
+		if (Search.hasFocus) sync = OPT_GET("Tool/Search Replace/Video Update")->GetBool();
+		else sync = OPT_GET("Video/Subtitle Sync")->GetBool();
+
+		if (sync) {
 			VideoContext::Get()->Stop();
 			AssDialogue *cur = grid->GetDialogue(n);
 			if (cur) VideoContext::Get()->JumpToFrame(VFR_Output.GetFrameAtTime(cur->Start.GetMS(),true));
@@ -499,8 +499,7 @@ void SubsEditBox::OnKeyDown(wxStyledTextEvent &event) {
 ///
 void SubsEditBox::OnSyntaxBox(wxCommandEvent &event) {
 	TextEdit->UpdateStyle();
-	Options.SetBool(_T("Syntax Highlight Enabled"),SyntaxHighlight->GetValue());
-	Options.Save();
+	OPT_SET("Subtitle/Highlight/Syntax")->SetBool(SyntaxHighlight->GetValue());
 	event.Skip();
 }
 
@@ -724,7 +723,7 @@ void SubsEditBox::OnLayerEnter(wxCommandEvent &event) {
 ///
 void SubsEditBox::OnStartTimeChange(wxCommandEvent &event) {
 	if (StartTime->time > EndTime->time) StartTime->SetTime(EndTime->time.GetMS());
-	bool join = Options.AsBool(_T("Link Time Boxes Commit")) && EndTime->HasBeenModified();
+	bool join = OPT_GET("Subtitle/Edit Box/Link Time Boxes Commit")->GetBool() && EndTime->HasBeenModified();
 	StartTime->Update();
 	Duration->Update();
 	if (join) EndTime->Update();
@@ -738,7 +737,7 @@ void SubsEditBox::OnStartTimeChange(wxCommandEvent &event) {
 ///
 void SubsEditBox::OnEndTimeChange(wxCommandEvent &event) {
 	if (StartTime->time > EndTime->time) EndTime->SetTime(StartTime->time.GetMS());
-	bool join = Options.AsBool(_T("Link Time Boxes Commit")) && StartTime->HasBeenModified();
+	bool join = OPT_GET("Subtitle/Edit Box/Link Time Boxes Commit")->GetBool() && StartTime->HasBeenModified();
 	EndTime->Update();
 	Duration->Update();
 	if (join) StartTime->Update();
@@ -1015,7 +1014,7 @@ void SubsEditBox::Commit(bool stay) {
 		if (next >= nrows) {
 			AssDialogue *newline = new AssDialogue;
 			newline->Start = cur->End;
-			newline->End.SetMS(cur->End.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+			newline->End.SetMS(cur->End.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 			newline->Style = cur->Style;
 			newline->UpdateData();
 			grid->InsertLine(newline,next-1,true,true);
diff --git a/aegisub/src/subs_edit_ctrl.cpp b/aegisub/src/subs_edit_ctrl.cpp
index b6b0d5c79..1215db372 100644
--- a/aegisub/src/subs_edit_ctrl.cpp
+++ b/aegisub/src/subs_edit_ctrl.cpp
@@ -44,6 +44,8 @@
 #endif
 
 #include "ass_dialogue.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -201,54 +203,54 @@ void SubsTextEditCtrl::SetStyles() {
 	// Styles
 	wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
 	font.SetEncoding(wxFONTENCODING_DEFAULT); // this solves problems with some fonts not working properly
-	wxString fontname = Options.AsText(_T("Edit Font Face"));
+	wxString fontname = lagi_wxString(OPT_GET("Subtitle/Edit Box/Font Face")->GetString());
 	if (fontname != _T("")) font.SetFaceName(fontname);
-	int size = Options.AsInt(_T("Edit Font Size"));
+	int size = OPT_GET("Subtitle/Edit Box/Font Size")->GetInt();
 
 	// Normal style
 	StyleSetFont(0,font);
 	StyleSetSize(0,size);
-	StyleSetForeground(0,Options.AsColour(_T("Syntax Highlight Normal")));
+	StyleSetForeground(0,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Normal")->GetColour()));
 
 	// Brackets style
 	StyleSetFont(1,font);
 	StyleSetSize(1,size);
-	StyleSetForeground(1,Options.AsColour(_T("Syntax Highlight Brackets")));
+	StyleSetForeground(1,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Brackets")->GetColour()));
 
 	// Slashes/Parenthesis/Comma style
 	StyleSetFont(2,font);
 	StyleSetSize(2,size);
-	StyleSetForeground(2,Options.AsColour(_T("Syntax Highlight Slashes")));
+	StyleSetForeground(2,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Slashes")->GetColour()));
 
 	// Tags style
 	StyleSetFont(3,font);
 	StyleSetSize(3,size);
 	StyleSetBold(3,true);
-	StyleSetForeground(3,Options.AsColour(_T("Syntax Highlight Tags")));
+	StyleSetForeground(3,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Highlight Tags")->GetColour()));
 
 	// Error style
 	StyleSetFont(4,font);
 	StyleSetSize(4,size);
-	StyleSetForeground(4,Options.AsColour(_T("Syntax Highlight Error")));
-	StyleSetBackground(4,Options.AsColour(_T("Syntax Highlight Error Background")));
+	StyleSetForeground(4,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Error")->GetColour()));
+	StyleSetBackground(4,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Background/Error")->GetColour()));
 
 	// Tag Parameters style
 	StyleSetFont(5,font);
 	StyleSetSize(5,size);
-	StyleSetForeground(5,Options.AsColour(_T("Syntax Highlight Parameters")));
+	StyleSetForeground(5,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Parameters")->GetColour()));
 
 	// Line breaks style
 	StyleSetFont(6,font);
 	StyleSetSize(6,size);
 	StyleSetBold(6,true);
-	StyleSetForeground(6,Options.AsColour(_T("Syntax Highlight Line Break")));
+	StyleSetForeground(6,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Line Break")->GetColour()));
 
 	// Karaoke template code block style
 	StyleSetFont(7,font);
 	StyleSetSize(7,size);
 	StyleSetBold(7,true);
 	//StyleSetItalic(7,true);
-	StyleSetForeground(7,Options.AsColour(_T("Syntax Highlight Karaoke Template")));
+	StyleSetForeground(7,lagi_wxColour(OPT_GET("Colour/Subtitle/Syntax/Karaoke Template")->GetColour()));
 
 	// Misspelling indicator
 	IndicatorSetStyle(0,wxSTC_INDIC_SQUIGGLE);
@@ -264,7 +266,7 @@ void SubsTextEditCtrl::SetStyles() {
 ///
 void SubsTextEditCtrl::UpdateStyle(int start, int _length) {
 	// Styling enabled?
-	if (Options.AsBool(_T("Syntax Highlight Enabled")) == 0) return;
+	if (OPT_GET("Subtitle/Highlight/Syntax")->GetBool() == 0) return;
 
 	// Check if it's a template line
 	AssDialogue *diag = control->grid->GetDialogue(control->linen);
@@ -483,7 +485,7 @@ void SubsTextEditCtrl::UpdateStyle(int start, int _length) {
 ///
 void SubsTextEditCtrl::UpdateCallTip() {
 	// Enabled?
-	if (!Options.AsBool(_T("Call tips enabled"))) return;
+	if (!OPT_GET("App/Call Tips")->GetBool()) return;
 
 	// Get position and text
 	const unsigned int pos = GetReverseUnicodePosition(GetCurrentPos());
@@ -863,7 +865,7 @@ void SubsTextEditCtrl::ShowPopupMenu(int activePos) {
 		wxArrayString langs = spellchecker->GetLanguageList();	// This probably should be cached...
 
 		// Current language
-		wxString curLang = Options.AsText(_T("Spell checker language"));
+		wxString curLang = lagi_wxString(OPT_GET("Tool/Spell Checker/Language")->GetString());
 
 		// Languages
 		wxMenu *languageMenu = new wxMenu();
@@ -949,7 +951,7 @@ void SubsTextEditCtrl::ShowPopupMenu(int activePos) {
 		wxArrayString langs = thesaurus->GetLanguageList();	// This probably should be cached...
 
 		// Current language
-		wxString curLang = Options.AsText(_T("Thesaurus language"));
+		wxString curLang = lagi_wxString(OPT_GET("Tool/Thesaurus/Language")->GetString());
 
 		// Languages
 		wxMenu *languageMenu = new wxMenu();
@@ -1144,8 +1146,7 @@ void SubsTextEditCtrl::OnSetDicLanguage(wxCommandEvent &event) {
 	wxString lang;
 	if (index >= 0) lang = langs[index];
 	spellchecker->SetLanguage(lang);
-	Options.SetText(_T("Spell checker language"),lang);
-	Options.Save();
+	OPT_SET("Tool/Spell Checker/Language")->SetString(STD_STR(lang));
 
 	// Update styling
 	UpdateStyle();
@@ -1165,8 +1166,7 @@ void SubsTextEditCtrl::OnSetThesLanguage(wxCommandEvent &event) {
 	wxString lang;
 	if (index >= 0) lang = langs[index];
 	thesaurus->SetLanguage(lang);
-	Options.SetText(_T("Thesaurus language"),lang);
-	Options.Save();
+	OPT_SET("Tool/Thesaurus/Language")->SetString(STD_STR(lang));
 
 	// Update styling
 	UpdateStyle();
diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp
index f6bab0f18..bd5bd4f65 100644
--- a/aegisub/src/subs_grid.cpp
+++ b/aegisub/src/subs_grid.cpp
@@ -55,6 +55,7 @@
 #include "dialog_paste_over.h"
 #include "frame_main.h"
 #include "hotkeys.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -223,13 +224,11 @@ void SubtitlesGrid::OnPopupMenu(bool alternate) {
 /// @param event 
 ///
 void SubtitlesGrid::OnShowColMenu(wxCommandEvent &event) {
-	// Set width
 	int item = event.GetId()-MENU_SHOW_COL;
 	showCol[item] = !showCol[item];
 
-	// Save options
-	Options.SetBool(_T("Grid show column ") + AegiIntegerToString(item),showCol[item]);
-	Options.Save();
+	std::vector<bool> map(showCol, showCol + sizeof(showCol) / sizeof(bool));
+	OPT_SET("Subtitle/Grid/Column")->SetListBool(map);
 
 	// Update
 	SetColumnWidths();
@@ -456,14 +455,14 @@ void SubtitlesGrid::OnInsertBefore (wxCommandEvent &event) {
 		def->End = GetDialogue(n)->Start;
 	}
 	else if (GetDialogue(n-1)->End.GetMS() > GetDialogue(n)->Start.GetMS()) {
-		def->Start.SetMS(GetDialogue(n)->Start.GetMS()-Options.AsInt(_T("Timing Default Duration")));
+		def->Start.SetMS(GetDialogue(n)->Start.GetMS()-OPT_GET("Timing/Default Duration")->GetInt());
 		def->End = GetDialogue(n)->Start;
 	}
 	else {
 		def->Start = GetDialogue(n-1)->End;
 		def->End = GetDialogue(n)->Start;
 	}
-	if (def->End.GetMS() < def->Start.GetMS()) def->End.SetMS(def->Start.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+	if (def->End.GetMS() < def->Start.GetMS()) def->End.SetMS(def->Start.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 	def->Style = GetDialogue(n)->Style;
 
 	// Insert it
@@ -489,13 +488,13 @@ void SubtitlesGrid::OnInsertAfter (wxCommandEvent &event) {
 	if (n == nrows-1) {
 		def->Start = GetDialogue(n)->End;
 		def->End = GetDialogue(n)->End;
-		def->End.SetMS(def->End.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+		def->End.SetMS(def->End.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 	}
 	else {
 		def->Start = GetDialogue(n)->End;
 		def->End = GetDialogue(n+1)->Start;
 	}
-	if (def->End.GetMS() < def->Start.GetMS()) def->End.SetMS(def->Start.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+	if (def->End.GetMS() < def->Start.GetMS()) def->End.SetMS(def->Start.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 	def->Style = GetDialogue(n)->Style;
 
 	// Insert it
@@ -519,7 +518,7 @@ void SubtitlesGrid::OnInsertBeforeVideo (wxCommandEvent &event) {
 	AssDialogue *def = new AssDialogue;
 	int video_ms = VFR_Output.GetTimeAtFrame(VideoContext::Get()->GetFrameN(),true);
 	def->Start.SetMS(video_ms);
-	def->End.SetMS(video_ms+Options.AsInt(_T("Timing Default Duration")));
+	def->End.SetMS(video_ms+OPT_GET("Timing/Default Duration")->GetInt());
 	def->Style = GetDialogue(n)->Style;
 
 	// Insert it
@@ -543,7 +542,7 @@ void SubtitlesGrid::OnInsertAfterVideo (wxCommandEvent &event) {
 	AssDialogue *def = new AssDialogue;
 	int video_ms = VFR_Output.GetTimeAtFrame(VideoContext::Get()->GetFrameN(),true);
 	def->Start.SetMS(video_ms);
-	def->End.SetMS(video_ms+Options.AsInt(_T("Timing Default Duration")));
+	def->End.SetMS(video_ms+OPT_GET("Timing/Default Duration")->GetInt());
 	def->Style = GetDialogue(n)->Style;
 
 	// Insert it
@@ -1490,7 +1489,7 @@ void SubtitlesGrid::CommitChanges(bool force,bool videoOnly) {
 
 	if (!videoOnly) {
 		// Autosave if option is enabled
-		if (Options.AsBool(_T("Auto Save on Every Change"))) {
+		if (OPT_GET("App/Auto/Save on Every Change")->GetBool()) {
 			if (ass->IsModified() && !ass->filename.IsEmpty()) parentFrame->SaveSubtitles(false);
 		}
 
diff --git a/aegisub/src/subtitle_format_ttxt.cpp b/aegisub/src/subtitle_format_ttxt.cpp
index 323d10fbb..6ca68c5bf 100644
--- a/aegisub/src/subtitle_format_ttxt.cpp
+++ b/aegisub/src/subtitle_format_ttxt.cpp
@@ -41,6 +41,8 @@
 
 #include "ass_file.h"
 #include "ass_time.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "subtitle_format_ttxt.h"
 
@@ -369,7 +371,7 @@ void TTXTSubtitleFormat::ConvertToTTXT () {
 	// Insert blank line at the end
 	AssDialogue *diag = new AssDialogue();
 	diag->Start.SetMS(lastTime.GetMS());
-	diag->End.SetMS(lastTime.GetMS()+Options.AsInt(_T("Timing Default Duration")));
+	diag->End.SetMS(lastTime.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
 	diag->group = _T("[Events]");
 	diag->Style = _T("Default");
 	diag->Comment = false;
diff --git a/aegisub/src/subtitle_format_txt.cpp b/aegisub/src/subtitle_format_txt.cpp
index fc023335d..468d131ce 100644
--- a/aegisub/src/subtitle_format_txt.cpp
+++ b/aegisub/src/subtitle_format_txt.cpp
@@ -40,7 +40,9 @@
 #include "config.h"
 
 #include "ass_dialogue.h"
+#include "compat.h"
 #include "dialog_text_import.h"
+#include "main.h"
 #include "options.h"
 #include "subtitle_format_txt.h"
 #include "text_file_reader.h"
@@ -115,8 +117,8 @@ void TXTSubtitleFormat::ReadFile(wxString filename,wxString encoding) {	using na
 
 	// Data
 	wxString actor;
-	wxString separator = Options.AsText(_T("Text actor separator"));
-	wxString comment = Options.AsText(_T("Text comment starter"));
+	wxString separator = lagi_wxString(OPT_GET("Tool/Import/Text/Actor Separator")->GetString());
+	wxString comment = lagi_wxString(OPT_GET("Tool/Import/Text/Comment Starter")->GetString());
 	bool isComment = false;
 	int lines = 0;
 
@@ -184,7 +186,7 @@ void TXTSubtitleFormat::ReadFile(wxString filename,wxString encoding) {	using na
 		line->group = _T("[Events]");
 		line->Style = _T("Default");
 		line->Start.SetMS(0);
-		line->End.SetMS(Options.AsInt(_T("Timing Default Duration")));
+		line->End.SetMS(OPT_GET("Timing/Default Duration")->GetInt());
 		Line->push_back(line);
 	}
 }
diff --git a/aegisub/src/subtitles_provider.cpp b/aegisub/src/subtitles_provider.cpp
index e04861363..5ba4c0d26 100644
--- a/aegisub/src/subtitles_provider.cpp
+++ b/aegisub/src/subtitles_provider.cpp
@@ -39,6 +39,8 @@
 // Headers
 #include "config.h"
 
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #ifdef WITH_CSRI
 #include "subtitles_provider_csri.h"
@@ -61,7 +63,7 @@ SubtitlesProvider::~SubtitlesProvider() {
 ///
 bool SubtitlesProviderFactoryManager::ProviderAvailable() {
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Subtitles provider")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Subtitle/Provider")->GetString()));
 
 	// None available
 	return (list.Count() > 0);
@@ -74,7 +76,7 @@ bool SubtitlesProviderFactoryManager::ProviderAvailable() {
 ///
 SubtitlesProvider* SubtitlesProviderFactoryManager::GetProvider() {
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Subtitles provider")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Subtitle/Provider")->GetString()));
 
 	// None available
 	if (list.Count() == 0) throw _T("No subtitle providers are available.");
diff --git a/aegisub/src/text_file_writer.cpp b/aegisub/src/text_file_writer.cpp
index 0659ef23b..bae68f22d 100644
--- a/aegisub/src/text_file_writer.cpp
+++ b/aegisub/src/text_file_writer.cpp
@@ -41,6 +41,8 @@
 #endif
 
 #include "charset_conv.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "text_file_writer.h"
 
@@ -60,7 +62,7 @@ TextFileWriter::TextFileWriter(wxString filename, wxString encoding)
 		throw _T("Failed opening file for writing.");
 	}
 
-	if (encoding.IsEmpty()) encoding = Options.AsText(_T("Save Charset"));
+	if (encoding.IsEmpty()) encoding = lagi_wxString(OPT_GET("App/Save Charset")->GetString());
 	conv.reset(new AegisubCSConv(encoding, true));
 
 	// Write the BOM
diff --git a/aegisub/src/thesaurus_myspell.cpp b/aegisub/src/thesaurus_myspell.cpp
index 3deffc923..54542b882 100644
--- a/aegisub/src/thesaurus_myspell.cpp
+++ b/aegisub/src/thesaurus_myspell.cpp
@@ -45,7 +45,9 @@
 #include <wx/log.h>
 #endif
 
+#include "compat.h"
 #include "mythes.hxx"
+#include "main.h"
 #include "options.h"
 #include "standard_paths.h"
 #include "thesaurus_myspell.h"
@@ -57,7 +59,7 @@
 MySpellThesaurus::MySpellThesaurus() {
 	conv = NULL;
 	mythes = NULL;
-	SetLanguage(Options.AsText(_T("Thesaurus Language")));
+	SetLanguage(lagi_wxString(OPT_GET("Tool/Thesaurus/Language")->GetString()));
 }
 
 
@@ -107,7 +109,7 @@ void MySpellThesaurus::Lookup(wxString word,ThesaurusEntryArray &result) {
 ///
 wxArrayString MySpellThesaurus::GetLanguageList() {
 	// Get dir name
-	wxString path = StandardPaths::DecodePathMaybeRelative(Options.AsText(_T("Dictionaries path")), _T("?data")) + _T("/");
+	wxString path = StandardPaths::DecodePathMaybeRelative(lagi_wxString(OPT_GET("Path/Dictionary")->GetString()), _T("?data")) + _T("/");
 	wxArrayString list;
 	wxFileName folder(path);
 	if (!folder.DirExists()) return list;
@@ -153,7 +155,7 @@ void MySpellThesaurus::SetLanguage(wxString language) {
 	if (language.IsEmpty()) return;
 
 	// Get dir name
-	wxString path = StandardPaths::DecodePathMaybeRelative(Options.AsText(_T("Dictionaries path")), _T("?data")) + _T("/");
+	wxString path = StandardPaths::DecodePathMaybeRelative(lagi_wxString(OPT_GET("Path/Dictionary")->GetString()), _T("?data")) + _T("/");
 
 	// Get affix and dictionary paths
 	wxString idxpath = path + _T("th_") + language + _T(".idx");
diff --git a/aegisub/src/timeedit_ctrl.cpp b/aegisub/src/timeedit_ctrl.cpp
index ede290124..e1b9e13b7 100644
--- a/aegisub/src/timeedit_ctrl.cpp
+++ b/aegisub/src/timeedit_ctrl.cpp
@@ -47,6 +47,8 @@
 #endif
 
 #include "ass_time.h"
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "timeedit_ctrl.h"
 #include "vfr.h"
@@ -151,7 +153,7 @@ void TimeEdit::Modified(bool byUser) {
 
 	// Colour
 	if (showModified && !modified) {
-		SetBackgroundColour(Options.AsColour(_T("Edit Box Need Enter Background")));
+		SetBackgroundColour(lagi_wxColour(OPT_GET("Colour/Background/Modified")->GetColour()));
 	}
 	modified = true;
 
@@ -223,7 +225,7 @@ void TimeEdit::Update() {
 	}
 
 	// Update time if not on insertion mode
-	else if (!Options.AsBool(_T("Insert Mode on Time Boxes"))) {
+	else if (!OPT_GET("Subtitle/Time Edit/Insert Mode")->GetBool()) {
 		UpdateTime();
 		SetValue(time.GetASSFormated());
 	}
@@ -242,7 +244,7 @@ void TimeEdit::Update() {
 /// @param byUser 
 ///
 void TimeEdit::UpdateTime(bool byUser) {
-	bool insertion = Options.AsBool(_T("Insert Mode on Time Boxes"));
+	bool insertion = OPT_GET("Subtitle/Time Edit/Insert Mode")->GetBool();
 	wxString text = GetValue();
 	long start=0,end=0;
 	if (insertion && byUser) {
@@ -279,7 +281,7 @@ void TimeEdit::UpdateTime(bool byUser) {
 void TimeEdit::OnKeyDown(wxKeyEvent &event) {
 	// Get key ID
 	int key = event.GetKeyCode();
-	bool insertMode = Options.AsBool(_T("Insert Mode on Time Boxes"));
+	bool insertMode = OPT_GET("Subtitle/Time Edit/Insert Mode")->GetBool();
 	Refresh();
 
 	// Check if it's an acceptable key
@@ -320,7 +322,7 @@ void TimeEdit::OnKeyDown(wxKeyEvent &event) {
 /// @param event 
 ///
 void TimeEdit::OnKillFocus(wxFocusEvent &event) {
-	if (!byFrame && !Options.AsBool(_T("Insert Mode on Time Boxes"))) {
+	if (!byFrame && !OPT_GET("Subtitle/Time Edit/Insert Mode")->GetBool()) {
 		if (time.GetASSFormated() != GetValue()) {
 			UpdateTime();
 			SetValue(time.GetASSFormated());
@@ -340,7 +342,7 @@ void TimeEdit::OnKillFocus(wxFocusEvent &event) {
 void TimeEdit::OnMouseEvent(wxMouseEvent &event) {
 	// Right click context menu
 	if (event.RightUp()) {
-		if (!byFrame && Options.AsBool(_T("Insert Mode on Time Boxes"))) {
+		if (!byFrame && OPT_GET("Subtitle/Time Edit/Insert Mode")->GetBool()) {
 			wxMenu menu;
 			menu.Append(Time_Edit_Copy,_("&Copy"));
 			menu.Append(Time_Edit_Paste,_("&Paste"));
diff --git a/aegisub/src/vfr.cpp b/aegisub/src/vfr.cpp
index 812189e0d..56145db69 100644
--- a/aegisub/src/vfr.cpp
+++ b/aegisub/src/vfr.cpp
@@ -43,6 +43,8 @@
 #include <wx/filename.h>
 #endif
 
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "text_file_reader.h"
 #include "text_file_writer.h"
@@ -236,7 +238,7 @@ void FrameRate::Load(wxString filename) {
 	FrameRateType = VFR;
 
 	// Add to recent
-	Options.AddToRecentList(filename,_T("Recent timecodes"));
+	AegisubApp::Get()->mru->Add("Timecodes", STD_STR(filename));
 }
 
 
diff --git a/aegisub/src/video_box.cpp b/aegisub/src/video_box.cpp
index eae6c5d77..2b5f8ca92 100644
--- a/aegisub/src/video_box.cpp
+++ b/aegisub/src/video_box.cpp
@@ -52,6 +52,7 @@
 #include "frame_main.h"
 #include "help_button.h"
 #include "libresrc/libresrc.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -86,7 +87,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached)
 	VideoStopButton->SetToolTip(_("Stop video playback"));
 	AutoScroll = new ToggleBitmap(videoPage,Video_Auto_Scroll,GETIMAGE(toggle_video_autoscroll_24),wxSize(30,-1));
 	AutoScroll->SetToolTip(_("Toggle autoscroll of video"));
-	AutoScroll->SetValue(Options.AsBool(_T("Sync video with subs")));
+	AutoScroll->SetValue(OPT_GET("Video/Subtitle Sync")->GetBool());
 
 	// Seek
 	videoSlider = new VideoSlider(videoPage,-1);
@@ -111,7 +112,7 @@ VideoBox::VideoBox(wxWindow *parent, bool isDetached)
 	visualToolBar->AddTool(Video_Mode_Vector_Clip,_("Vector Clip"),GETIMAGE(visual_vector_clip_24),_("Clip subtitles to a vectorial area."),wxITEM_RADIO);
 	visualToolBar->AddSeparator();
 	visualToolBar->AddTool(Video_Mode_Realtime,_("Realtime"),GETIMAGE(visual_realtime_24),_("Toggle realtime display of changes."),wxITEM_CHECK);
-	visualToolBar->ToggleTool(Video_Mode_Realtime,Options.AsBool(_T("Video Visual Realtime")));
+	visualToolBar->ToggleTool(Video_Mode_Realtime,OPT_GET("Video/Visual Realtime")->GetBool());
 	visualToolBar->AddTool(Video_Mode_Help,_("Help"),GETIMAGE(visual_help_24),_("Open the manual page for Visual Typesetting."));
 	visualToolBar->Realize();
 	// Avoid ugly themed background on Vista and possibly also Win7
@@ -218,16 +219,14 @@ void VideoBox::OnVideoStop(wxCommandEvent &event) {
 /// @param event 
 ///
 void VideoBox::OnVideoToggleScroll(wxCommandEvent &event) {
-	Options.SetBool(_T("Sync video with subs"),AutoScroll->GetValue());
-	Options.Save();
+	OPT_SET("Video/Subtitle Sync")->SetBool(AutoScroll->GetValue());
 }
 
 /// @brief Realtime toggle 
 /// @param event 
 ///
 void VideoBox::OnToggleRealtime(wxCommandEvent &event) {
-	Options.SetBool(_T("Video Visual Realtime"),event.IsChecked());
-	Options.Save();
+	OPT_SET("Video/Visual Realtime")->SetBool(event.IsChecked());
 }
 
 
diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp
index 79728a5e9..50fd8f1f1 100644
--- a/aegisub/src/video_context.cpp
+++ b/aegisub/src/video_context.cpp
@@ -62,6 +62,8 @@
 #include "ass_style.h"
 #include "ass_time.h"
 #include "audio_display.h"
+#include "compat.h"
+#include "main.h"
 #include "mkv_wrap.h"
 #include "options.h"
 #include "standard_paths.h"
@@ -269,7 +271,7 @@ void VideoContext::SetVideo(const wxString &filename) {
 
 			// Set filename
 			videoName = filename;
-			Options.AddToRecentList(filename,_T("Recent vid"));
+			AegisubApp::Get()->mru->Add("Video", STD_STR(filename));
 			wxFileName fn(filename);
 			StandardPaths::SetPathValue(_T("?video"),fn.GetPath());
 
@@ -329,7 +331,7 @@ void VideoContext::UpdateDisplays(bool full) {
 
 	// Update audio display
 	if (audio && audio->loaded && audio->IsShownOnScreen()) {
-		if (Options.AsBool(_T("Audio Draw Video Position"))) {
+		if (OPT_GET("Audio/Display/Draw/Video Position")->GetBool()) {
 			audio->UpdateImage(false);
 		}
 	}
@@ -374,7 +376,7 @@ void VideoContext::JumpToFrame(int n) {
 	UpdateDisplays(false);
 
 	// Update grid
-	if (!isPlaying && Options.AsBool(_T("Highlight subs in frame"))) grid->Refresh(false);
+	if (!isPlaying && OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")->GetBool()) grid->Refresh(false);
 }
 
 
@@ -439,7 +441,7 @@ AegiVideoFrame VideoContext::GetFrame(int n,bool raw) {
 ///
 void VideoContext::SaveSnapshot(bool raw) {
 	// Get folder
-	wxString option = Options.AsText(_T("Video Screenshot Path"));
+	wxString option = lagi_wxString(OPT_GET("Path/Screenshot")->GetString());
 	wxFileName videoFile(videoName);
 	wxString basepath;
 	// Is it a path specifier and not an actual fixed path?
@@ -493,7 +495,7 @@ void VideoContext::PlayNextFrame() {
 	int thisFrame = frame_n;
 	JumpToFrame(frame_n + 1);
 	// Start playing audio
-	if (Options.AsBool(_T("Audio Plays When Stepping Video")))
+	if (OPT_GET("Audio/Plays When Stepping")->GetBool())
 		audio->Play(VFR_Output.GetTimeAtFrame(thisFrame),VFR_Output.GetTimeAtFrame(thisFrame + 1));
 }
 
@@ -507,7 +509,7 @@ void VideoContext::PlayPrevFrame() {
 	int thisFrame = frame_n;
 	JumpToFrame(frame_n -1);
 	// Start playing audio
-	if (Options.AsBool(_T("Audio Plays When Stepping Video")))
+	if (OPT_GET("Audio/Plays When Stepping")->GetBool())
 		audio->Play(VFR_Output.GetTimeAtFrame(thisFrame - 1),VFR_Output.GetTimeAtFrame(thisFrame));
 }
 
diff --git a/aegisub/src/video_display.cpp b/aegisub/src/video_display.cpp
index 8749ae16c..395de508b 100644
--- a/aegisub/src/video_display.cpp
+++ b/aegisub/src/video_display.cpp
@@ -58,6 +58,7 @@
 #include "ass_dialogue.h"
 #include "hotkeys.h"
 #include "options.h"
+#include "main.h"
 #include "video_out_gl.h"
 #include "vfr.h"
 #include "video_box.h"
@@ -107,12 +108,12 @@ int attribList[] = { WX_GL_RGBA , WX_GL_DOUBLEBUFFER, WX_GL_STENCIL_SIZE, 8, 0 }
 /// @class VideoOutRenderException
 /// @extends VideoOutException
 /// @brief An OpenGL error occured while uploading or displaying a frame
-class OpenGlException : public Aegisub::Exception {
+class OpenGlException : public agi::Exception {
 public:
 	OpenGlException(const wxChar *func, int err)
-		: Aegisub::Exception(wxString::Format("%s failed with error code %d", func, err))
+		: agi::Exception(STD_STR(wxString::Format("%s failed with error code %d", func, err)))
 	{ }
-	const wxChar * GetName() const { return L"video/opengl"; }
+	const char * GetName() const { return "video/opengl"; }
 	Exception * Copy() const { return new OpenGlException(*this); }
 };
 
@@ -172,8 +173,8 @@ void VideoDisplay::SetFrame(int frameNumber) {
 		PositionDisplay->SetValue(wxString::Format(L"%01i:%02i:%02i.%03i - %i", h, m, s, ms, frameNumber));
 		if (context->GetKeyFrames().Index(frameNumber) != wxNOT_FOUND) {
 			// Set the background color to indicate this is a keyframe
-			PositionDisplay->SetBackgroundColour(Options.AsColour(L"Grid selection background"));
-			PositionDisplay->SetForegroundColour(Options.AsColour(L"Grid selection foreground"));
+			PositionDisplay->SetBackgroundColour(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColour()));
+			PositionDisplay->SetForegroundColour(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
 		}
 		else {
 			PositionDisplay->SetBackgroundColour(wxNullColour);
@@ -260,7 +261,7 @@ void VideoDisplay::Render() try {
 	E(glLoadIdentity());
 	E(glOrtho(0.0f, w, h, 0.0f, -1000.0f, 1000.0f));
 
-	if (Options.AsBool(L"Show Overscan Mask")) {
+	if (OPT_GET("Video/Overscan Mask")->GetBool()) {
 		double ar = context->GetAspectRatioValue();
 
 		// Based on BBC's guidelines: http://www.bbc.co.uk/guidelines/dq/pdf/tv/tv_standards_london.pdf
@@ -279,7 +280,7 @@ void VideoDisplay::Render() try {
 
 	if (video.x > INT_MIN ||
 	    video.y > INT_MIN ||
-	    Options.AsBool(L"Always show visual tools")) {
+		OPT_GET("Tool/Visual/Always Show")->GetBool()) {
 		tool->Draw();
 	}
 
diff --git a/aegisub/src/video_out_gl.cpp b/aegisub/src/video_out_gl.cpp
index b7dc585d8..c0ec1c7cc 100644
--- a/aegisub/src/video_out_gl.cpp
+++ b/aegisub/src/video_out_gl.cpp
@@ -58,8 +58,8 @@ using std::max;
 #include "utils.h"
 #include "video_frame.h"
 
-#define CHECK_INIT_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutInitException(_T(#cmd), err)
-#define CHECK_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutRenderException(_T(#cmd), err)
+#define CHECK_INIT_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutInitException(#cmd, err)
+#define CHECK_ERROR(cmd) cmd; if (GLenum err = glGetError()) throw VideoOutRenderException(#cmd, err)
 
 /// @brief Structure tracking all precomputable information about a subtexture
 struct VideoOutGL::TextureInfo {
@@ -111,7 +111,7 @@ void VideoOutGL::DetectOpenGLCapabilities() {
 	// Test for supported internalformats
 	if (TestTexture(64, 64, GL_RGBA8)) internalFormat = GL_RGBA8;
 	else if (TestTexture(64, 64, GL_RGBA)) internalFormat = GL_RGBA;
-	else throw VideoOutInitException(L"Could not create a 64x64 RGB texture in any format.");
+	else throw VideoOutInitException("Could not create a 64x64 RGB texture in any format.");
 
 	// Test for the maximum supported texture size
 	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
@@ -256,7 +256,7 @@ void VideoOutGL::InitTextures(int width, int height, GLenum format, int bpp, boo
 				glTexCoord2f(right, bottom);  glVertex2f(x2, y2);
 				glTexCoord2f(left,  bottom);  glVertex2f(x1, y2);
 			glEnd();
-			if (GLenum err = glGetError()) throw VideoOutRenderException(L"GL_QUADS", err);
+			if (GLenum err = glGetError()) throw VideoOutRenderException("GL_QUADS", err);
 		}
 	}
 	CHECK_ERROR(glDisable(GL_TEXTURE_2D));
diff --git a/aegisub/src/video_out_gl.h b/aegisub/src/video_out_gl.h
index c33144884..7bc7c2393 100644
--- a/aegisub/src/video_out_gl.h
+++ b/aegisub/src/video_out_gl.h
@@ -34,12 +34,14 @@
 /// @ingroup video
 ///
 
-#include "include/aegisub/exception.h"
+#include <libaegisub/exception.h>
 
 #ifndef AGI_PRE
 #include <vector>
 #endif
 
+#include "compat.h"
+
 class AegiVideoFrame;
 
 /// @class VideoOutGL
@@ -102,17 +104,17 @@ public:
 /// @class VideoOutException
 /// @extends Aegisub::Exception
 /// @brief Base class for all exceptions thrown by VideoOutGL
-DEFINE_BASE_EXCEPTION_NOINNER(VideoOutException, Aegisub::Exception)
+DEFINE_BASE_EXCEPTION_NOINNER(VideoOutException, agi::Exception)
 
 /// @class VideoOutRenderException
 /// @extends VideoOutException
 /// @brief An OpenGL error occured while uploading or displaying a frame
 class VideoOutRenderException : public VideoOutException {
 public:
-	VideoOutRenderException(const wxChar *func, int err)
-		: VideoOutException(wxString::Format("%s failed with error code %d", func, err))
+	VideoOutRenderException(const char *func, int err)
+		: VideoOutException(STD_STR(wxString::Format("%s failed with error code %d", func, err)))
 	{ }
-	const wxChar * GetName() const { return L"videoout/opengl/render"; }
+	const char * GetName() const { return "videoout/opengl/render"; }
 	Exception * Copy() const { return new VideoOutRenderException(*this); }
 };
 /// @class VideoOutOpenGLException
@@ -120,10 +122,10 @@ public:
 /// @brief An OpenGL error occured while setting up the video display
 class VideoOutInitException : public VideoOutException {
 public:
-	VideoOutInitException(const wxChar *func, int err)
-		: VideoOutException(wxString::Format("%s failed with error code %d", func, err))
+	VideoOutInitException(const char *func, int err)
+		: VideoOutException(STD_STR(wxString::Format("%s failed with error code %d", func, err)))
 	{ }
-	VideoOutInitException(const wxChar *err) : VideoOutException(err) { }
-	const wxChar * GetName() const { return L"videoout/opengl/init"; }
+	VideoOutInitException(const char *err) : VideoOutException(err) { }
+	const char * GetName() const { return "videoout/opengl/init"; }
 	Exception * Copy() const { return new VideoOutInitException(*this); }
 };
diff --git a/aegisub/src/video_provider_cache.cpp b/aegisub/src/video_provider_cache.cpp
index 6778ed597..c0f94662d 100644
--- a/aegisub/src/video_provider_cache.cpp
+++ b/aegisub/src/video_provider_cache.cpp
@@ -38,6 +38,7 @@
 ///////////
 // Headers
 #include "config.h"
+#include "main.h"
 #include "options.h"
 
 #include "video_provider_cache.h"
@@ -50,7 +51,7 @@ VideoProviderCache::VideoProviderCache(VideoProvider *parent) {
 	master = parent;
 	cacheMax = 0;
 	if (parent->WantsCaching())
-		cacheMax = Options.AsInt(_T("Video cache size")) << 20; // convert MB to bytes
+		cacheMax = OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20; // convert MB to bytes
 	else
 		cacheMax = 0;
 }
diff --git a/aegisub/src/video_provider_ffmpegsource.cpp b/aegisub/src/video_provider_ffmpegsource.cpp
index ff329d298..32600b640 100644
--- a/aegisub/src/video_provider_ffmpegsource.cpp
+++ b/aegisub/src/video_provider_ffmpegsource.cpp
@@ -54,6 +54,7 @@
 
 #include "aegisub_endian.h"
 #include "include/aegisub/aegisub.h"
+#include "main.h"
 #include "options.h"
 #include "video_context.h"
 #include "video_provider_ffmpegsource.h"
@@ -168,7 +169,7 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	
 	// moment of truth
 	if (!IndexIsValid) {
-		int TrackMask = Options.AsBool(_T("FFmpegSource always index all tracks")) ? FFMS_TRACKMASK_ALL : FFMS_TRACKMASK_NONE;
+		int TrackMask = OPT_GET("Provider/FFmpegSource/Index All Tracks")->GetBool() ? FFMS_TRACKMASK_ALL : FFMS_TRACKMASK_NONE;
 		try {
 			// ignore audio decoding errors here, we don't care right now
 			Index = DoIndexing(Indexer, CacheName, TrackMask, FFMS_IEH_IGNORE);
@@ -201,14 +202,14 @@ void FFmpegSourceVideoProvider::LoadVideo(wxString filename) {
 	}
 
 	// set thread count
-	int Threads = Options.AsInt(_T("FFmpegSource decoding threads"));
+	int Threads = OPT_GET("Provider/Video/FFmpegSource/Decoding Threads")->GetInt();
 	if (Threads < 1)
 		throw _T("FFmpegSource video provider: invalid decoding thread count");
 
 	// set seekmode
 	// TODO: give this its own option?
 	int SeekMode;
-	if (Options.AsBool(_T("FFmpeg allow unsafe seeking")))
+	if (OPT_GET("Provider/Video/FFMpegSource/Unsafe Seeking")->GetBool())
 		SeekMode = FFMS_SEEK_UNSAFE;
 	else 
 		SeekMode = FFMS_SEEK_NORMAL;
diff --git a/aegisub/src/video_provider_manager.cpp b/aegisub/src/video_provider_manager.cpp
index 884027196..95c9842fc 100644
--- a/aegisub/src/video_provider_manager.cpp
+++ b/aegisub/src/video_provider_manager.cpp
@@ -39,6 +39,8 @@
 // Headers
 #include "config.h"
 
+#include "compat.h"
+#include "main.h"
 #include "options.h"
 #include "vfr.h"
 #ifdef WITH_AVISYNTH
@@ -80,7 +82,7 @@ VideoProvider *VideoProviderFactoryManager::GetProvider(wxString video) {
 	}
 
 	// List of providers
-	wxArrayString list = GetFactoryList(Options.AsText(_T("Video provider")));
+	wxArrayString list = GetFactoryList(lagi_wxString(OPT_GET("Video/Provider")->GetString()));
 
 	// None available
 	if (list.Count() == 0) throw _T("No video providers are available.");
diff --git a/aegisub/src/video_slider.cpp b/aegisub/src/video_slider.cpp
index 5983fb11d..c5da60382 100644
--- a/aegisub/src/video_slider.cpp
+++ b/aegisub/src/video_slider.cpp
@@ -44,6 +44,7 @@
 #endif
 
 #include "ass_dialogue.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -310,7 +311,7 @@ void VideoSlider::OnKeyDown(wxKeyEvent &event) {
 		// Fast move
 		if (!ctrl && !shift && alt) {
 			if (VideoContext::Get()->IsPlaying()) return;
-			int target = MID(min,GetValue() + direction * Options.AsInt(_T("Video Fast Jump Step")),max);
+			int target = MID(min,GetValue() + direction * OPT_GET("Video/Slider/Fast Jump Step")->GetInt(),max);
 			if (target != GetValue()) VideoContext::Get()->JumpToFrame(target);
 			return;
 		}
@@ -478,7 +479,7 @@ void VideoSlider::DrawImage(wxDC &destdc) {
 
 	// Draw keyframes
 	int curX;
-	if (Display && Options.AsBool(_T("Show keyframes on video slider"))) {
+	if (Display && OPT_GET("Video/Slider/Show Keyframes")->GetBool()) {
 		dc.SetPen(wxPen(shad));
 		wxArrayInt KeyFrames = VideoContext::Get()->GetKeyFrames();
 		int keys = KeyFrames.Count();
diff --git a/aegisub/src/visual_tool.cpp b/aegisub/src/visual_tool.cpp
index c20c520c4..0c01c27f1 100644
--- a/aegisub/src/visual_tool.cpp
+++ b/aegisub/src/visual_tool.cpp
@@ -51,6 +51,7 @@
 #include "ass_style.h"
 #include "ass_time.h"
 #include "export_visible_lines.h"
+#include "main.h"
 #include "options.h"
 #include "subs_edit_box.h"
 #include "subs_grid.h"
@@ -100,7 +101,7 @@ VisualTool<FeatureType>::~VisualTool() {
 
 template<class FeatureType>
 void VisualTool<FeatureType>::OnMouseEvent (wxMouseEvent &event) {
-	bool realTime = Options.AsBool(L"Video Visual Realtime");
+	bool realTime = OPT_GET("Video/Visual Realtime")->GetBool();
 
 	if (event.Leaving()) {
 		Update();
diff --git a/aegisub/tests/Makefile.am b/aegisub/tests/Makefile.am
new file mode 100644
index 000000000..f15228a92
--- /dev/null
+++ b/aegisub/tests/Makefile.am
@@ -0,0 +1,18 @@
+# $Id$
+
+bin_PROGRAMS = run
+
+run_LDFLAGS= -L../libaegisub -laegisub-2.2 -lgtest -rpath /mnt/free/aegisub/commit/libaegisub/.libs
+run_CPPFLAGS= -I../src/include -I../libaegisub/include
+
+
+run_SOURCES = \
+		main.cpp \
+		util.cpp \
+		libaegisub_access.cpp \
+		libaegisub_cajun.cpp \
+		libaegisub_util.cpp \
+		libaegisub_mru.cpp
+		
+run_SOURCES += \
+	*.h
diff --git a/aegisub/tests/libaegisub_access.cpp b/aegisub/tests/libaegisub_access.cpp
new file mode 100644
index 000000000..17a8b2f3e
--- /dev/null
+++ b/aegisub/tests/libaegisub_access.cpp
@@ -0,0 +1,109 @@
+// 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.
+//
+// $Id$
+
+/// @file libaegisub_access.cpp
+/// @brief agi::acs tests.
+/// @ingroup acs
+
+#include <libaegisub/access.h>
+
+#include "main.h"
+
+using namespace agi::acs;
+
+class lagi_acs : public libagi {
+
+protected:
+    // place holder for future code placement
+};
+
+
+// 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_AcsNotFound(func, pass) \
+	TEST_F(lagi_acs, func##ExAcsNotFound) { \
+		EXPECT_THROW(func("data/nonexistent"), AcsNotFound); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+#define EX_AcsAccess(func, fail, pass) \
+	TEST_F(lagi_acs, func##ExAcsAccess) { \
+		EXPECT_THROW(func(fail), AcsAccess); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+#define EX_AcsNotAFile(func, fail, pass) \
+	TEST_F(lagi_acs, func##ExAcsNotAFile) { \
+		EXPECT_THROW(func(fail), AcsNotAFile); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+#define EX_AcsNotADirectory(func, fail, pass) \
+	TEST_F(lagi_acs, func##ExAcsNotADirectory) { \
+		EXPECT_THROW(func(fail), AcsNotADirectory); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+#define EX_AcsRead(func, fail, pass) \
+	TEST_F(lagi_acs, func##ExAcsRead) { \
+		EXPECT_THROW(func(fail), AcsRead); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+#define EX_AcsWrite(func, fail, pass) \
+	TEST_F(lagi_acs, func##ExAcsWrite) { \
+		EXPECT_THROW(func(fail), AcsWrite); \
+		EXPECT_NO_THROW(func(pass)); \
+	}
+
+
+/*
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsFatal, AcsError, "io/fatal")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsAccessRead, AcsError, "io/read")
+DEFINE_SIMPLE_EXCEPTION_NOINNER(AcsAccessWrite, AcsError, "io/write")
+*/
+
+
+EX_AcsNotFound(CheckFileRead, "data/file")
+EX_AcsAccess(CheckFileRead, "data/file_access_denied", "data/file")
+EX_AcsNotAFile(CheckFileRead, "data/dir", "data/file")
+TEST_F(lagi_acs, CheckFileRead) {
+	EXPECT_NO_THROW(CheckFileRead("data/file"));
+}
+
+EX_AcsNotFound(CheckFileWrite, "data/file")
+EX_AcsAccess(CheckFileWrite, "data/file_access_denied", "data/file")
+EX_AcsNotAFile(CheckFileWrite, "data/dir", "data/file")
+EX_AcsWrite(CheckFileWrite, "data/file_read_only", "data/file")
+TEST_F(lagi_acs, CheckFileWrite) {
+	EXPECT_NO_THROW(CheckFileRead("data/file"));
+}
+
+EX_AcsNotFound(CheckDirRead, "data/dir")
+EX_AcsAccess(CheckDirRead, "data/dir_access_denied", "data/dir")
+EX_AcsNotADirectory(CheckDirRead, "data/file", "data/dir")
+TEST_F(lagi_acs, CheckDirRead) {
+	EXPECT_NO_THROW(CheckDirRead("data/dir"));
+}
+
+EX_AcsNotFound(CheckDirWrite, "data/dir")
+EX_AcsAccess(CheckDirWrite, "data/dir_access_denied", "data/dir")
+EX_AcsNotADirectory(CheckDirWrite, "data/file", "data/dir")
+EX_AcsWrite(CheckDirWrite, "data/dir_read_only", "data/dir")
+TEST_F(lagi_acs, CheckDirWrite) {
+	EXPECT_NO_THROW(CheckDirWrite("data/dir"));
+}
diff --git a/aegisub/tests/libaegisub_cajun.cpp b/aegisub/tests/libaegisub_cajun.cpp
new file mode 100644
index 000000000..61870517b
--- /dev/null
+++ b/aegisub/tests/libaegisub_cajun.cpp
@@ -0,0 +1,159 @@
+// 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.
+//
+// $Id$
+
+/// @file libaegisub_cajun.cpp
+/// @brief Cajun/Json tests.
+/// @ingroup cajun
+
+#include "main.h"
+
+#include <libaegisub/cajun/reader.h>
+#include <libaegisub/cajun/writer.h>
+#include <libaegisub/cajun/elements.h>
+#include <libaegisub/cajun/visitor.h>
+
+class lagi_cajun : public libagi {
+
+protected:
+    // place holder for future code placement
+};
+
+
+TEST_F(lagi_cajun, ObjectCreateNumber) {
+	json::Object obj;
+	obj["Integer"] = json::Number(1);
+}
+
+
+TEST_F(lagi_cajun, ObjectCreateString) {
+	json::Object obj;
+	obj["String"] = json::String("test");
+}
+
+
+TEST_F(lagi_cajun, ObjectCreateBoolean) {
+	json::Object obj;
+	obj["Boolean"] = json::Boolean(true);
+}
+
+
+TEST_F(lagi_cajun, ObjectCreateNull) {
+	json::Object obj;
+	obj["Null"] = json::Null();
+}
+
+
+TEST_F(lagi_cajun, ObjectCreateArray) {
+	json::Object obj;
+	obj["Inside"] = json::String();
+	json::Array array;
+	array.Insert(obj);
+}
+
+
+TEST_F(lagi_cajun, ObjectEquality) {
+	json::Object obj;
+	obj["Inside"] = json::String();
+	json::Array array;
+	array.Insert(obj);
+	obj["Array"] = array;
+	json::Object obj_dupe = obj;
+
+	EXPECT_TRUE(obj_dupe == obj);
+
+	obj_dupe["NotEqual"] = array;
+	EXPECT_FALSE(obj_dupe == obj);
+
+}
+
+// Cajun doesn't have chained exceptions, so there's no real way to test the
+// difference in the following exceptions.  I'll try emailing the author to see
+// If they'll add them, if not we'll do it ourselves.
+TEST_F(lagi_cajun, ExExceptionArrayOutOfBounds) {
+	json::Object obj;
+	obj["Inside"] = json::String();
+	json::Array array;
+	array.Insert(obj);
+	obj["Array"] = array;
+	const json::Array& const_array = obj["Array"];
+
+	EXPECT_THROW({
+		const json::String& str = const_array[1];
+		str.Value(); // avoid unused variable warning
+	}, json::Exception);
+}
+
+
+TEST_F(lagi_cajun, ExExceptionArrayObjNotFound) {
+	json::Object obj;
+	obj["Inside"] = json::String();
+	json::Array array;
+	array.Insert(obj);
+	obj["Array"] = array;
+	const json::Array& const_array = obj["Array"];
+
+	EXPECT_THROW({
+		const json::String& str = const_array[0]["Nothere"];
+		str.Value(); // avoid unused variable warning
+	}, json::Exception);
+}
+
+
+TEST_F(lagi_cajun, ExExceptionArrayBadCast) {
+	json::Object obj;
+	obj["Inside"] = json::String();
+	json::Array array;
+	array.Insert(obj);
+	obj["Array"] = array;
+	const json::Array& const_array = obj["Array"];
+	const json::Object& arr_obj = const_array[0];
+
+	EXPECT_THROW({
+		const json::UnknownElement& unkn = arr_obj["Array"]["BadCast"];
+		unkn["Array"]; // avoid unused variable warning
+	}, json::Exception);
+}
+
+
+TEST_F(lagi_cajun, Read) {
+	json::Object obj;
+	std::istringstream doc("{\"String\" : \"This is a test\"}");
+	EXPECT_NO_THROW(json::Reader::Read(obj, doc));
+}
+
+
+TEST_F(lagi_cajun, Write) {
+	json::Object obj;
+	std::istringstream doc("{\"String\" : \"This is a test\"}");
+
+	std::stringstream stream;
+	EXPECT_NO_THROW(json::Writer::Write(obj, stream));
+}
+
+TEST_F(lagi_cajun, ReaderExParseException) {
+	json::Object obj;
+	std::istringstream doc("[1 2]");
+
+	EXPECT_THROW(json::Reader::Read(obj, doc), json::Reader::ParseException);
+}
+
+
+TEST_F(lagi_cajun, ReaderExScanException) {
+	json::Object obj;
+	std::istringstream doc("[true, false, thiswontwork]");
+
+	EXPECT_THROW(json::Reader::Read(obj, doc), json::Reader::ScanException);
+}
diff --git a/aegisub/tests/libaegisub_mru.cpp b/aegisub/tests/libaegisub_mru.cpp
new file mode 100644
index 000000000..8ceae9a70
--- /dev/null
+++ b/aegisub/tests/libaegisub_mru.cpp
@@ -0,0 +1,84 @@
+// 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.
+//
+// $Id$
+
+/// @file libaegisub_mru.cpp
+/// @brief agi::mru (Most Recently Used)
+/// @ingroup mru
+
+#include <libaegisub/mru.h>
+#include "main.h"
+#include "util.h"
+
+class lagi_mru : public libagi {
+protected:
+	std::string default_mru;
+	std::string conf_ok;
+
+	virtual void SetUp() {
+		default_mru = "{\"Valid_Int\" : []}";
+		conf_ok = "./data/mru_ok.json";
+	}
+};
+
+
+TEST_F(lagi_mru, MRUConstructFromFile) {
+	agi::MRUManager mru(conf_ok, default_mru);
+}
+
+TEST_F(lagi_mru, MRUConstructFromString) {
+	util::remove("data/mru_tmp");
+
+	const std::string nonexistent("data/mru_tmp");
+	agi::MRUManager mru(nonexistent, default_mru);
+}
+
+
+TEST_F(lagi_mru, MRUConstructInvalid) {
+	util::copy("data/mru_invalid.json", "data/mru_tmp");
+
+	const std::string nonexistent("data/mru_tmp");
+	agi::MRUManager mru(nonexistent, default_mru);
+
+	// Make sure it didn't load from the file.
+	EXPECT_THROW(mru.Add("Invalid", "/path/to/file"), agi::MRUErrorInvalidKey);
+	EXPECT_NO_THROW(mru.Add("Valid_Int", "/path/to/file"));
+}
+
+TEST_F(lagi_mru, MRUEntryAdd) {
+	util::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, MRUEntryRemove) {
+	util::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"));
+}
+
+TEST_F(lagi_mru, MRUKeyInvalid) {
+	util::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);
+}
+
+TEST_F(lagi_mru, MRUKeyValid) {
+	util::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"));
+}
+
diff --git a/aegisub/tests/libaegisub_option.cpp b/aegisub/tests/libaegisub_option.cpp
new file mode 100644
index 000000000..0dd1bcf3b
--- /dev/null
+++ b/aegisub/tests/libaegisub_option.cpp
@@ -0,0 +1,41 @@
+
+// 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.
+//
+// $Id$
+
+/// @file libaegisub_option.cpp
+/// @brief Option class
+/// @ingroup option
+
+#include <libaegisub/option.h>
+#include "main.h"
+#include "util.h"
+
+class lagi_option : public libagi {
+protected:
+	std::string default_opt;
+	std::string conf_ok;
+
+	virtual void SetUp() {
+		default_opt = "{\"Valid\" : \"This is valid\"}";
+		conf_ok = "./data/option_string.json";
+	}
+};
+
+
+TEST_F(lagi_option, OptionConstructFromFile) {
+	EXPECT_NO_THROW(agi::Options *opt = new agi::Options(conf_ok, default_opt));
+}
+
diff --git a/aegisub/tests/libaegisub_util.cpp b/aegisub/tests/libaegisub_util.cpp
new file mode 100644
index 000000000..6c47fa81f
--- /dev/null
+++ b/aegisub/tests/libaegisub_util.cpp
@@ -0,0 +1,70 @@
+// 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.
+//
+// $Id$
+
+/// @file libaegisub_util.cpp
+/// @brief agi::util (Utilities)
+/// @ingroup util
+
+#include <fstream>
+#include <libaegisub/util.h>
+#include "main.h"
+
+class lagi_util : public libagi {
+protected:
+// placeholder
+};
+
+
+namespace agi {
+
+TEST_F(lagi_util, UtilDirnameEmpty) {
+	EXPECT_STREQ(".", util::DirName("").c_str());
+}
+
+TEST_F(lagi_util, UtilDirnameNoTrailingSlash) {
+	EXPECT_STREQ(".", util::DirName("dot").c_str());
+}
+
+TEST_F(lagi_util, UtilDirnameRoot) {
+	EXPECT_STREQ("/", util::DirName("/").c_str());
+}
+
+TEST_F(lagi_util, UtilDirnameHeir) {
+	EXPECT_STREQ("/last/part/not_stripped/", util::DirName("/last/part/not_stripped/").c_str());
+}
+
+TEST_F(lagi_util, UtilDirnameHeirNoTrailingSlash) {
+	EXPECT_STREQ("/last/part/", util::DirName("/last/part/stripped").c_str());
+}
+
+TEST_F(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_F(lagi_util, UtilRenameNew) {
+	util::Rename("./data/rename_me", "./data/rename_me_renamed");
+	util::Rename("./data/rename_me_renamed", "./data/rename_me");
+}
+
+TEST_F(lagi_util, UtilRenameExNotFound) {
+	EXPECT_THROW(util::Rename("./data/nonexistent", ""), acs::AcsNotFound);
+}
+
+
+
+} // namespace agi
diff --git a/aegisub/tests/main.cpp b/aegisub/tests/main.cpp
new file mode 100644
index 000000000..54e935bd8
--- /dev/null
+++ b/aegisub/tests/main.cpp
@@ -0,0 +1,27 @@
+// 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.
+//
+// $Id$
+
+/// @file main.cpp
+/// @brief Main
+/// @ingroup main
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/aegisub/tests/main.h b/aegisub/tests/main.h
new file mode 100644
index 000000000..078e231c9
--- /dev/null
+++ b/aegisub/tests/main.h
@@ -0,0 +1,34 @@
+// 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.
+//
+// $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
+
diff --git a/aegisub/tests/setup.sh b/aegisub/tests/setup.sh
new file mode 100755
index 000000000..24fb6a980
--- /dev/null
+++ b/aegisub/tests/setup.sh
@@ -0,0 +1,40 @@
+chmod 664 data/*
+rm -rf data
+mkdir -p data
+
+touch data/file
+mkdir data/dir
+
+touch data/file_access_denied
+chmod 000 data/file_access_denied
+
+touch data/file_read_only
+chmod 444 data/file_read_only
+
+
+mkdir data/dir_access_denied
+chmod 000 data/dir_access_denied
+
+mkdir data/dir_read_only
+chmod 444 data/dir_read_only
+
+echo '{"Valid" : []}' > data/mru_ok.json
+
+echo '{"Invalid" : [1 3]}' > data/mru_invalid.json
+
+touch data/rename_me
+
+touch data/rename_me_overwrite
+touch data/rename_me_overwrite_renamed
+
+
+echo '{"String" : "This is a test"}' > data/option_string.json
+echo '{"Integer" : 1}' > data/option_integer.json
+echo '{"Double" : 2.1}' > data/option_double.json
+echo '{"Bool" : true}' > data/option_bool.json
+echo '{"Null" : null}' > data/option_null.json
+
+echo '{"String" : [{"string" : "This is a test"}, {"string" : "This is a test"}]}' > data/option_array_string
+echo '{"Integer" : [{"int" : 1}, {"int" : 1}]}' > data/option_array_integer
+echo '{"Double" : [{"double" : 2.1}, {"double" : 2.1}]}' > data/option_array_double
+echo '{"Bool" : [{"bool" : true}, {"bool" : true}]}' > data/option_array_bool
diff --git a/aegisub/tests/util.cpp b/aegisub/tests/util.cpp
new file mode 100644
index 000000000..20a1285f8
--- /dev/null
+++ b/aegisub/tests/util.cpp
@@ -0,0 +1,39 @@
+// 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.
+//
+// $Id$
+
+/// @file util.cpp
+/// @brief Common utilities used in tests.
+/// @ingroup util
+
+#include <unistd.h>
+#include <fstream>
+
+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();
+}
+
+void remove(const std::string& file) {
+	unlink(file.c_str());
+}
+
+} // namespace util
+
+
diff --git a/aegisub/tests/util.h b/aegisub/tests/util.h
new file mode 100644
index 000000000..f57856edc
--- /dev/null
+++ b/aegisub/tests/util.h
@@ -0,0 +1,28 @@
+// 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.
+//
+// $Id$
+
+/// @file util.cpp
+/// @brief Common utilities used in tests.
+/// @ingroup util
+
+namespace util {
+
+void copy(const std::string from, const std::string to);
+void remove(const std::string& file);
+
+} // namespace util
+
+
diff --git a/aegisub/tinderbox/www/doxygen.sh b/aegisub/tinderbox/www/doxygen.sh
index 63bd5e167..7c173b394 100755
--- a/aegisub/tinderbox/www/doxygen.sh
+++ b/aegisub/tinderbox/www/doxygen.sh
@@ -18,6 +18,9 @@ case "$1" in
     "reporter")
 		OUTPUT="reporter"
     ;;
+    "libaegisub")
+		OUTPUT="libaegisub"
+    ;;
 esac
 
 cd docs/doxygen
diff --git a/aegisub/tools/common_respack_packresources.vsprops b/aegisub/tools/common_respack_packresources.vsprops
index 847119182..aa6b95dc5 100644
--- a/aegisub/tools/common_respack_packresources.vsprops
+++ b/aegisub/tools/common_respack_packresources.vsprops
@@ -6,6 +6,6 @@
 	>
 	<Tool
 		Name="VCPostBuildEventTool"
-		CommandLine="$(TargetPath) &quot;$(ProjectDir)\..\src\libresrc\bitmap.cpp&quot; &quot;$(ProjectDir)\..\src\bitmaps\16&quot; &quot;$(ProjectDir)\..\src\bitmaps\24&quot; &quot;$(ProjectDir)\..\src\bitmaps\misc&quot;&#x0D;&#x0A;$(TargetPath) &quot;$(ProjectDir)\..\src\libresrc\default_config.cpp&quot; &quot;$(ProjectDir)\..\src\libresrc\default_mru.json&quot;"
+		CommandLine="$(TargetPath) &quot;$(ProjectDir)\..\src\libresrc\bitmap.cpp&quot; &quot;$(ProjectDir)\..\src\bitmaps\16&quot; &quot;$(ProjectDir)\..\src\bitmaps\24&quot; &quot;$(ProjectDir)\..\src\bitmaps\misc&quot;&#x0D;&#x0A;$(TargetPath) &quot;$(ProjectDir)\..\src\libresrc\default_config.cpp&quot; &quot;$(ProjectDir)\..\src\libresrc\.\default_mru.json&quot; &quot;$(ProjectDir)\..\src\libresrc\.\default_config.json&quot;"
 	/>
 </VisualStudioPropertySheet>
-- 
GitLab