diff --git a/.github/workflows/stable-compilation.yml b/.github/workflows/stable-compilation.yml
index cb7393878..eb8293ef1 100644
--- a/.github/workflows/stable-compilation.yml
+++ b/.github/workflows/stable-compilation.yml
@@ -36,7 +36,7 @@ jobs:
           apt-get update
           apt-get install -y --no-install-recommends --no-install-suggests \
             ca-certificates build-essential cmake ninja-build git \
-            libicu-dev libexpat1-dev
+            libicu-dev libexpat1-dev libinih-dev
 
       - name: Clone Repository
         uses: actions/checkout@v4
diff --git a/.gitignore b/.gitignore
index a639c80da..fc831d1d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,11 @@ build/
 # IntelliJ / CLion
 .idea/
 
+# VS Code
+.vscode/
+.cache/
+compile_commands.json
+
 # doxygen generated files
 /doc/
 
@@ -72,3 +77,4 @@ test_runner*
 
 # distribution archives
 liblcf-*.tar.*
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 79df46b5e..60096a8d0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,7 +27,6 @@ set(LCF_SOURCES
 	src/dbarray.cpp
 	src/dbstring_struct.cpp
 	src/encoder.cpp
-	src/ini.cpp
 	src/inireader.cpp
 	src/ldb_equipment.cpp
 	src/ldb_eventcommand.cpp
@@ -201,7 +200,6 @@ set(LCF_HEADERS
 	src/lcf/encoder.h
 	src/lcf/enum_tags.h
 	src/lcf/flag_set.h
-	src/lcf/ini.h
 	src/lcf/inireader.h
 	src/lcf/ldb/reader.h
 	src/lcf/lmt/reader.h
@@ -343,6 +341,10 @@ set_property(TARGET lcf PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON)
 # Name of the exported library
 set_property(TARGET lcf PROPERTY EXPORT_NAME liblcf)
 
+# inih
+find_package(inih REQUIRED)
+target_link_libraries(lcf inih::inih)
+
 # icu
 set(LCF_SUPPORT_ICU 0)
 if(LIBLCF_WITH_ICU)
@@ -380,7 +382,6 @@ set_property(TARGET lcf PROPERTY SOVERSION 0)
 
 # installation
 if(LIBLCF_ENABLE_INSTALL)
-
 	# pkg-config file generation
 	set(LCF_LIBDIR ${CMAKE_INSTALL_LIBDIR})
 	if(IS_ABSOLUTE ${LCF_LIBDIR})
@@ -441,6 +442,7 @@ if(LIBLCF_ENABLE_INSTALL)
 	install(FILES
 		${CMAKE_CURRENT_BINARY_DIR}/liblcf-config.cmake
 		${CMAKE_CURRENT_BINARY_DIR}/liblcf-config-version.cmake
+		${CMAKE_CURRENT_SOURCE_DIR}/builds/cmake/Modules/Findinih.cmake
 		DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/liblcf
 	)
 
diff --git a/Makefile.am b/Makefile.am
index fe10c1e74..d7fc97df5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,9 +34,11 @@ liblcf_la_CXXFLAGS = \
 	-std=gnu++17 \
 	-fno-math-errno \
 	$(AM_CXXFLAGS) \
+	$(INIH_CFLAGS) \
 	$(EXPAT_CFLAGS) \
 	$(ICU_CFLAGS)
 liblcf_la_LIBADD = \
+	$(INIH_LIBS) \
 	$(EXPAT_LIBS) \
 	$(ICU_LIBS)
 liblcf_la_LDFLAGS = \
@@ -46,7 +48,6 @@ liblcf_la_SOURCES = \
 	src/dbarray.cpp \
 	src/dbstring_struct.cpp \
 	src/encoder.cpp \
-	src/ini.cpp \
 	src/inireader.cpp \
 	src/ldb_equipment.cpp \
 	src/ldb_eventcommand.cpp \
@@ -219,7 +220,6 @@ lcfinclude_HEADERS = \
 	src/lcf/encoder.h \
 	src/lcf/enum_tags.h \
 	src/lcf/flag_set.h \
-	src/lcf/ini.h \
 	src/lcf/inireader.h \
 	src/lcf/log_handler.h \
 	src/lcf/reader_lcf.h \
@@ -347,10 +347,12 @@ test_runner_CPPFLAGS = \
 test_runner_CXXFLAGS = \
 	-std=gnu++17 \
 	-DDOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING=1 \
+	$(INIH_CXXFLAGS) \
 	$(EXPAT_CXXFLAGS) \
 	$(ICU_CXXFLAGS)
 test_runner_LDADD = \
 	liblcf.la \
+	$(INIH_LIBS) \
 	$(EXPAT_LIBS) \
 	$(ICU_LIBS)
 test_runner_LDFLAGS = -no-install
diff --git a/README.md b/README.md
index 41ada9367..bc0359680 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org
 
 ## Requirements
 
+- [inih] for INI file reading. (required)
 - [Expat] for XML reading support.
 - [ICU] for character encoding detection and conversion (recommended).
 
diff --git a/builds/cmake/Modules/Findinih.cmake b/builds/cmake/Modules/Findinih.cmake
new file mode 100644
index 000000000..b919f9202
--- /dev/null
+++ b/builds/cmake/Modules/Findinih.cmake
@@ -0,0 +1,63 @@
+#.rst:
+# Findinih
+# --------
+#
+# Find the inih Library
+#
+# Imported Targets
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following :prop_tgt:`IMPORTED` targets:
+#
+# ``inih::inih``
+#   The ``inih`` library, if found.
+#
+# Result Variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module will set the following variables in your project:
+#
+# ``INIH_INCLUDE_DIRS``
+#   where to find inih headers.
+# ``INIH_LIBRARIES``
+#   the libraries to link against to use inih.
+# ``INIH_FOUND``
+#   true if the inih headers and libraries were found.
+
+find_package(PkgConfig QUIET)
+
+pkg_check_modules(PC_INIH QUIET libinih)
+
+# Look for the header file.
+find_path(INIH_INCLUDE_DIR
+	NAMES ini.h
+	HINTS ${PC_INIH_INCLUDE_DIRS})
+
+# Look for the library.
+# Allow INIH_LIBRARY to be set manually, as the location of the inih library
+if(NOT INIH_LIBRARY)
+	find_library(INIH_LIBRARY
+		NAMES libinih inih
+		HINTS ${PC_INIH_LIBRARY_DIRS})
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(inih
+	REQUIRED_VARS INIH_LIBRARY INIH_INCLUDE_DIR)
+
+if(INIH_FOUND)
+	set(INIH_INCLUDE_DIRS ${INIH_INCLUDE_DIR})
+
+	if(NOT INIH_LIBRARIES)
+		set(INIH_LIBRARIES ${INIH_LIBRARIES})
+	endif()
+
+	if(NOT TARGET inih::inih)
+		add_library(inih::inih UNKNOWN IMPORTED)
+		set_target_properties(inih::inih PROPERTIES
+			INTERFACE_INCLUDE_DIRECTORIES "${INIH_INCLUDE_DIRS}"
+			IMPORTED_LOCATION "${INIH_LIBRARY}")
+	endif()
+endif()
+
+mark_as_advanced(INIH_INCLUDE_DIR INIH_LIBRARY)
diff --git a/builds/cmake/liblcf-config.cmake.in b/builds/cmake/liblcf-config.cmake.in
index c892c0a56..b595bc1c1 100644
--- a/builds/cmake/liblcf-config.cmake.in
+++ b/builds/cmake/liblcf-config.cmake.in
@@ -2,6 +2,11 @@
 
 include(CMakeFindDependencyMacro)
 
+# Required to find our installed Findinih.cmake
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
+
+find_dependency(inih REQUIRED)
+
 if(@LCF_SUPPORT_ICU@)
 	find_dependency(ICU COMPONENTS i18n uc data REQUIRED)
 endif()
diff --git a/configure.ac b/configure.ac
index fae5ccb94..ed25ed4bc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,6 +21,8 @@ LT_INIT([win32-dll])
 AM_CONDITIONAL(CROSS_COMPILING,[test "x$cross_compiling" = "xyes"])
 
 # Checks for libraries.
+AX_PKG_CHECK_MODULES([INIH],[],[inih],[])
+
 AC_SUBST([LCF_SUPPORT_ICU],[0])
 AC_ARG_ENABLE([icu],[AS_HELP_STRING([--disable-icu],[Disable ICU encoding handling (only windows-1252 supported) [default=no]])])
 AS_IF([test "x$enable_icu" != "xno"],[
diff --git a/generator/csv/enums_easyrpg.csv b/generator/csv/enums_easyrpg.csv
index 67ae15a62..624e83719 100644
--- a/generator/csv/enums_easyrpg.csv
+++ b/generator/csv/enums_easyrpg.csv
@@ -1,4 +1,9 @@
 Structure,Entry,Value,Index
+EventCommand,Code,EasyRpg_TriggerEventAt,2002
+EventCommand,Code,EasyRpg_CallMovementAction,2050
+EventCommand,Code,EasyRpg_WaitForSingleMovement,2051
+EventCommand,Code,EasyRpg_AnimateVariable,2052
+EventCommand,Code,EasyRpg_SpawnMapEvent,2056
 EventCommand,Code,Maniac_GetSaveInfo,3001
 EventCommand,Code,Maniac_Save,3002
 EventCommand,Code,Maniac_Load,3003
diff --git a/generator/csv/fields_easyrpg.csv b/generator/csv/fields_easyrpg.csv
index 2b1b1f6ef..e943ffba3 100644
--- a/generator/csv/fields_easyrpg.csv
+++ b/generator/csv/fields_easyrpg.csv
@@ -8,6 +8,9 @@ SaveEventExecFrame,maniac_event_id,f,Int32,0x0F,0,0,0,Event ID
 SaveEventExecFrame,maniac_event_page_id,f,Int32,0x10,0,0,0,Page ID when it is a map event
 SaveEventExecFrame,maniac_loop_info_size,f,Int32,0x11,0,0,0,Amount of loop info groups
 SaveEventExecFrame,maniac_loop_info,f,Vector<Int32>,0x12,,0,0,"One group of (Current loop count, end loop value) for each identation"
+SaveEventExecState,easyrpg_active,f,Boolean,0xC9,False,0,0,When true state of an event is preserved in easyrpg_string and easyrpg_parameters
+SaveEventExecState,easyrpg_string,f,DBString,0xCA,,0,0,Preserved string data of an event
+SaveEventExecState,easyrpg_parameters,f,Vector<Int32>,0xCB,,0,0,Preserved int parameter of an event
 SavePicture,easyrpg_flip,f,Enum<EasyRpgFlip>,0xC8,0,0,1,How to flip the picture
 SavePicture,easyrpg_blend_mode,f,Int32,0xC9,0,0,1,Blend mode to use for blit. See Bitmap::BlendMode
 SavePicture,easyrpg_type,f,Enum<EasyRpgPictureType>,0xCA,0,0,1,Type of this picture
@@ -27,6 +30,7 @@ SaveEasyRpgText,font_size,f,Int32,0x05,12,0,0,Font size
 SaveEasyRpgText,letter_spacing,f,Int32,0x06,0,0,0,Additional spacing between letters
 SaveEasyRpgText,line_spacing,f,Int32,0x07,4,0,0,Additional spacing between lines
 SaveEasyRpgText,flags,f,SaveEasyRpgText_Flags,0x08,3,0,0,Various text settings
+SaveMapEventBase,easyrpg_move_failure_count,f,Int32,0xC9,0,0,0,Tracks how often the current move operation in a move route failed
 SaveSystem,maniac_strings,f,Vector<DBString>,0x24,,0,0,rpg::Strings
 SaveSystem,maniac_frameskip,,Int32,0x88,0,0,0,"FatalMix Frameskip (0=None, 1=1/5, 2=1/3, 3=1/2)"
 SaveSystem,maniac_picture_limit,,Int32,0x89,0,0,0,FatalMix Picture Limit
diff --git a/src/generated/lcf/lsd/chunks.h b/src/generated/lcf/lsd/chunks.h
index 443aa2c16..0e7c53efe 100644
--- a/src/generated/lcf/lsd/chunks.h
+++ b/src/generated/lcf/lsd/chunks.h
@@ -408,6 +408,8 @@ namespace LSD_Reader {
 			flash_current_level = 0x54,
 			/** int */
 			flash_time_left = 0x55,
+			/** Tracks how often the current move operation in a move route failed */
+			easyrpg_move_failure_count = 0xC9,
 			/**  */
 			boarding = 0x65,
 			/**  */
@@ -522,6 +524,8 @@ namespace LSD_Reader {
 			flash_current_level = 0x54,
 			/** int */
 			flash_time_left = 0x55,
+			/** Tracks how often the current move operation in a move route failed */
+			easyrpg_move_failure_count = 0xC9,
 			/** Which vehicle */
 			vehicle = 0x65,
 			/** From 0 to 255 - In flying vehicles; remaining distance to ascend */
@@ -733,7 +737,13 @@ namespace LSD_Reader {
 			/**  */
 			keyinput_timed = 0x29,
 			/** Used for a wait command WaitForKeyInput rm2k3 feature to wait for decision key press. */
-			wait_key_enter = 0x2A
+			wait_key_enter = 0x2A,
+			/** When true state of an event is preserved in easyrpg_string and easyrpg_parameters */
+			easyrpg_active = 0xC9,
+			/** Preserved string data of an event */
+			easyrpg_string = 0xCA,
+			/** Preserved int parameter of an event */
+			easyrpg_parameters = 0xCB
 		};
 	};
 	struct ChunkSaveMapEventBase {
@@ -815,7 +825,9 @@ namespace LSD_Reader {
 			/** double */
 			flash_current_level = 0x54,
 			/** int */
-			flash_time_left = 0x55
+			flash_time_left = 0x55,
+			/** Tracks how often the current move operation in a move route failed */
+			easyrpg_move_failure_count = 0xC9
 		};
 	};
 	struct ChunkSaveMapEvent {
@@ -898,6 +910,8 @@ namespace LSD_Reader {
 			flash_current_level = 0x54,
 			/** int */
 			flash_time_left = 0x55,
+			/** Tracks how often the current move operation in a move route failed */
+			easyrpg_move_failure_count = 0xC9,
 			/** If true; this event is waiting for foreground execution. */
 			waiting_execution = 0x65,
 			/** Index of custom move route */
diff --git a/src/generated/lcf/rpg/eventcommand.h b/src/generated/lcf/rpg/eventcommand.h
index f54c9f05f..8303d981e 100644
--- a/src/generated/lcf/rpg/eventcommand.h
+++ b/src/generated/lcf/rpg/eventcommand.h
@@ -157,6 +157,11 @@ namespace rpg {
 			Comment_2 = 22410,
 			ElseBranch_B = 23310,
 			EndBranch_B = 23311,
+			EasyRpg_TriggerEventAt = 2002,
+			EasyRpg_CallMovementAction = 2050,
+			EasyRpg_WaitForSingleMovement = 2051,
+			EasyRpg_AnimateVariable = 2052,
+			EasyRpg_SpawnMapEvent = 2056,
 			Maniac_GetSaveInfo = 3001,
 			Maniac_Save = 3002,
 			Maniac_Load = 3003,
@@ -313,6 +318,11 @@ namespace rpg {
 			Code::Comment_2, "Comment_2",
 			Code::ElseBranch_B, "ElseBranch_B",
 			Code::EndBranch_B, "EndBranch_B",
+			Code::EasyRpg_TriggerEventAt, "EasyRpg_TriggerEventAt",
+			Code::EasyRpg_CallMovementAction, "EasyRpg_CallMovementAction",
+			Code::EasyRpg_WaitForSingleMovement, "EasyRpg_WaitForSingleMovement",
+			Code::EasyRpg_AnimateVariable, "EasyRpg_AnimateVariable",
+			Code::EasyRpg_SpawnMapEvent, "EasyRpg_SpawnMapEvent",
 			Code::Maniac_GetSaveInfo, "Maniac_GetSaveInfo",
 			Code::Maniac_Save, "Maniac_Save",
 			Code::Maniac_Load, "Maniac_Load",
diff --git a/src/generated/lcf/rpg/saveeventexecstate.h b/src/generated/lcf/rpg/saveeventexecstate.h
index 607ef3f96..d49d0104e 100644
--- a/src/generated/lcf/rpg/saveeventexecstate.h
+++ b/src/generated/lcf/rpg/saveeventexecstate.h
@@ -15,6 +15,7 @@
 // Headers
 #include <stdint.h>
 #include <vector>
+#include "lcf/dbstring.h"
 #include "lcf/rpg/saveeventexecframe.h"
 #include "lcf/context.h"
 #include <ostream>
@@ -49,6 +50,9 @@ namespace rpg {
 		int32_t keyinput_2k3up = 0;
 		bool keyinput_timed = false;
 		bool wait_key_enter = false;
+		bool easyrpg_active = false;
+		DBString easyrpg_string;
+		std::vector<int32_t> easyrpg_parameters;
 	};
 
 	inline bool operator==(const SaveEventExecState& l, const SaveEventExecState& r) {
@@ -73,7 +77,10 @@ namespace rpg {
 		&& l.keyinput_2k3right == r.keyinput_2k3right
 		&& l.keyinput_2k3up == r.keyinput_2k3up
 		&& l.keyinput_timed == r.keyinput_timed
-		&& l.wait_key_enter == r.wait_key_enter;
+		&& l.wait_key_enter == r.wait_key_enter
+		&& l.easyrpg_active == r.easyrpg_active
+		&& l.easyrpg_string == r.easyrpg_string
+		&& l.easyrpg_parameters == r.easyrpg_parameters;
 	}
 
 	inline bool operator!=(const SaveEventExecState& l, const SaveEventExecState& r) {
@@ -88,6 +95,8 @@ namespace rpg {
 			const auto ctx1 = Context<SaveEventExecState, ParentCtx>{ "stack", i, &obj, parent_ctx };
 			ForEachString(obj.stack[i], f, &ctx1);
 		}
+		const auto ctx24 = Context<SaveEventExecState, ParentCtx>{ "easyrpg_string", -1, &obj, parent_ctx };
+		f(obj.easyrpg_string, ctx24);
 		(void)obj;
 		(void)f;
 		(void)parent_ctx;
diff --git a/src/generated/lcf/rpg/savemapeventbase.h b/src/generated/lcf/rpg/savemapeventbase.h
index 0c773eb21..6bff9dcea 100644
--- a/src/generated/lcf/rpg/savemapeventbase.h
+++ b/src/generated/lcf/rpg/savemapeventbase.h
@@ -67,6 +67,7 @@ namespace rpg {
 		int32_t flash_blue = -1;
 		double flash_current_level = 0.0;
 		int32_t flash_time_left = 0;
+		int32_t easyrpg_move_failure_count = 0;
 	};
 
 	inline bool operator==(const SaveMapEventBase& l, const SaveMapEventBase& r) {
@@ -108,7 +109,8 @@ namespace rpg {
 		&& l.flash_green == r.flash_green
 		&& l.flash_blue == r.flash_blue
 		&& l.flash_current_level == r.flash_current_level
-		&& l.flash_time_left == r.flash_time_left;
+		&& l.flash_time_left == r.flash_time_left
+		&& l.easyrpg_move_failure_count == r.easyrpg_move_failure_count;
 	}
 
 	inline bool operator!=(const SaveMapEventBase& l, const SaveMapEventBase& r) {
diff --git a/src/generated/lsd_saveeventexecstate.cpp b/src/generated/lsd_saveeventexecstate.cpp
index 9d5429824..e8f684698 100644
--- a/src/generated/lsd_saveeventexecstate.cpp
+++ b/src/generated/lsd_saveeventexecstate.cpp
@@ -174,6 +174,27 @@ static TypedField<rpg::SaveEventExecState, bool> static_wait_key_enter(
 	0,
 	0
 );
+static TypedField<rpg::SaveEventExecState, bool> static_easyrpg_active(
+	&rpg::SaveEventExecState::easyrpg_active,
+	LSD_Reader::ChunkSaveEventExecState::easyrpg_active,
+	"easyrpg_active",
+	0,
+	0
+);
+static TypedField<rpg::SaveEventExecState, DBString> static_easyrpg_string(
+	&rpg::SaveEventExecState::easyrpg_string,
+	LSD_Reader::ChunkSaveEventExecState::easyrpg_string,
+	"easyrpg_string",
+	0,
+	0
+);
+static TypedField<rpg::SaveEventExecState, std::vector<int32_t>> static_easyrpg_parameters(
+	&rpg::SaveEventExecState::easyrpg_parameters,
+	LSD_Reader::ChunkSaveEventExecState::easyrpg_parameters,
+	"easyrpg_parameters",
+	0,
+	0
+);
 
 
 template <>
@@ -200,6 +221,9 @@ Field<rpg::SaveEventExecState> const* Struct<rpg::SaveEventExecState>::fields[]
 	&static_keyinput_2k3up,
 	&static_keyinput_timed,
 	&static_wait_key_enter,
+	&static_easyrpg_active,
+	&static_easyrpg_string,
+	&static_easyrpg_parameters,
 	NULL
 };
 
diff --git a/src/generated/lsd_savemapevent.cpp b/src/generated/lsd_savemapevent.cpp
index 541afa1ec..bf14bf839 100644
--- a/src/generated/lsd_savemapevent.cpp
+++ b/src/generated/lsd_savemapevent.cpp
@@ -293,6 +293,13 @@ static TypedField<rpg::SaveMapEvent, int32_t> static_flash_time_left(
 	0,
 	0
 );
+static TypedField<rpg::SaveMapEvent, int32_t> static_easyrpg_move_failure_count(
+	&rpg::SaveMapEvent::easyrpg_move_failure_count,
+	LSD_Reader::ChunkSaveMapEvent::easyrpg_move_failure_count,
+	"easyrpg_move_failure_count",
+	0,
+	0
+);
 static TypedField<rpg::SaveMapEvent, bool> static_waiting_execution(
 	&rpg::SaveMapEvent::waiting_execution,
 	LSD_Reader::ChunkSaveMapEvent::waiting_execution,
@@ -364,6 +371,7 @@ Field<rpg::SaveMapEvent> const* Struct<rpg::SaveMapEvent>::fields[] = {
 	&static_flash_blue,
 	&static_flash_current_level,
 	&static_flash_time_left,
+	&static_easyrpg_move_failure_count,
 	&static_waiting_execution,
 	&static_original_move_route_index,
 	&static_triggered_by_decision_key,
diff --git a/src/generated/lsd_savemapeventbase.cpp b/src/generated/lsd_savemapeventbase.cpp
index 1796177be..db8e60804 100644
--- a/src/generated/lsd_savemapeventbase.cpp
+++ b/src/generated/lsd_savemapeventbase.cpp
@@ -293,6 +293,13 @@ static TypedField<rpg::SaveMapEventBase, int32_t> static_flash_time_left(
 	0,
 	0
 );
+static TypedField<rpg::SaveMapEventBase, int32_t> static_easyrpg_move_failure_count(
+	&rpg::SaveMapEventBase::easyrpg_move_failure_count,
+	LSD_Reader::ChunkSaveMapEventBase::easyrpg_move_failure_count,
+	"easyrpg_move_failure_count",
+	0,
+	0
+);
 
 
 template <>
@@ -336,6 +343,7 @@ Field<rpg::SaveMapEventBase> const* Struct<rpg::SaveMapEventBase>::fields[] = {
 	&static_flash_blue,
 	&static_flash_current_level,
 	&static_flash_time_left,
+	&static_easyrpg_move_failure_count,
 	NULL
 };
 
diff --git a/src/generated/lsd_savepartylocation.cpp b/src/generated/lsd_savepartylocation.cpp
index fd8d572b9..b2af54105 100644
--- a/src/generated/lsd_savepartylocation.cpp
+++ b/src/generated/lsd_savepartylocation.cpp
@@ -293,6 +293,13 @@ static TypedField<rpg::SavePartyLocation, int32_t> static_flash_time_left(
 	0,
 	0
 );
+static TypedField<rpg::SavePartyLocation, int32_t> static_easyrpg_move_failure_count(
+	&rpg::SavePartyLocation::easyrpg_move_failure_count,
+	LSD_Reader::ChunkSavePartyLocation::easyrpg_move_failure_count,
+	"easyrpg_move_failure_count",
+	0,
+	0
+);
 static TypedField<rpg::SavePartyLocation, bool> static_boarding(
 	&rpg::SavePartyLocation::boarding,
 	LSD_Reader::ChunkSavePartyLocation::boarding,
@@ -448,6 +455,7 @@ Field<rpg::SavePartyLocation> const* Struct<rpg::SavePartyLocation>::fields[] =
 	&static_flash_blue,
 	&static_flash_current_level,
 	&static_flash_time_left,
+	&static_easyrpg_move_failure_count,
 	&static_boarding,
 	&static_aboard,
 	&static_vehicle,
diff --git a/src/generated/lsd_savevehiclelocation.cpp b/src/generated/lsd_savevehiclelocation.cpp
index 88d19274d..9868b82ed 100644
--- a/src/generated/lsd_savevehiclelocation.cpp
+++ b/src/generated/lsd_savevehiclelocation.cpp
@@ -293,6 +293,13 @@ static TypedField<rpg::SaveVehicleLocation, int32_t> static_flash_time_left(
 	0,
 	0
 );
+static TypedField<rpg::SaveVehicleLocation, int32_t> static_easyrpg_move_failure_count(
+	&rpg::SaveVehicleLocation::easyrpg_move_failure_count,
+	LSD_Reader::ChunkSaveVehicleLocation::easyrpg_move_failure_count,
+	"easyrpg_move_failure_count",
+	0,
+	0
+);
 static TypedField<rpg::SaveVehicleLocation, int32_t> static_vehicle(
 	&rpg::SaveVehicleLocation::vehicle,
 	LSD_Reader::ChunkSaveVehicleLocation::vehicle,
@@ -371,6 +378,7 @@ Field<rpg::SaveVehicleLocation> const* Struct<rpg::SaveVehicleLocation>::fields[
 	&static_flash_blue,
 	&static_flash_current_level,
 	&static_flash_time_left,
+	&static_easyrpg_move_failure_count,
 	&static_vehicle,
 	&static_remaining_ascent,
 	&static_remaining_descent,
diff --git a/src/generated/rpg_saveeventexecstate.cpp b/src/generated/rpg_saveeventexecstate.cpp
index b81be6246..4c58ae4bf 100644
--- a/src/generated/rpg_saveeventexecstate.cpp
+++ b/src/generated/rpg_saveeventexecstate.cpp
@@ -43,6 +43,13 @@ std::ostream& operator<<(std::ostream& os, const SaveEventExecState& obj) {
 	os << ", keyinput_2k3up="<< obj.keyinput_2k3up;
 	os << ", keyinput_timed="<< obj.keyinput_timed;
 	os << ", wait_key_enter="<< obj.wait_key_enter;
+	os << ", easyrpg_active="<< obj.easyrpg_active;
+	os << ", easyrpg_string="<< obj.easyrpg_string;
+	os << ", easyrpg_parameters=";
+	for (size_t i = 0; i < obj.easyrpg_parameters.size(); ++i) {
+		os << (i == 0 ? "[" : ", ") << obj.easyrpg_parameters[i];
+	}
+	os << "]";
 	os << "}";
 	return os;
 }
diff --git a/src/generated/rpg_savemapeventbase.cpp b/src/generated/rpg_savemapeventbase.cpp
index 3906e2c1f..03b765547 100644
--- a/src/generated/rpg_savemapeventbase.cpp
+++ b/src/generated/rpg_savemapeventbase.cpp
@@ -56,6 +56,7 @@ std::ostream& operator<<(std::ostream& os, const SaveMapEventBase& obj) {
 	os << ", flash_blue="<< obj.flash_blue;
 	os << ", flash_current_level="<< obj.flash_current_level;
 	os << ", flash_time_left="<< obj.flash_time_left;
+	os << ", easyrpg_move_failure_count="<< obj.easyrpg_move_failure_count;
 	os << "}";
 	return os;
 }
diff --git a/src/ini.cpp b/src/ini.cpp
deleted file mode 100644
index 68caa3eab..000000000
--- a/src/ini.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-/* inih -- simple .INI file parser
-
-The "inih" library is distributed under the New BSD license:
-
-Copyright (c) 2009, Ben Hoyt
-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 Ben Hoyt 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 BEN HOYT ''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 BEN HOYT 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.
-
-Go to the project home page for more info: https://github.com/benhoyt/inih
-
-*/
-
-#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#include <cstdio>
-#include <cctype>
-#include <cstring>
-
-#include "lcf/ini.h"
-
-#if !INI_USE_STACK
-#include <cstdlib>
-#endif
-
-#define MAX_SECTION 50
-#define MAX_NAME 50
-
-/* Used by ini_parse_string() to keep track of string parsing state. */
-typedef struct {
-	const char* ptr;
-	size_t num_left;
-} ini_parse_string_ctx;
-
-/* Strip whitespace chars off end of given string, in place. Return s. */
-static char* rstrip(char* s)
-{
-	char* p = s + strlen(s);
-	while (p > s && isspace((unsigned char)(*--p)))
-		*p = '\0';
-	return s;
-}
-
-/* Return pointer to first non-whitespace char in given string. */
-static char* lskip(const char* s)
-{
-	while (*s && isspace((unsigned char)(*s)))
-		s++;
-	return (char*)s;
-}
-
-/* Return pointer to first char (of chars) or inline comment in given string,
-   or pointer to null at end of string if neither found. Inline comment must
-   be prefixed by a whitespace character to register as a comment. */
-static char* find_chars_or_comment(const char* s, const char* chars)
-{
-#if INI_ALLOW_INLINE_COMMENTS
-	int was_space = 0;
-	while (*s && (!chars || !strchr(chars, *s)) &&
-	       !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
-		was_space = isspace((unsigned char)(*s));
-		s++;
-	}
-#else
-	while (*s && (!chars || !strchr(chars, *s))) {
-		s++;
-	}
-#endif
-	return (char*)s;
-}
-
-/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
-static char* strncpy0(char* dest, const char* src, size_t size)
-{
-	strncpy(dest, src, size - 1);
-	dest[size - 1] = '\0';
-	return dest;
-}
-
-/* See documentation in header file. */
-int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
-					 void* user)
-{
-	/* Uses a fair bit of stack (use heap instead if you need to) */
-#if INI_USE_STACK
-	char line[INI_MAX_LINE];
-	int max_line = INI_MAX_LINE;
-#else
-	char* line;
-	int max_line = INI_INITIAL_ALLOC;
-#endif
-#if INI_ALLOW_REALLOC && !INI_USE_STACK
-	char* new_line;
-	int offset;
-#endif
-	char section[MAX_SECTION] = "";
-	char prev_name[MAX_NAME] = "";
-
-	char* start;
-	char* end;
-	char* name;
-	char* value;
-	int lineno = 0;
-	int error = 0;
-
-#if !INI_USE_STACK
-	line = (char*)malloc(INI_INITIAL_ALLOC);
-	if (!line) {
-		return -2;
-	}
-#endif
-
-#if INI_HANDLER_LINENO
-#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
-#else
-#define HANDLER(u, s, n, v) handler(u, s, n, v)
-#endif
-
-	/* Scan through stream line by line */
-	while (reader(line, max_line, stream) != NULL) {
-#if INI_ALLOW_REALLOC && !INI_USE_STACK
-		offset = strlen(line);
-		while (offset == max_line - 1 && line[offset - 1] != '\n') {
-			max_line *= 2;
-			if (max_line > INI_MAX_LINE)
-				max_line = INI_MAX_LINE;
-			new_line = realloc(line, max_line);
-			if (!new_line) {
-				free(line);
-				return -2;
-			}
-			line = new_line;
-			if (reader(line + offset, max_line - offset, stream) == NULL)
-				break;
-			if (max_line >= INI_MAX_LINE)
-				break;
-			offset += strlen(line + offset);
-		}
-#endif
-
-		lineno++;
-
-		start = line;
-#if INI_ALLOW_BOM
-		if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
-		    (unsigned char)start[1] == 0xBB &&
-		    (unsigned char)start[2] == 0xBF) {
-			start += 3;
-		}
-#endif
-		start = lskip(rstrip(start));
-
-		if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
-			/* Start-of-line comment */
-		}
-#if INI_ALLOW_MULTILINE
-		else if (*prev_name && *start && start > line) {
-			/* Non-blank line with leading whitespace, treat as continuation
-			   of previous name's value (as per Python configparser). */
-			if (!HANDLER(user, section, prev_name, start) && !error)
-				error = lineno;
-		}
-#endif
-		else if (*start == '[') {
-			/* A "[section]" line */
-			end = find_chars_or_comment(start + 1, "]");
-			if (*end == ']') {
-				*end = '\0';
-				strncpy0(section, start + 1, sizeof(section));
-				*prev_name = '\0';
-			}
-			else if (!error) {
-				/* No ']' found on section line */
-				error = lineno;
-			}
-		}
-		else if (*start) {
-			/* Not a comment, must be a name[=:]value pair */
-			end = find_chars_or_comment(start, "=:");
-			if (*end == '=' || *end == ':') {
-				*end = '\0';
-				name = rstrip(start);
-				value = end + 1;
-#if INI_ALLOW_INLINE_COMMENTS
-				end = find_chars_or_comment(value, NULL);
-				if (*end)
-					*end = '\0';
-#endif
-				value = lskip(value);
-				rstrip(value);
-
-				/* Valid name[=:]value pair found, call handler */
-				strncpy0(prev_name, name, sizeof(prev_name));
-				if (!HANDLER(user, section, name, value) && !error)
-					error = lineno;
-			}
-			else if (!error) {
-				/* No '=' or ':' found on name[=:]value line */
-				error = lineno;
-			}
-		}
-
-#if INI_STOP_ON_FIRST_ERROR
-		if (error)
-			break;
-#endif
-	}
-
-#if !INI_USE_STACK
-	free(line);
-#endif
-
-	return error;
-}
-
-/* See documentation in header file. */
-int ini_parse_file(FILE* file, ini_handler handler, void* user)
-{
-	return ini_parse_stream((ini_reader)fgets, file, handler, user);
-}
-
-/* See documentation in header file. */
-int ini_parse(const char* filename, ini_handler handler, void* user)
-{
-	FILE* file;
-	int error;
-
-	file = fopen(filename, "r");
-	if (!file)
-		return -1;
-	error = ini_parse_file(file, handler, user);
-	fclose(file);
-	return error;
-}
-
-/* An ini_reader function to read the next line from a string buffer. This
-   is the fgets() equivalent used by ini_parse_string(). */
-static char* ini_reader_string(char* str, int num, void* stream) {
-	ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
-	const char* ctx_ptr = ctx->ptr;
-	size_t ctx_num_left = ctx->num_left;
-	char* strp = str;
-	char c;
-
-	if (ctx_num_left == 0 || num < 2)
-		return NULL;
-
-	while (num > 1 && ctx_num_left != 0) {
-		c = *ctx_ptr++;
-		ctx_num_left--;
-		*strp++ = c;
-		if (c == '\n')
-			break;
-		num--;
-	}
-
-	*strp = '\0';
-	ctx->ptr = ctx_ptr;
-	ctx->num_left = ctx_num_left;
-	return str;
-}
-
-/* See documentation in header file. */
-int ini_parse_string(const char* string, ini_handler handler, void* user) {
-	ini_parse_string_ctx ctx;
-
-	ctx.ptr = string;
-	ctx.num_left = strlen(string);
-	return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
-	                        user);
-}
diff --git a/src/inireader.cpp b/src/inireader.cpp
index 57f2fbb99..6222ae29d 100644
--- a/src/inireader.cpp
+++ b/src/inireader.cpp
@@ -34,7 +34,7 @@
 #include <cstdlib>
 #include <cstring>
 #include <istream>
-#include "lcf/ini.h"
+#include <ini.h>
 #include "lcf/inireader.h"
 
 namespace lcf {
diff --git a/src/lcf/ini.h b/src/lcf/ini.h
deleted file mode 100644
index 32d50449b..000000000
--- a/src/lcf/ini.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/* inih -- simple .INI file parser
-
-The "inih" library is distributed under the New BSD license:
-
-Copyright (c) 2009, Ben Hoyt
-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 Ben Hoyt 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 BEN HOYT ''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 BEN HOYT 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.
-
-Go to the project home page for more info: https://github.com/benhoyt/inih
-
-*/
-
-#ifndef LCF_INI_H
-#define LCF_INI_H
-
-/* Make this header file easier to include in C++ code */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdio.h>
-
-/* Nonzero if ini_handler callback should accept lineno parameter. */
-#ifndef INI_HANDLER_LINENO
-#define INI_HANDLER_LINENO 0
-#endif
-
-/* Typedef for prototype of handler function. */
-#if INI_HANDLER_LINENO
-typedef int (*ini_handler)(void* user, const char* section,
-                           const char* name, const char* value,
-                           int lineno);
-#else
-typedef int (*ini_handler)(void* user, const char* section,
-                           const char* name, const char* value);
-#endif
-
-/* Typedef for prototype of fgets-style reader function. */
-typedef char* (*ini_reader)(char* str, int num, void* stream);
-
-/* Parse given INI-style file. May have [section]s, name=value pairs
-   (whitespace stripped), and comments starting with ';' (semicolon). Section
-   is "" if name=value pair parsed before any section heading. name:value
-   pairs are also supported as a concession to Python's configparser.
-
-   For each name=value pair parsed, call handler function with given user
-   pointer as well as section, name, and value (data only valid for duration
-   of handler call). Handler should return nonzero on success, zero on error.
-
-   Returns 0 on success, line number of first error on parse error (doesn't
-   stop on first error), -1 on file open error, or -2 on memory allocation
-   error (only when INI_USE_STACK is zero).
-*/
-int ini_parse(const char* filename, ini_handler handler, void* user);
-
-/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
-   close the file when it's finished -- the caller must do that. */
-int ini_parse_file(FILE* file, ini_handler handler, void* user);
-
-/* Same as ini_parse(), but takes an ini_reader function pointer instead of
-   filename. Used for implementing custom or string-based I/O (see also
-   ini_parse_string). */
-int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
-                     void* user);
-
-/* Same as ini_parse(), but takes a zero-terminated string with the INI data
-   instead of a file. Useful for parsing INI data from a network socket or
-   already in memory. */
-int ini_parse_string(const char* string, ini_handler handler, void* user);
-
-/* Nonzero to allow multi-line value parsing, in the style of Python's
-   configparser. If allowed, ini_parse() will call the handler with the same
-   name for each subsequent line parsed. */
-#ifndef INI_ALLOW_MULTILINE
-#define INI_ALLOW_MULTILINE 1
-#endif
-
-/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
-   the file. See https://github.com/benhoyt/inih/issues/21 */
-#ifndef INI_ALLOW_BOM
-#define INI_ALLOW_BOM 1
-#endif
-
-/* Chars that begin a start-of-line comment. Per Python configparser, allow
-   both ; and # comments at the start of a line by default. */
-#ifndef INI_START_COMMENT_PREFIXES
-#define INI_START_COMMENT_PREFIXES ";#"
-#endif
-
-/* Nonzero to allow inline comments (with valid inline comment characters
-   specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
-   Python 3.2+ configparser behaviour. */
-#ifndef INI_ALLOW_INLINE_COMMENTS
-#define INI_ALLOW_INLINE_COMMENTS 1
-#endif
-#ifndef INI_INLINE_COMMENT_PREFIXES
-#define INI_INLINE_COMMENT_PREFIXES ";"
-#endif
-
-/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
-#ifndef INI_USE_STACK
-#define INI_USE_STACK 1
-#endif
-
-/* Maximum line length for any line in INI file (stack or heap). Note that
-   this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
-#ifndef INI_MAX_LINE
-#define INI_MAX_LINE 200
-#endif
-
-/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
-   fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
-   zero. */
-#ifndef INI_ALLOW_REALLOC
-#define INI_ALLOW_REALLOC 0
-#endif
-
-/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
-   is zero. */
-#ifndef INI_INITIAL_ALLOC
-#define INI_INITIAL_ALLOC 200
-#endif
-
-/* Stop parsing on first error (default is to keep parsing). */
-#ifndef INI_STOP_ON_FIRST_ERROR
-#define INI_STOP_ON_FIRST_ERROR 0
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif