diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b3951da9c..51b5e9617 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1011,6 +1011,7 @@ set (PCH_SOURCES
 	gamedata/textures/texturemanager.cpp
 	gamedata/textures/multipatchtexturebuilder.cpp
 	gamedata/textures/skyboxtexture.cpp
+	gamedata/textures/buildloader.cpp
 	gamedata/textures/formats/automaptexture.cpp
 	gamedata/textures/formats/brightmaptexture.cpp
 	gamedata/textures/formats/buildtexture.cpp
diff --git a/src/gamedata/textures/buildloader.cpp b/src/gamedata/textures/buildloader.cpp
new file mode 100644
index 000000000..e1516ebe3
--- /dev/null
+++ b/src/gamedata/textures/buildloader.cpp
@@ -0,0 +1,299 @@
+/*
+** buildtexture.cpp
+** Handling Build textures (now as a usable editing feature!)
+**
+**---------------------------------------------------------------------------
+** Copyright 2004-2006 Randy Heit
+** Copyright 2018 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. 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.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+**---------------------------------------------------------------------------
+**
+**
+*/
+
+#include "files.h"
+#include "filesystem.h"
+#include "templates.h"
+#include "cmdlib.h"
+#include "colormatcher.h"
+#include "bitmap.h"
+#include "textures/textures.h"
+#include "resourcefile.h"
+#include "image.h"
+#include "animations.h"
+#include "texturemanager.h"
+#include "r_translate.h"
+#include "r_data/sprites.h"
+
+//===========================================================================
+//
+// CountTiles
+//
+// Returns the number of tiles provided by an artfile
+//
+//===========================================================================
+
+static int CountTiles (const void *tiles)
+{
+	int version = LittleLong(*(uint32_t *)tiles);
+	if (version != 1)
+	{
+		return 0;
+	}
+
+	int tilestart = LittleLong(((uint32_t *)tiles)[2]);
+	int tileend = LittleLong(((uint32_t *)tiles)[3]);
+
+	return tileend >= tilestart ? tileend - tilestart + 1 : 0;
+}
+
+//===========================================================================
+//
+// Create palette data and remap table for the tile set's palette
+//
+//===========================================================================
+
+static int BuildPaletteTranslation(int lump)
+{
+	if (fileSystem.FileLength(lump) < 768)
+	{
+		return false;
+	}
+
+	FileData data = fileSystem.ReadFile(lump);
+	const uint8_t *ipal = (const uint8_t *)data.GetMem();
+	FRemapTable opal;
+
+	bool blood = false;
+	for (int c = 0; c < 765; c++)	// Build used VGA palettes (color values 0..63), Blood used full palettes (0..255) Let's hope this doesn't screw up...
+	{
+		if (ipal[c] >= 64)
+		{
+			blood = true;
+			break;
+		}
+	}
+
+	for (int c = 0; c < 255; c++)
+	{
+		int r, g, b;
+		if (!blood)
+		{
+			r = (ipal[3*c    ] << 2) | (ipal[3 * c    ] >> 4);
+			g = (ipal[3*c + 1] << 2) | (ipal[3 * c + 1] >> 4);
+			b = (ipal[3*c + 2] << 2) | (ipal[3 * c + 2] >> 4);
+		}
+		else
+		{
+			r = ipal[3 * c] << 2;
+			g = ipal[3 * c + 1] << 2;
+			b = ipal[3 * c + 2] << 2;
+		}
+		opal.Palette[c] = PalEntry(255, r, g, b);
+		opal.Remap[c] = ColorMatcher.Pick(r, g, b);
+	}
+	// The last entry is transparent.
+	opal.Palette[255] = 0;
+	opal.Remap[255] = 0;
+	// Store the remap table in the translation manager so that we do not need to keep track of it ourselves. 
+	// Slot 0 for internal translations is a convenient location because normally it only contains a small number of translations.
+	return GetTranslationIndex(GPalette.StoreTranslation(TRANSLATION_Standard, &opal));
+}
+
+
+//===========================================================================
+//
+// AddTiles
+//
+// Adds all the tiles in an artfile to the texture manager.
+//
+//===========================================================================
+
+void AddTiles(const FString& pathprefix, const void* tiles, FRemapTable *remap)
+{
+
+	//	int numtiles = LittleLong(((uint32_t *)tiles)[1]);	// This value is not reliable
+	int tilestart = LittleLong(((uint32_t*)tiles)[2]);
+	int tileend = LittleLong(((uint32_t*)tiles)[3]);
+	const uint16_t* tilesizx = &((const uint16_t*)tiles)[8];
+	const uint16_t* tilesizy = &tilesizx[tileend - tilestart + 1];
+	const uint32_t* picanm = (const uint32_t*)&tilesizy[tileend - tilestart + 1];
+	const uint8_t* tiledata = (const uint8_t*)&picanm[tileend - tilestart + 1];
+
+	for (int i = tilestart; i <= tileend; ++i)
+	{
+		int pic = i - tilestart;
+		int width = LittleShort(tilesizx[pic]);
+		int height = LittleShort(tilesizy[pic]);
+		uint32_t anm = LittleLong(picanm[pic]);
+		int xoffs = (int8_t)((anm >> 8) & 255) + width / 2;
+		int yoffs = (int8_t)((anm >> 16) & 255) + height / 2;
+		int size = width * height;
+		FTextureID texnum;
+		FTexture* tex;
+
+		if (width <= 0 || height <= 0) continue;
+
+		FStringf name("%sBTIL%04d", pathprefix.GetChars(), i);
+		tex = new FImageTexture(new FBuildTexture(pathprefix, i, tiledata, remap, width, height, xoffs, yoffs), name);
+		texnum = TexMan.AddTexture(tex);
+		tiledata += size;
+		tex->SetUseType(ETextureType::Override);
+
+
+		// reactivate only if the texture counter works here.
+		//StartScreen->Progress();
+
+		if ((picanm[pic] & 63) && (picanm[pic] & 192))
+		{
+			int type, speed;
+
+			switch (picanm[pic] & 192)
+			{
+			case 64:	type = 2;	break;
+			case 128:	type = 0;	break;
+			case 192:	type = 1;	break;
+			default:    type = 0;   break;  // Won't happen, but GCC bugs me if I don't put this here.
+			}
+
+			speed = (anm >> 24) & 15;
+			speed = MAX(1, (1 << speed) * 1000 / 120);	// Convert from 120 Hz to 1000 Hz.
+
+			TexAnim.AddSimpleAnim(texnum, picanm[pic] & 63, type, speed);
+		}
+
+		// Blood's rotation types:
+		// 0 - Single
+		// 1 - 5 Full
+		// 2 - 8 Full
+		// 3 - Bounce (looks no different from Single; seems to signal bouncy sprites)
+		// 4 - 5 Half (not used in game)
+		// 5 - 3 Flat (not used in game)
+		// 6 - Voxel
+		// 7 - Spin Voxel
+
+		int rotType = (anm >> 28) & 7;
+		if (rotType == 1)
+		{
+			spriteframe_t rot;
+			rot.Texture[0] =
+				rot.Texture[1] = texnum;
+			for (int j = 1; j < 4; ++j)
+			{
+				rot.Texture[j * 2].SetIndex(texnum.GetIndex() + j);
+				rot.Texture[j * 2 + 1].SetIndex(texnum.GetIndex() + j);
+				rot.Texture[16 - j * 2].SetIndex(texnum.GetIndex() + j);
+				rot.Texture[17 - j * 2].SetIndex(texnum.GetIndex() + j);
+			}
+			rot.Texture[8].SetIndex(texnum.GetIndex());
+			rot.Texture[9].SetIndex(texnum.GetIndex());
+			rot.Flip = 0x00FC;
+			rot.Voxel = NULL;
+			tex->SetRotations(SpriteFrames.Push(rot));
+		}
+		else if (rotType == 2)
+		{
+			spriteframe_t rot;
+			rot.Texture[0] =
+				rot.Texture[1] = texnum;
+			for (int j = 1; j < 8; ++j)
+			{
+				rot.Texture[16 - j * 2].SetIndex(texnum.GetIndex() + j);
+				rot.Texture[17 - j * 2].SetIndex(texnum.GetIndex() + j);
+			}
+			rot.Flip = 0;
+			rot.Voxel = NULL;
+			tex->SetRotations(SpriteFrames.Push(rot));
+		}
+	}
+}
+
+//===========================================================================
+//
+// R_CountBuildTiles
+//
+// Returns the number of tiles found. Also loads all the data for
+// R_InitBuildTiles() to process later.
+//
+//===========================================================================
+
+void InitBuildTiles()
+{
+	int lumpnum;
+	int numtiles;
+	int totaltiles = 0;
+
+	// The search rules are as follows: 
+	// - scan the entire lump directory for palette.dat files.
+	// - if one is found, process the directory for .ART files and add textures for them.
+	// - once all have been found, process all directories that may contain Build data.
+	// - the root is not excluded which allows to read this from .GRP files as well.
+	// - Blood support has been removed because it is not useful for modding to have loose .ART files.
+	//
+	// Unfortunately neither the palettes nor the .ART files contain any usable identifying marker
+	// so this can only go by the file names.
+
+	int numlumps = fileSystem.GetNumEntries();
+	for (int i = 0; i < numlumps; i++)
+	{
+		const char* name = fileSystem.GetFileFullName(i);
+		if (fileSystem.CheckNumForFullName(name) != i) continue;	// This palette is hidden by a later one. Do not process
+		FString base = ExtractFileBase(name, true);
+		base.ToLower();
+		if (base.Compare("palette.dat") == 0 && fileSystem.FileLength(i) >= 768)	// must be a valid palette, i.e. at least 256 colors.
+		{
+			FString path = ExtractFilePath(name);
+			if (path.IsNotEmpty() && path.Back() != '/') path += '/';
+
+			int translation = BuildPaletteTranslation(i);
+			auto remap = GPalette.GetTranslation(TRANSLATION_Standard, translation);
+
+			for (int numartfiles = 0; numartfiles < 1000; numartfiles++)
+			{
+				FStringf artpath("%stiles%03d.art", path.GetChars(), numartfiles);
+				// only read from the same source as the palette.
+				// The entire format here is just too volatile to allow liberal mixing.
+				// An .ART set must be treated as one unit.
+				lumpnum = fileSystem.CheckNumForFullName(artpath, fileSystem.GetFileContainer(i));
+				if (lumpnum < 0)
+				{
+					break;
+				}
+
+				auto& artdata = TexMan.GetNewBuildTileData();
+				artdata.Resize(fileSystem.FileLength(lumpnum));
+				fileSystem.ReadFile(lumpnum, &artdata[0]);
+
+				if ((numtiles = CountTiles(&artdata[0])) > 0)
+				{
+					AddTiles(path, &artdata[0], remap);
+					totaltiles += numtiles;
+				}
+			}
+		}
+	}
+}
+
diff --git a/src/gamedata/textures/formats/buildtexture.cpp b/src/gamedata/textures/formats/buildtexture.cpp
index dff76d757..44648a51c 100644
--- a/src/gamedata/textures/formats/buildtexture.cpp
+++ b/src/gamedata/textures/formats/buildtexture.cpp
@@ -35,36 +35,9 @@
 */
 
 #include "files.h"
-#include "filesystem.h"
-#include "templates.h"
-#include "cmdlib.h"
-#include "colormatcher.h"
 #include "bitmap.h"
-#include "textures/textures.h"
-#include "resourcefile.h"
 #include "image.h"
-#include "animations.h"
-#include "texturemanager.h"
-#include "r_translate.h"
-
-
-//==========================================================================
-//
-// A texture defined in a Build TILESxxx.ART file
-//
-//==========================================================================
-
-class FBuildTexture : public FImageSource
-{
-public:
-	FBuildTexture (const FString &pathprefix, int tilenum, const uint8_t *pixels, int translation, int width, int height, int left, int top);
-	TArray<uint8_t> CreatePalettedPixels(int conversion) override;
-	int CopyPixels(FBitmap *bmp, int conversion) override;
-
-protected:
-	const uint8_t *RawPixels;
-	int Translation;
-};
+#include "palettecontainer.h"
 
 
 //==========================================================================
@@ -73,7 +46,7 @@ protected:
 //
 //==========================================================================
 
-FBuildTexture::FBuildTexture(const FString &pathprefix, int tilenum, const uint8_t *pixels, int translation, int width, int height, int left, int top)
+FBuildTexture::FBuildTexture(const FString &pathprefix, int tilenum, const uint8_t *pixels, FRemapTable *translation, int width, int height, int left, int top)
 : RawPixels (pixels), Translation(translation)
 {
 	Width = width;
@@ -85,7 +58,7 @@ FBuildTexture::FBuildTexture(const FString &pathprefix, int tilenum, const uint8
 TArray<uint8_t> FBuildTexture::CreatePalettedPixels(int conversion)
 {
 	TArray<uint8_t> Pixels(Width * Height, true);
-	FRemapTable *Remap = GPalette.GetTranslation(TRANSLATION_Standard, Translation);
+	FRemapTable *Remap = Translation;
 	for (int i = 0; i < Width*Height; i++)
 	{
 		auto c = RawPixels[i];
@@ -96,257 +69,9 @@ TArray<uint8_t> FBuildTexture::CreatePalettedPixels(int conversion)
 
 int FBuildTexture::CopyPixels(FBitmap *bmp, int conversion)
 {
-	PalEntry *Remap = GPalette.GetTranslation(TRANSLATION_Standard, Translation)->Palette;
+	PalEntry *Remap = Translation->Palette;
 	bmp->CopyPixelData(0, 0, RawPixels, Width, Height, Height, 1, 0, Remap);
 	return -1;
 
 }
 
-//===========================================================================
-//
-// CountTiles
-//
-// Returns the number of tiles provided by an artfile
-//
-//===========================================================================
-
-static int CountTiles (const void *tiles)
-{
-	int version = LittleLong(*(uint32_t *)tiles);
-	if (version != 1)
-	{
-		return 0;
-	}
-
-	int tilestart = LittleLong(((uint32_t *)tiles)[2]);
-	int tileend = LittleLong(((uint32_t *)tiles)[3]);
-
-	return tileend >= tilestart ? tileend - tilestart + 1 : 0;
-}
-
-//===========================================================================
-//
-// Create palette data and remap table for the tile set's palette
-//
-//===========================================================================
-
-static int BuildPaletteTranslation(int lump)
-{
-	if (fileSystem.FileLength(lump) < 768)
-	{
-		return false;
-	}
-
-	FileData data = fileSystem.ReadFile(lump);
-	const uint8_t *ipal = (const uint8_t *)data.GetMem();
-	FRemapTable opal;
-
-	bool blood = false;
-	for (int c = 0; c < 765; c++)	// Build used VGA palettes (color values 0..63), Blood used full palettes (0..255) Let's hope this doesn't screw up...
-	{
-		if (ipal[c] >= 64)
-		{
-			blood = true;
-			break;
-		}
-	}
-
-	for (int c = 0; c < 255; c++)
-	{
-		int r, g, b;
-		if (!blood)
-		{
-			r = (ipal[3*c    ] << 2) | (ipal[3 * c    ] >> 4);
-			g = (ipal[3*c + 1] << 2) | (ipal[3 * c + 1] >> 4);
-			b = (ipal[3*c + 2] << 2) | (ipal[3 * c + 2] >> 4);
-		}
-		else
-		{
-			r = ipal[3 * c] << 2;
-			g = ipal[3 * c + 1] << 2;
-			b = ipal[3 * c + 2] << 2;
-		}
-		opal.Palette[c] = PalEntry(255, r, g, b);
-		opal.Remap[c] = ColorMatcher.Pick(r, g, b);
-	}
-	// The last entry is transparent.
-	opal.Palette[255] = 0;
-	opal.Remap[255] = 0;
-	// Store the remap table in the translation manager so that we do not need to keep track of it ourselves. 
-	// Slot 0 for internal translations is a convenient location because normally it only contains a small number of translations.
-	return GetTranslationIndex(GPalette.StoreTranslation(TRANSLATION_Standard, &opal));
-}
-
-
-#include "r_data/sprites.h"
-//===========================================================================
-//
-// AddTiles
-//
-// Adds all the tiles in an artfile to the texture manager.
-//
-//===========================================================================
-
-void AddTiles(const FString& pathprefix, const void* tiles, int translation)
-{
-
-	//	int numtiles = LittleLong(((uint32_t *)tiles)[1]);	// This value is not reliable
-	int tilestart = LittleLong(((uint32_t*)tiles)[2]);
-	int tileend = LittleLong(((uint32_t*)tiles)[3]);
-	const uint16_t* tilesizx = &((const uint16_t*)tiles)[8];
-	const uint16_t* tilesizy = &tilesizx[tileend - tilestart + 1];
-	const uint32_t* picanm = (const uint32_t*)&tilesizy[tileend - tilestart + 1];
-	const uint8_t* tiledata = (const uint8_t*)&picanm[tileend - tilestart + 1];
-
-	for (int i = tilestart; i <= tileend; ++i)
-	{
-		int pic = i - tilestart;
-		int width = LittleShort(tilesizx[pic]);
-		int height = LittleShort(tilesizy[pic]);
-		uint32_t anm = LittleLong(picanm[pic]);
-		int xoffs = (int8_t)((anm >> 8) & 255) + width / 2;
-		int yoffs = (int8_t)((anm >> 16) & 255) + height / 2;
-		int size = width * height;
-		FTextureID texnum;
-		FTexture* tex;
-
-		if (width <= 0 || height <= 0) continue;
-
-		FStringf name("%sBTIL%04d", pathprefix.GetChars(), i);
-		tex = new FImageTexture(new FBuildTexture(pathprefix, i, tiledata, translation, width, height, xoffs, yoffs), name);
-		texnum = TexMan.AddTexture(tex);
-		tiledata += size;
-		tex->SetUseType(ETextureType::Override);
-
-
-		// reactivate only if the texture counter works here.
-		//StartScreen->Progress();
-
-		if ((picanm[pic] & 63) && (picanm[pic] & 192))
-		{
-			int type, speed;
-
-			switch (picanm[pic] & 192)
-			{
-			case 64:	type = 2;	break;
-			case 128:	type = 0;	break;
-			case 192:	type = 1;	break;
-			default:    type = 0;   break;  // Won't happen, but GCC bugs me if I don't put this here.
-			}
-
-			speed = (anm >> 24) & 15;
-			speed = MAX(1, (1 << speed) * 1000 / 120);	// Convert from 120 Hz to 1000 Hz.
-
-			TexAnim.AddSimpleAnim(texnum, picanm[pic] & 63, type, speed);
-		}
-
-		// Blood's rotation types:
-		// 0 - Single
-		// 1 - 5 Full
-		// 2 - 8 Full
-		// 3 - Bounce (looks no different from Single; seems to signal bouncy sprites)
-		// 4 - 5 Half (not used in game)
-		// 5 - 3 Flat (not used in game)
-		// 6 - Voxel
-		// 7 - Spin Voxel
-
-		int rotType = (anm >> 28) & 7;
-		if (rotType == 1)
-		{
-			spriteframe_t rot;
-			rot.Texture[0] =
-				rot.Texture[1] = texnum;
-			for (int j = 1; j < 4; ++j)
-			{
-				rot.Texture[j * 2].SetIndex(texnum.GetIndex() + j);
-				rot.Texture[j * 2 + 1].SetIndex(texnum.GetIndex() + j);
-				rot.Texture[16 - j * 2].SetIndex(texnum.GetIndex() + j);
-				rot.Texture[17 - j * 2].SetIndex(texnum.GetIndex() + j);
-			}
-			rot.Texture[8].SetIndex(texnum.GetIndex());
-			rot.Texture[9].SetIndex(texnum.GetIndex());
-			rot.Flip = 0x00FC;
-			rot.Voxel = NULL;
-			tex->SetRotations(SpriteFrames.Push(rot));
-		}
-		else if (rotType == 2)
-		{
-			spriteframe_t rot;
-			rot.Texture[0] =
-				rot.Texture[1] = texnum;
-			for (int j = 1; j < 8; ++j)
-			{
-				rot.Texture[16 - j * 2].SetIndex(texnum.GetIndex() + j);
-				rot.Texture[17 - j * 2].SetIndex(texnum.GetIndex() + j);
-			}
-			rot.Flip = 0;
-			rot.Voxel = NULL;
-			tex->SetRotations(SpriteFrames.Push(rot));
-		}
-	}
-}
-
-//===========================================================================
-//
-// R_CountBuildTiles
-//
-// Returns the number of tiles found. Also loads all the data for
-// R_InitBuildTiles() to process later.
-//
-//===========================================================================
-
-void InitBuildTiles()
-{
-	int lumpnum;
-	int numtiles;
-	int totaltiles = 0;
-
-	// The search rules are as follows: 
-	// - scan the entire lump directory for palette.dat files.
-	// - if one is found, process the directory for .ART files and add textures for them.
-	// - once all have been found, process all directories that may contain Build data.
-	// - the root is not excluded which allows to read this from .GRP files as well.
-	// - Blood support has been removed because it is not useful for modding to have loose .ART files.
-	//
-	// Unfortunately neither the palettes nor the .ART files contain any usable identifying marker
-	// so this can only go by the file names.
-
-	int numlumps = fileSystem.GetNumEntries();
-	for (int i = 0; i < numlumps; i++)
-	{
-		const char* name = fileSystem.GetFileFullName(i);
-		if (fileSystem.CheckNumForFullName(name) != i) continue;	// This palette is hidden by a later one. Do not process
-		FString base = ExtractFileBase(name, true);
-		base.ToLower();
-		if (base.Compare("palette.dat") == 0 && fileSystem.FileLength(i) >= 768)	// must be a valid palette, i.e. at least 256 colors.
-		{
-			FString path = ExtractFilePath(name);
-			if (path.IsNotEmpty() && path.Back() != '/') path += '/';
-
-			int translation = BuildPaletteTranslation(i);
-			for (int numartfiles = 0; numartfiles < 1000; numartfiles++)
-			{
-				FStringf artpath("%stiles%03d.art", path.GetChars(), numartfiles);
-				// only read from the same source as the palette.
-				// The entire format here is just too volatile to allow liberal mixing.
-				// An .ART set must be treated as one unit.
-				lumpnum = fileSystem.CheckNumForFullName(artpath, fileSystem.GetFileContainer(i));
-				if (lumpnum < 0)
-				{
-					break;
-				}
-
-				auto& artdata = TexMan.GetNewBuildTileData();
-				artdata.Resize(fileSystem.FileLength(lumpnum));
-				fileSystem.ReadFile(lumpnum, &artdata[0]);
-
-				if ((numtiles = CountTiles(&artdata[0])) > 0)
-				{
-					AddTiles(path, &artdata[0], translation);
-					totaltiles += numtiles;
-				}
-			}
-		}
-	}
-}
-
diff --git a/src/gamedata/textures/image.h b/src/gamedata/textures/image.h
index f05d7c5bd..cb5a554eb 100644
--- a/src/gamedata/textures/image.h
+++ b/src/gamedata/textures/image.h
@@ -146,6 +146,27 @@ public:
 	static void RegisterForPrecache(FImageSource *img, bool requiretruecolor);
 };
 
+
+//==========================================================================
+//
+// A texture defined in a Build TILESxxx.ART file
+//
+//==========================================================================
+struct FRemapTable;
+
+class FBuildTexture : public FImageSource
+{
+public:
+	FBuildTexture(const FString& pathprefix, int tilenum, const uint8_t* pixels, FRemapTable* translation, int width, int height, int left, int top);
+	TArray<uint8_t> CreatePalettedPixels(int conversion) override;
+	int CopyPixels(FBitmap* bmp, int conversion) override;
+
+protected:
+	const uint8_t* RawPixels;
+	FRemapTable* Translation;
+};
+
+
 class FTexture;
 
 FTexture* CreateImageTexture(FImageSource* img, const char *name = nullptr) noexcept;
diff --git a/src/gamedata/textures/textures.h b/src/gamedata/textures/textures.h
index 26b79d023..fee24dc1a 100644
--- a/src/gamedata/textures/textures.h
+++ b/src/gamedata/textures/textures.h
@@ -500,6 +500,7 @@ public:
 	}
 };
 
+
 class FImageTexture : public FTexture
 {
 	FImageSource* mImage;