diff --git a/aegisub/src/auto4_lua.h b/aegisub/src/auto4_lua.h
index fe0fdbb85855c08cf48103a1421d21790c14fd29..ec87b420c9276d620fffa9a9dfd48fd388d5ab41 100644
--- a/aegisub/src/auto4_lua.h
+++ b/aegisub/src/auto4_lua.h
@@ -73,19 +73,34 @@ namespace Automation4 {
 		void CheckBounds(int idx);
 
 		/// How ass file been modified by the script since the last commit
-		int modification_type;
+		int modification_type = 0;
 
 		/// Reference count used to avoid deleting this until both lua and the
 		/// calling C++ code are done with it
-		int references;
+		int references = 2;
 
 		/// Set of subtitle lines being modified; initially a shallow copy of ass->Line
 		std::vector<AssEntry*> lines;
+		bool script_info_copied = false;
+
 		/// Commits to apply once processing completes successfully
 		std::deque<PendingCommit> pending_commits;
 		/// Lines to delete once processing complete successfully
 		std::vector<std::unique_ptr<AssEntry>> lines_to_delete;
 
+		/// Create copies of all of the lines in the script info section if it
+		/// hasn't already happened. This is done lazily, since it only needs
+		/// to happen when the user modifies the headers in some way, which
+		/// most runs of a script will not do.
+		void InitScriptInfoIfNeeded();
+		/// Add the line at the given index to the list of lines to be deleted
+		/// when the script completes, unless it's an AssInfo, since those are
+		/// owned by the container.
+		void QueueLineForDeletion(size_t idx);
+		/// Set the line at the index to the given value
+		void AssignLine(size_t idx, std::unique_ptr<AssEntry> e);
+		void InsertLine(std::vector<AssEntry *> &vec, size_t idx, std::unique_ptr<AssEntry> e);
+
 		int ObjectIndexRead(lua_State *L);
 		void ObjectIndexWrite(lua_State *L);
 		int ObjectGetLen(lua_State *L);
@@ -107,7 +122,7 @@ namespace Automation4 {
 		static LuaAssFile *GetObjPointer(lua_State *L, int idx);
 
 		/// makes a Lua representation of AssEntry and places on the top of the stack
-		static void AssEntryToLua(lua_State *L, AssEntry *e);
+		void AssEntryToLua(lua_State *L, size_t idx);
 		/// assumes a Lua representation of AssEntry on the top of the stack, and creates an AssEntry object of it
 		static std::unique_ptr<AssEntry> LuaToAssEntry(lua_State *L);
 
diff --git a/aegisub/src/auto4_lua_assfile.cpp b/aegisub/src/auto4_lua_assfile.cpp
index 3747b2f47b0bbfb8e73b8515b904dd89c3eb22f2..ec83325870f7d7d2401851b80e95e6ae1b7e8697 100644
--- a/aegisub/src/auto4_lua_assfile.cpp
+++ b/aegisub/src/auto4_lua_assfile.cpp
@@ -37,7 +37,6 @@
 #include "auto4_lua.h"
 
 #include "auto4_lua_utils.h"
-#include "ass_attachment.h"
 #include "ass_dialogue.h"
 #include "ass_info.h"
 #include "ass_file.h"
@@ -50,8 +49,6 @@
 
 #include <algorithm>
 #include <boost/algorithm/string/case_conv.hpp>
-#include <boost/range/adaptor/indirected.hpp>
-#include <boost/range/algorithm_ext.hpp>
 #include <cassert>
 #include <memory>
 
@@ -118,8 +115,8 @@ namespace {
 
 	int modification_mask(AssEntry *e)
 	{
-		switch (e->Group())
-		{
+		if (!e) return AssFile::COMMIT_SCRIPTINFO;
+		switch (e->Group()) {
 			case AssEntryGroup::DIALOGUE: return AssFile::COMMIT_DIAG_ADDREM;
 			case AssEntryGroup::STYLE:    return AssFile::COMMIT_STYLES;
 			default:                      return AssFile::COMMIT_SCRIPTINFO;
@@ -142,19 +139,23 @@ namespace Automation4 {
 			luaL_error(L, "Requested out-of-range line from subtitle file: %d", idx);
 	}
 
-	void LuaAssFile::AssEntryToLua(lua_State *L, AssEntry *e)
+	void LuaAssFile::AssEntryToLua(lua_State *L, size_t idx)
 	{
 		lua_newtable(L);
 
+		const AssEntry *e = lines[idx];
+		if (!e)
+			e = &ass->Info[idx];
+
 		set_field(L, "section", e->GroupHeader());
 
-		if (AssInfo *info = dynamic_cast<AssInfo*>(e)) {
+		if (auto info = dynamic_cast<const AssInfo*>(e)) {
 			set_field(L, "raw", info->GetEntryData());
 			set_field(L, "key", info->Key());
 			set_field(L, "value", info->Value());
 			set_field(L, "class", "info");
 		}
-		else if (AssDialogue *dia = dynamic_cast<AssDialogue*>(e)) {
+		else if (auto dia = dynamic_cast<const AssDialogue*>(e)) {
 			set_field(L, "raw", dia->GetEntryData());
 			set_field(L, "comment", dia->Comment);
 
@@ -176,7 +177,7 @@ namespace Automation4 {
 
 			set_field(L, "class", "dialogue");
 		}
-		else if (AssStyle *sty = dynamic_cast<AssStyle*>(e)) {
+		else if (auto sty = dynamic_cast<const AssStyle*>(e)) {
 			set_field(L, "raw", sty->GetEntryData());
 			set_field(L, "name", sty->name);
 
@@ -308,7 +309,7 @@ namespace Automation4 {
 				// read an indexed AssEntry
 				int idx = lua_tointeger(L, 2);
 				CheckBounds(idx);
-				AssEntryToLua(L, lines[idx - 1]);
+				AssEntryToLua(L, idx - 1);
 				return 1;
 			}
 
@@ -350,6 +351,52 @@ namespace Automation4 {
 		return 0;
 	}
 
+	void LuaAssFile::InitScriptInfoIfNeeded()
+	{
+		if (script_info_copied) return;
+		size_t i = 0;
+		for (auto const& info : ass->Info) {
+			// Just in case an insane user inserted non-info lines into the
+			// script info section...
+			while (lines[i]) ++i;
+			lines_to_delete.emplace_back(agi::util::make_unique<AssInfo>(info));
+			lines[i++] = lines_to_delete.back().get();
+		}
+		script_info_copied = true;
+	}
+
+	void LuaAssFile::QueueLineForDeletion(size_t idx)
+	{
+		if (!lines[idx] || lines[idx]->Group() == AssEntryGroup::INFO)
+			InitScriptInfoIfNeeded();
+		else
+			lines_to_delete.emplace_back(lines[idx]);
+	}
+
+	void LuaAssFile::AssignLine(size_t idx, std::unique_ptr<AssEntry> e)
+	{
+		auto group = e->Group();
+		if (group == AssEntryGroup::INFO)
+			InitScriptInfoIfNeeded();
+		lines[idx] = e.get();
+		if (group == AssEntryGroup::INFO)
+			lines_to_delete.emplace_back(std::move(e));
+		else
+			e.release();
+	}
+
+	void LuaAssFile::InsertLine(std::vector<AssEntry *> &vec, size_t idx, std::unique_ptr<AssEntry> e)
+	{
+		auto group = e->Group();
+		if (group == AssEntryGroup::INFO)
+			InitScriptInfoIfNeeded();
+		vec.insert(vec.begin() + idx, e.get());
+		if (group == AssEntryGroup::INFO)
+			lines_to_delete.emplace_back(std::move(e));
+		else
+			e.release();
+	}
+
 	void LuaAssFile::ObjectIndexWrite(lua_State *L)
 	{
 		// instead of implementing everything twice, just call the other modification-functions from here
@@ -375,11 +422,12 @@ namespace Automation4 {
 			// replace line at index n or delete
 			if (!lua_isnil(L, 3)) {
 				// insert
+				CheckBounds(n);
+
 				auto e = LuaToAssEntry(L);
 				modification_type |= modification_mask(e.get());
-				CheckBounds(n);
-				lines_to_delete.emplace_back(lines[n - 1]);
-				lines[n - 1] = e.release();
+				QueueLineForDeletion(n - 1);
+				AssignLine(n - 1, std::move(e));
 			}
 			else {
 				// delete
@@ -418,7 +466,7 @@ namespace Automation4 {
 		for (size_t i = 0; i < lines.size(); ++i) {
 			if (id_idx < ids.size() && ids[id_idx] == i) {
 				modification_type |= modification_mask(lines[i]);
-				lines_to_delete.emplace_back(lines[i]);
+				QueueLineForDeletion(i);
 				++id_idx;
 			}
 			else {
@@ -440,7 +488,7 @@ namespace Automation4 {
 
 		for (size_t i = a; i < b; ++i) {
 			modification_type |= modification_mask(lines[i]);
-			lines_to_delete.emplace_back(lines[i]);
+			QueueLineForDeletion(i);
 		}
 
 		lines.erase(lines.begin() + a, lines.begin() + b);
@@ -457,24 +505,23 @@ namespace Automation4 {
 			auto e = LuaToAssEntry(L);
 			modification_type |= modification_mask(e.get());
 
+			if (lines.empty()) {
+				InsertLine(lines, 0, std::move(e));
+				continue;
+			}
+
 			// Find the appropriate place to put it
-			auto it = lines.end();
-			if (!lines.empty()) {
-				do {
-					--it;
+			auto group = e->Group();
+			for (size_t i = lines.size(); i > 0; --i) {
+				auto cur_group = lines[i - 1] ? lines[i - 1]->Group() : AssEntryGroup::INFO;
+				if (cur_group == group) {
+					InsertLine(lines, i, std::move(e));
+					break;
 				}
-				while (it != lines.begin() && (*it)->Group() != e->Group());
 			}
 
-			if (it == lines.end() || (*it)->Group() != e->Group()) {
-				// The new entry belongs to a group that doesn't exist yet, so
-				// create it at the end of the file
-				lines.push_back(e.release());
-			}
-			else {
-				// Append the entry to the end of the existing group
-				lines.insert(++it, e.release());
-			}
+			// No lines of this type exist already, so just append it to the end
+			if (e) InsertLine(lines, lines.size(), std::move(e));
 		}
 	}
 
@@ -500,7 +547,7 @@ namespace Automation4 {
 			lua_pushvalue(L, i);
 			auto e = LuaToAssEntry(L);
 			modification_type |= modification_mask(e.get());
-			new_entries[i - 2] = e.release();
+			InsertLine(new_entries, i - 2, std::move(e));
 			lua_pop(L, 1);
 		}
 		lines.insert(lines.begin() + before - 1, new_entries.begin(), new_entries.end());
@@ -531,7 +578,7 @@ namespace Automation4 {
 		}
 
 		push_value(L, i + 1);
-		AssEntryToLua(L, lines[i]);
+		AssEntryToLua(L, i);
 		return 2;
 	}
 
@@ -596,11 +643,13 @@ namespace Automation4 {
 	std::vector<AssEntry *> LuaAssFile::ProcessingComplete(wxString const& undo_description)
 	{
 		auto apply_lines = [&](std::vector<AssEntry *> const& lines) {
-			ass->Info.clear();
+			if (script_info_copied)
+				ass->Info.clear();
 			ass->Styles.clear();
 			ass->Events.clear();
 
 			for (auto line : lines) {
+				if (!line) continue;
 				switch (line->Group()) {
 					case AssEntryGroup::INFO:     ass->Info.push_back(*static_cast<AssInfo *>(line)); break;
 					case AssEntryGroup::STYLE:    ass->Styles.push_back(*static_cast<AssStyle *>(line)); break;
@@ -641,11 +690,9 @@ namespace Automation4 {
 	, L(L)
 	, can_modify(can_modify)
 	, can_set_undo(can_set_undo)
-	, modification_type(0)
-	, references(2)
 	{
 		for (auto& line : ass->Info)
-			lines.push_back(&line);
+			lines.push_back(nullptr);
 		for (auto& line : ass->Styles)
 			lines.push_back(&line);
 		for (auto& line : ass->Events)