diff --git a/src/tools/map_replace_area.cpp b/src/tools/map_replace_area.cpp
new file mode 100644
index 000000000..5be50c080
--- /dev/null
+++ b/src/tools/map_replace_area.cpp
@@ -0,0 +1,274 @@
+#include
+#include
+#include
+#include
+#include
+
+// global new layers data (set by ReplaceAreaTiles and ReplaceAreaQuads)
+void *g_pNewData[1024];
+void *g_pNewItem[1024];
+int g_NewDataSize[1024];
+
+bool ReplaceArea(IStorage*, const char[][64], const int[][2]);
+bool OpenMaps(IStorage*, const char[][64], CDataFileReader[], CDataFileWriter&);
+bool CompareLayers(const char[][64], CDataFileReader[]);
+void ReplaceAreaTiles(CDataFileReader[], const int[][2], CMapItemLayer*[]);
+void ReplaceAreaQuads(CDataFileReader[], const int[][2], CMapItemLayer*[], const int);
+bool IsQuadInsideArea(const int[][2], const int, CPoint&);
+void SaveOutputMap(CDataFileReader[], CDataFileWriter&);
+
+int main(int argc, const char *argv[])
+{
+ cmdline_fix(&argc, &argv);
+ dbg_logger_stdout();
+
+ if(argc != 10)
+ {
+ dbg_msg("map_replace_area", "Invalid arguments");
+ dbg_msg("map_replace_area", "Usage: %s ", argv[0]);
+ return -1;
+ }
+
+ char pMapNames[3][64];
+ strcpy(pMapNames[0], argv[1]); //from_map
+ strcpy(pMapNames[1], argv[4]); //to_map
+ strcpy(pMapNames[2], argv[9]); //output_map
+
+ int pAreaData[3][2];
+ pAreaData[0][0] = atoi(argv[2]); //from_x
+ pAreaData[0][1] = atoi(argv[3]); //from_y
+ pAreaData[1][0] = atoi(argv[5]); //to_x
+ pAreaData[1][1] = atoi(argv[6]); //to_y
+ pAreaData[2][0] = atoi(argv[7]); //width
+ pAreaData[2][1] = atoi(argv[8]); //height
+
+ cmdline_free(argc, argv);
+
+ IStorage *pStorage = CreateLocalStorage();
+ for(int i = 0; i < 1024; i++)
+ {
+ g_pNewData[i] = g_pNewItem[i] = 0;
+ g_NewDataSize[i] = 0;
+ }
+
+ dbg_msg("map_replace_area", "from_map='%s'; to_map='%s'; from_area='(%dx,%dy)'; to_area='%dx,%dy'; area_width='%d'; area_heigth='%d'; output_map='%s'",
+ pMapNames[0], pMapNames[1], pAreaData[0][0], pAreaData[0][1], pAreaData[1][0], pAreaData[1][1], pAreaData[2][0], pAreaData[2][1], pMapNames[2]);
+
+ return ReplaceArea(pStorage, pMapNames, pAreaData) ? 0 : 1;
+}
+
+bool ReplaceArea(IStorage *pStorage, const char pMapNames[3][64], const int pAreaData[2][2])
+{
+
+ CDataFileReader InputMaps[2];
+ CDataFileWriter OutputMap;
+
+ if(!OpenMaps(pStorage, pMapNames, InputMaps, OutputMap))
+ return false;
+
+ if(!CompareLayers(pMapNames, InputMaps))
+ return false;
+
+ int LayersOffset[2], LayersCount;
+ InputMaps[0].GetType(MAPITEMTYPE_LAYER, &LayersOffset[0], &LayersCount);
+ InputMaps[1].GetType(MAPITEMTYPE_LAYER, &LayersOffset[1], &LayersCount);
+
+ for(int j = 0; j < LayersCount; j++)
+ {
+ CMapItemLayer *pItem[2];
+ for(int i = 0; i < 2; i++)
+ pItem[i] = (CMapItemLayer *)InputMaps[i].GetItem(LayersOffset[i] + j, 0, 0);
+
+ if(pItem[0]->m_Type == LAYERTYPE_TILES)
+ ReplaceAreaTiles(InputMaps, pAreaData, pItem);
+ else if (pItem[0]->m_Type == LAYERTYPE_QUADS)
+ ReplaceAreaQuads(InputMaps, pAreaData, pItem, LayersOffset[1] + j);
+ }
+
+ SaveOutputMap(InputMaps, OutputMap);
+
+ return true;
+}
+
+
+bool OpenMaps(IStorage *pStorage, const char pMapNames[3][64], CDataFileReader InputMaps[2], CDataFileWriter& OutputMap)
+{
+ for(int i = 0; i < 2; i++)
+ {
+ if(!InputMaps[i].Open(pStorage, pMapNames[i], IStorage::TYPE_ABSOLUTE))
+ {
+ dbg_msg("map_replace_area", "Error: unable to open map '%s'", pMapNames[i]);
+ return false;
+ }
+ }
+
+ if(!OutputMap.Open(pStorage, pMapNames[2], IStorage::TYPE_ABSOLUTE))
+ {
+ dbg_msg("map_replace_area", "Error: unable to open map '%s'", pMapNames[2]);
+ return false;
+ }
+
+ return true;
+}
+
+bool CompareLayers(const char pMapNames[3][64], CDataFileReader InputMaps[2])
+{
+ int Start[2], Num[2];
+ InputMaps[0].GetType(MAPITEMTYPE_LAYER, &Start[0], &Num[0]);
+ InputMaps[1].GetType(MAPITEMTYPE_LAYER, &Start[1], &Num[1]);
+
+ if(Num[0] != Num[1])
+ {
+ dbg_msg("map_replace_area", "Error: different layers number");
+ for(int i = 0; i < 2; i++)
+ dbg_msg("map_replace_area", " \"%s\": %d layers", pMapNames[i], Num[i]);
+ return false;
+ }
+
+ for(int j = 0; j < Num[0]; j++)
+ {
+ CMapItemLayer *pItem[2];
+ for(int i = 0; i < 2; i++)
+ pItem[i] = (CMapItemLayer *)InputMaps[i].GetItem(Start[i] + j, 0, 0);
+
+ if(pItem[0]->m_Type != pItem[1]->m_Type)
+ {
+ dbg_msg("map_replace_area", "Error: different types on layer number %d", j);
+ for(int i = 0; i < 2; i++)
+ {
+ char aLayerType[16];
+ strcpy(aLayerType, pItem[i]->m_Type == LAYERTYPE_TILES ? "tiles layer" : "quad layer");
+ dbg_msg("map_replace_area", " \"%s\": %s", pMapNames[i], aLayerType);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ReplaceAreaTiles(CDataFileReader InputMaps[2], const int pAreaData[2][2], CMapItemLayer* pItem[2])
+{
+ CMapItemLayerTilemap *pTilemap[2];
+ for(int i = 0; i < 2; ++i)
+ pTilemap[i] = (CMapItemLayerTilemap *)pItem[i];
+
+ CTile *pTile[2];
+ for(int i = 0; i < 2; ++i)
+ pTile[i] = (CTile *)InputMaps[i].GetData(pTilemap[i]->m_Data);
+
+ bool bDataChanged = false;
+ for(int y = 0; y < pAreaData[2][1]; y++)
+ {
+ if(y + pAreaData[0][1] >= pTilemap[0]->m_Height || y + pAreaData[1][1] >= pTilemap[1]->m_Height)
+ continue;
+
+ for(int x = 0; x < pAreaData[2][0]; x++)
+ {
+ if(x + pAreaData[0][0] >= pTilemap[1]->m_Width || x + pAreaData[1][0] >= pTilemap[1]->m_Width)
+ continue;
+
+ int pos[2];
+ for(int i = 0; i < 2; i++)
+ pos[i] = (y + pAreaData[i][1]) * pTilemap[i]->m_Width + x + pAreaData[i][0];
+
+ pTile[1][pos[1]] = pTile[0][pos[0]];
+ bDataChanged = true;
+ }
+ }
+
+ if(bDataChanged)
+ g_pNewData[pTilemap[1]->m_Data] = pTile[1];
+}
+
+void ReplaceAreaQuads(CDataFileReader InputMaps[2], const int pAreaData[2][2], CMapItemLayer* pItem[2], const int ItemNumber)
+{
+
+ CMapItemLayerQuads *pQuadLayer[2];
+ for(int i = 0; i < 2; i++)
+ pQuadLayer[i] = (CMapItemLayerQuads *)pItem[i];
+
+ CQuad *pQuads[3];
+ for(int i = 0; i < 2; i++)
+ pQuads[i] = (CQuad *)InputMaps[i].GetDataSwapped(pQuadLayer[i]->m_Data);
+
+ pQuads[3] = new CQuad[maximum(pQuadLayer[0]->m_NumQuads, pQuadLayer[1]->m_NumQuads)];
+ int QuadsCounter = 0;
+
+ bool bDataChanged = false;
+ for(int i = 0; i < pQuadLayer[1]->m_NumQuads; i++)
+ {
+ if(IsQuadInsideArea(pAreaData, 1, pQuads[1][i].m_aPoints[4]))
+ {
+ bDataChanged = true;
+ continue;
+ }
+
+ pQuads[3][QuadsCounter] = pQuads[1][i];
+ QuadsCounter++;
+ }
+
+ for(int i = 0; i < pQuadLayer[0]->m_NumQuads; i++)
+ {
+ if(IsQuadInsideArea(pAreaData, 0, pQuads[0][i].m_aPoints[4]))
+ {
+ pQuads[3][QuadsCounter] = pQuads[0][i];
+
+ float Distancex = (pAreaData[0][0] - pAreaData[1][0]) * 32;
+ float Distancey = (pAreaData[0][1] - pAreaData[1][1]) * 32;
+
+ for(int j = 0; j < 5; j++)
+ {
+ pQuads[3][QuadsCounter].m_aPoints[j].x -= f2fx(Distancex);
+ pQuads[3][QuadsCounter].m_aPoints[j].y -= f2fx(Distancey);
+ }
+
+ QuadsCounter++;
+ bDataChanged = true;
+ }
+ }
+
+ if(bDataChanged)
+ {
+ g_pNewData[pQuadLayer[1]->m_Data] = pQuads[3];
+ g_NewDataSize[pQuadLayer[1]->m_Data] = ((int)sizeof(CQuad)) * QuadsCounter;
+ pQuadLayer[1]->m_NumQuads = QuadsCounter;
+ g_pNewItem[ItemNumber] = pItem[1];
+ }
+
+}
+
+bool IsQuadInsideArea(const int pAreaData[2][2], const int AreaIndex, CPoint& QuadPivot)
+{
+ return fx2f(QuadPivot.x) / 32 >= pAreaData[AreaIndex][0]
+ && fx2f(QuadPivot.x) / 32 <= pAreaData[AreaIndex][0] + pAreaData[2][0]
+ && fx2f(QuadPivot.y) / 32 >= pAreaData[AreaIndex][1]
+ && fx2f(QuadPivot.y) / 32 <= pAreaData[AreaIndex][1] + pAreaData[2][1];
+}
+
+void SaveOutputMap(CDataFileReader InputMaps[2], CDataFileWriter& OutputMap)
+{
+ for(int i = 0; i < InputMaps[1].NumItems(); i++)
+ {
+ int ID, Type;
+ int Size = InputMaps[1].GetItemSize(i);
+ void *pItem = InputMaps[1].GetItem(i, &Type, &ID);
+ if(g_pNewItem[i])
+ pItem = g_pNewItem[i];
+
+ // filter ITEMTYPE_EX items, they will be automatically added again
+ if(Type == ITEMTYPE_EX)
+ continue;
+
+ OutputMap.AddItem(Type, ID, Size, pItem);
+ }
+
+ for(int i = 0; i < InputMaps[1].NumData(); i++)
+ {
+ void *pData = g_pNewData[i] ? g_pNewData[i] : InputMaps[1].GetData(i);
+ int Size = g_NewDataSize[i] ? g_NewDataSize[i] : InputMaps[1].GetDataSize(i);
+ OutputMap.AddData(Size, pData);
+ }
+
+ OutputMap.Finish();
+}
\ No newline at end of file