diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
index 17c6fa7dd013e984ea3d4af8d6c51b294eed2a47..722efa42bc445fe1cef0c681872c1d5caa9582aa 100644
--- a/build/android/jni/Android.mk
+++ b/build/android/jni/Android.mk
@@ -224,6 +224,7 @@ LOCAL_SRC_FILES :=                                \
 		jni/src/unittest/test_objdef.cpp          \
 		jni/src/unittest/test_profiler.cpp        \
 		jni/src/unittest/test_random.cpp          \
+		jni/src/unittest/test_schematic.cpp       \
 		jni/src/unittest/test_serialization.cpp   \
 		jni/src/unittest/test_settings.cpp        \
 		jni/src/unittest/test_socket.cpp          \
diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp
index bea08ef12c891096ea908632cc6d3248b1e2ee26..33f82a74cbaa8cbd99eaead66c97263239247013 100644
--- a/src/mg_schematic.cpp
+++ b/src/mg_schematic.cpp
@@ -94,8 +94,7 @@ void Schematic::resolveNodeNames()
 }
 
 
-void Schematic::blitToVManip(v3s16 p, MMVManip *vm,
-	Rotation rot, bool force_placement)
+void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place)
 {
 	sanity_check(m_ndef != NULL);
 
@@ -151,7 +150,7 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm,
 				if (schemdata[i].param1 == MTSCHEM_PROB_NEVER)
 					continue;
 
-				if (!force_placement) {
+				if (!force_place) {
 					content_t c = vm->m_data[vi].getContent();
 					if (c != CONTENT_AIR && c != CONTENT_IGNORE)
 						continue;
@@ -174,7 +173,7 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm,
 
 
 void Schematic::placeStructure(Map *map, v3s16 p, u32 flags,
-	Rotation rot, bool force_placement)
+	Rotation rot, bool force_place)
 {
 	assert(schemdata != NULL); // Pre-condition
 	sanity_check(m_ndef != NULL);
@@ -198,7 +197,7 @@ void Schematic::placeStructure(Map *map, v3s16 p, u32 flags,
 	v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1));
 	vm->initialEmerge(bp1, bp2);
 
-	blitToVManip(p, vm, rot, force_placement);
+	blitToVManip(p, vm, rot, force_place);
 
 	std::map<v3s16, MapBlock *> lighting_modified_blocks;
 	std::map<v3s16, MapBlock *> modified_blocks;
@@ -405,15 +404,17 @@ bool Schematic::loadSchematicFromFile(const std::string &filename,
 }
 
 
-bool Schematic::saveSchematicToFile(const std::string &filename)
+bool Schematic::saveSchematicToFile(const std::string &filename,
+	INodeDefManager *ndef)
 {
 	MapNode *orig_schemdata = schemdata;
 	std::vector<std::string> ndef_nodenames;
 	std::vector<std::string> *names;
 
-	// Only carry out the modification if we know the nodes
-	// were resolved at this point
-	if (m_resolve_done) {
+	if (m_resolve_done && ndef == NULL)
+		ndef = m_ndef;
+
+	if (ndef) {
 		names = &ndef_nodenames;
 
 		u32 volume = size.X * size.Y * size.Z;
@@ -421,19 +422,22 @@ bool Schematic::saveSchematicToFile(const std::string &filename)
 		for (u32 i = 0; i != volume; i++)
 			schemdata[i] = orig_schemdata[i];
 
-		generate_nodelist_and_update_ids(schemdata, volume, names, m_ndef);
+		generate_nodelist_and_update_ids(schemdata, volume, names, ndef);
 	} else { // otherwise, use the names we have on hand in the list
 		names = &m_nodenames;
 	}
 
 	std::ostringstream os(std::ios_base::binary);
-	serializeToMts(&os, *names);
+	bool status = serializeToMts(&os, *names);
 
-	if (m_resolve_done) {
+	if (ndef) {
 		delete []schemdata;
 		schemdata = orig_schemdata;
 	}
 
+	if (!status)
+		return false;
+
 	return fs::safeWriteToFile(filename, os.str());
 }
 
diff --git a/src/mg_schematic.h b/src/mg_schematic.h
index 5b546f879c539fa0e080d438bcd59b5f4609b9af..3d3e328d3185636a10d92d4ab74cedcb11a43198 100644
--- a/src/mg_schematic.h
+++ b/src/mg_schematic.h
@@ -85,26 +85,14 @@ enum SchematicFormatType {
 
 class Schematic : public ObjDef, public NodeResolver {
 public:
-	std::vector<content_t> c_nodes;
-
-	u32 flags;
-	v3s16 size;
-	MapNode *schemdata;
-	u8 *slice_probs;
-
 	Schematic();
 	virtual ~Schematic();
 
 	virtual void resolveNodeNames();
 
-	void updateContentIds();
-
-	void blitToVManip(v3s16 p, MMVManip *vm,
-		Rotation rot, bool force_placement);
-
 	bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef,
-		StringMap *replace_names);
-	bool saveSchematicToFile(const std::string &filename);
+		StringMap *replace_names=NULL);
+	bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef);
 	bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2);
 
 	bool deserializeFromMts(std::istream *is, std::vector<std::string> *names);
@@ -112,11 +100,18 @@ class Schematic : public ObjDef, public NodeResolver {
 	bool serializeToLua(std::ostream *os, const std::vector<std::string> &names,
 		bool use_comments, u32 indent_spaces);
 
-	void placeStructure(Map *map, v3s16 p, u32 flags,
-		Rotation rot, bool force_placement);
+	void blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place);
+	void placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place);
+
 	void applyProbabilities(v3s16 p0,
 		std::vector<std::pair<v3s16, u8> > *plist,
 		std::vector<std::pair<s16, u8> > *splist);
+
+	std::vector<content_t> c_nodes;
+	u32 flags;
+	v3s16 size;
+	MapNode *schemdata;
+	u8 *slice_probs;
 };
 
 class SchematicManager : public ObjDefManager {
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index 7e9c07939fd7326de300861d5d52d8c3c11472d2..5422447edeb6365f6d384278a383a4cc89c55dc3 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -1031,10 +1031,9 @@ int ModApiMapgen::l_generate_decorations(lua_State *L)
 // create_schematic(p1, p2, probability_list, filename, y_slice_prob_list)
 int ModApiMapgen::l_create_schematic(lua_State *L)
 {
-	Schematic schem;
-	schem.m_ndef = getServer(L)->getNodeDefManager();
-
+	INodeDefManager *ndef = getServer(L)->getNodeDefManager();
 	Map *map = &(getEnv(L)->getMap());
+	Schematic schem;
 
 	v3s16 p1 = check_v3s16(L, 1);
 	v3s16 p2 = check_v3s16(L, 2);
@@ -1081,7 +1080,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L)
 
 	schem.applyProbabilities(p1, &prob_list, &slice_prob_list);
 
-	schem.saveSchematicToFile(filename);
+	schem.saveSchematicToFile(filename, ndef);
 	actionstream << "create_schematic: saved schematic file '"
 		<< filename << "'." << std::endl;
 
diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt
index 6ec5eaea514431412b54a938584828f04ca67fbe..dcc68f9c8bcd07d8278158e2b3a1615825fa82da 100644
--- a/src/unittest/CMakeLists.txt
+++ b/src/unittest/CMakeLists.txt
@@ -12,6 +12,7 @@ set (UNITTEST_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/test_objdef.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp
diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp
index 57843da5ee8f7c5ec27aa078ac70af6b905304f8..d0ffb423ff515d041fcda808781111741511796b 100644
--- a/src/unittest/test.cpp
+++ b/src/unittest/test.cpp
@@ -276,9 +276,35 @@ bool TestBase::testModule(IGameDef *gamedef)
 		<< " failures / " << num_tests_run << " tests) - " << tdiff
 		<< "ms" << std::endl;
 
+	if (!m_test_dir.empty())
+		fs::RecursiveDelete(m_test_dir);
+
 	return num_tests_failed == 0;
 }
 
+std::string TestBase::getTestTempDirectory()
+{
+	if (!m_test_dir.empty())
+		return m_test_dir;
+
+	char buf[32];
+	snprintf(buf, sizeof(buf), "%08X", myrand());
+
+	m_test_dir = fs::TempPath() + DIR_DELIM "mttest_" + buf;
+	if (!fs::CreateDir(m_test_dir))
+		throw TestFailedException();
+
+	return m_test_dir;
+}
+
+std::string TestBase::getTestTempFile()
+{
+	char buf[32];
+	snprintf(buf, sizeof(buf), "%08X", myrand());
+
+	return getTestTempDirectory() + DIR_DELIM + buf + ".tmp";
+}
+
 
 /*
 	NOTE: These tests became non-working then NodeContainer was removed.
diff --git a/src/unittest/test.h b/src/unittest/test.h
index 8219e30fc0f2eb64c464d560abed1f4816116bf4..e1f1721f951127943f788bd012afcdf0478b6fb1 100644
--- a/src/unittest/test.h
+++ b/src/unittest/test.h
@@ -102,11 +102,17 @@ class IGameDef;
 class TestBase {
 public:
 	bool testModule(IGameDef *gamedef);
+	std::string getTestTempDirectory();
+	std::string getTestTempFile();
+
 	virtual void runTests(IGameDef *gamedef) = 0;
 	virtual const char *getName() = 0;
 
 	u32 num_tests_failed;
 	u32 num_tests_run;
+
+private:
+	std::string m_test_dir;
 };
 
 class TestManager {
diff --git a/src/unittest/test_schematic.cpp b/src/unittest/test_schematic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c9e9704590535eda83abc9392c5b9d88f3416a6a
--- /dev/null
+++ b/src/unittest/test_schematic.cpp
@@ -0,0 +1,265 @@
+ /*
+Minetest
+Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "test.h"
+
+#include "mg_schematic.h"
+#include "gamedef.h"
+#include "nodedef.h"
+
+class TestSchematic : public TestBase {
+public:
+	TestSchematic() { TestManager::registerTestModule(this); }
+	const char *getName() { return "TestSchematic"; }
+
+	void runTests(IGameDef *gamedef);
+
+	void testMtsSerializeDeserialize(INodeDefManager *ndef);
+	void testLuaTableSerialize(INodeDefManager *ndef);
+	void testFileSerializeDeserialize(INodeDefManager *ndef);
+
+	static const content_t test_schem_data[7 * 6 * 4];
+	static const content_t test_schem_data2[3 * 3 * 3];
+	static const char *expected_lua_output;
+};
+
+static TestSchematic g_test_instance;
+
+void TestSchematic::runTests(IGameDef *gamedef)
+{
+	IWritableNodeDefManager *ndef =
+		(IWritableNodeDefManager *)gamedef->getNodeDefManager();
+
+	ndef->setNodeRegistrationStatus(true);
+
+	TEST(testMtsSerializeDeserialize, ndef);
+	TEST(testLuaTableSerialize, ndef);
+	TEST(testFileSerializeDeserialize, ndef);
+
+	ndef->resetNodeResolveState();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TestSchematic::testMtsSerializeDeserialize(INodeDefManager *ndef)
+{
+	static const v3s16 size(7, 6, 4);
+	static const u32 volume = size.X * size.Y * size.Z;
+
+	std::stringstream ss(std::ios_base::binary |
+		std::ios_base::in | std::ios_base::out);
+
+	std::vector<std::string> names;
+	names.push_back("foo");
+	names.push_back("bar");
+	names.push_back("baz");
+	names.push_back("qux");
+
+	Schematic schem, schem2;
+
+	schem.flags       = 0;
+	schem.size        = size;
+	schem.schemdata   = new MapNode[volume];
+	schem.slice_probs = new u8[size.Y];
+	for (size_t i = 0; i != volume; i++)
+		schem.schemdata[i] = MapNode(test_schem_data[i], MTSCHEM_PROB_ALWAYS, 0);
+	for (size_t y = 0; y != size.Y; y++)
+		schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;
+
+	UASSERT(schem.serializeToMts(&ss, names));
+
+	ss.seekg(0);
+	names.clear();
+
+	UASSERT(schem2.deserializeFromMts(&ss, &names));
+
+	UASSERTEQ(size_t, names.size(), 4);
+	UASSERTEQ(std::string, names[0], "foo");
+	UASSERTEQ(std::string, names[1], "bar");
+	UASSERTEQ(std::string, names[2], "baz");
+	UASSERTEQ(std::string, names[3], "qux");
+
+	UASSERT(schem2.size == size);
+	for (size_t i = 0; i != volume; i++)
+		UASSERT(schem2.schemdata[i] == schem.schemdata[i]);
+	for (size_t y = 0; y != size.Y; y++)
+		UASSERTEQ(u8, schem2.slice_probs[y], schem.slice_probs[y]);
+}
+
+
+void TestSchematic::testLuaTableSerialize(INodeDefManager *ndef)
+{
+	static const v3s16 size(3, 3, 3);
+	static const u32 volume = size.X * size.Y * size.Z;
+
+	Schematic schem;
+
+	schem.flags       = 0;
+	schem.size        = size;
+	schem.schemdata   = new MapNode[volume];
+	schem.slice_probs = new u8[size.Y];
+	for (size_t i = 0; i != volume; i++)
+		schem.schemdata[i] = MapNode(test_schem_data2[i], MTSCHEM_PROB_ALWAYS, 0);
+	for (size_t y = 0; y != size.Y; y++)
+		schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;
+
+	std::vector<std::string> names;
+	names.push_back("air");
+	names.push_back("default:lava_source");
+	names.push_back("default:glass");
+
+	std::ostringstream ss(std::ios_base::binary);
+
+	UASSERT(schem.serializeToLua(&ss, names, false, 0));
+	UASSERTEQ(std::string, ss.str(), expected_lua_output);
+}
+
+
+void TestSchematic::testFileSerializeDeserialize(INodeDefManager *ndef)
+{
+	static const v3s16 size(3, 3, 3);
+	static const u32 volume = size.X * size.Y * size.Z;
+	static const content_t content_map[] = {
+		CONTENT_AIR,
+		t_CONTENT_STONE,
+		t_CONTENT_LAVA,
+	};
+	static const content_t content_map2[] = {
+		CONTENT_AIR,
+		t_CONTENT_STONE,
+		t_CONTENT_WATER,
+	};
+	StringMap replace_names;
+	replace_names["default:lava"] = "default:water";
+
+	Schematic schem1, schem2;
+
+	//// Construct the schematic to save
+	schem1.flags          = 0;
+	schem1.size           = size;
+	schem1.schemdata      = new MapNode[volume];
+	schem1.slice_probs    = new u8[size.Y];
+	schem1.slice_probs[0] = 80;
+	schem1.slice_probs[1] = 160;
+	schem1.slice_probs[2] = 240;
+
+	for (size_t i = 0; i != volume; i++) {
+		content_t c = content_map[test_schem_data2[i]];
+		schem1.schemdata[i] = MapNode(c, MTSCHEM_PROB_ALWAYS, 0);
+	}
+
+	std::string temp_file = getTestTempFile();
+	UASSERT(schem1.saveSchematicToFile(temp_file, ndef));
+	UASSERT(schem2.loadSchematicFromFile(temp_file, ndef, &replace_names));
+
+	UASSERT(schem2.size == size);
+	UASSERT(schem2.slice_probs[0] == 80);
+	UASSERT(schem2.slice_probs[1] == 160);
+	UASSERT(schem2.slice_probs[2] == 240);
+
+	for (size_t i = 0; i != volume; i++) {
+		content_t c = content_map2[test_schem_data2[i]];
+		UASSERT(schem2.schemdata[i] == MapNode(c, MTSCHEM_PROB_ALWAYS, 0));
+	}
+}
+
+
+// Should form a cross-shaped-thing...?
+const content_t TestSchematic::test_schem_data[7 * 6 * 4] = {
+	3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0
+	3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0
+	3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0
+	3, 1, 1, 2, 1, 1, 3, // Y=3, Z=0
+	3, 2, 2, 2, 2, 2, 3, // Y=4, Z=0
+	3, 1, 1, 2, 1, 1, 3, // Y=5, Z=0
+
+	0, 0, 1, 1, 1, 0, 0, // Y=0, Z=1
+	0, 0, 1, 2, 1, 0, 0, // Y=1, Z=1
+	0, 0, 1, 2, 1, 0, 0, // Y=2, Z=1
+	1, 1, 1, 2, 1, 1, 1, // Y=3, Z=1
+	1, 2, 2, 2, 2, 2, 1, // Y=4, Z=1
+	1, 1, 1, 2, 1, 1, 1, // Y=5, Z=1
+
+	0, 0, 1, 1, 1, 0, 0, // Y=0, Z=2
+	0, 0, 1, 2, 1, 0, 0, // Y=1, Z=2
+	0, 0, 1, 2, 1, 0, 0, // Y=2, Z=2
+	1, 1, 1, 2, 1, 1, 1, // Y=3, Z=2
+	1, 2, 2, 2, 2, 2, 1, // Y=4, Z=2
+	1, 1, 1, 2, 1, 1, 1, // Y=5, Z=2
+
+	3, 3, 1, 1, 1, 3, 3, // Y=0, Z=3
+	3, 0, 1, 2, 1, 0, 3, // Y=1, Z=3
+	3, 0, 1, 2, 1, 0, 3, // Y=2, Z=3
+	3, 1, 1, 2, 1, 1, 3, // Y=3, Z=3
+	3, 2, 2, 2, 2, 2, 3, // Y=4, Z=3
+	3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3
+};
+
+const content_t TestSchematic::test_schem_data2[3 * 3 * 3] = {
+	0, 0, 0,
+	0, 2, 0,
+	0, 0, 0,
+
+	0, 2, 0,
+	2, 1, 2,
+	0, 2, 0,
+
+	0, 0, 0,
+	0, 2, 0,
+	0, 0, 0,
+};
+
+const char *TestSchematic::expected_lua_output =
+	"schematic = {\n"
+	"\tsize = {x=3, y=3, z=3},\n"
+	"\tyslice_prob = {\n"
+	"\t\t{ypos=0, prob=255},\n"
+	"\t\t{ypos=1, prob=255},\n"
+	"\t\t{ypos=2, prob=255},\n"
+	"\t},\n"
+	"\tdata = {\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:lava_source\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"default:glass\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t\t{name=\"air\", param1=255, param2=0},\n"
+	"\t},\n"
+	"}\n";