1/* (c) DDNet developers. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <base/logger.h>
5#include <base/os.h>
6#include <base/system.h>
7
8#include <engine/gfx/image_loader.h>
9#include <engine/shared/datafile.h>
10#include <engine/storage.h>
11
12#include <game/gamecore.h>
13#include <game/mapitems.h>
14
15/*
16 Usage: map_convert_07 <source map filepath> <dest map filepath>
17*/
18
19static CDataFileReader g_DataReader;
20static CDataFileWriter g_DataWriter;
21
22// global new image data (set by ReplaceImageItem)
23static int g_aNewDataSize[MAX_MAPIMAGES];
24static void *g_apNewData[MAX_MAPIMAGES];
25
26static int g_Index = 0;
27static int g_NextDataItemId = -1;
28
29static int g_aImageIds[MAX_MAPIMAGES];
30
31static bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename)
32{
33 if(LayerType != MAPITEMTYPE_LAYER)
34 return true;
35
36 CMapItemLayer *pImgLayer = (CMapItemLayer *)pLayerItem;
37 if(pImgLayer->m_Type != LAYERTYPE_TILES)
38 return true;
39
40 CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pImgLayer;
41 if(pTMap->m_Image == -1)
42 return true;
43
44 int Type;
45 void *pItem = g_DataReader.GetItem(Index: g_aImageIds[pTMap->m_Image], pType: &Type);
46 if(Type != MAPITEMTYPE_IMAGE)
47 return true;
48
49 CMapItemImage *pImgItem = (CMapItemImage *)pItem;
50
51 if(pImgItem->m_Width % 16 == 0 && pImgItem->m_Height % 16 == 0 && pImgItem->m_Width > 0 && pImgItem->m_Height > 0)
52 return true;
53
54 char aTileLayerName[12];
55 IntsToStr(pInts: pTMap->m_aName, NumInts: std::size(pTMap->m_aName), pStr: aTileLayerName, StrSize: std::size(aTileLayerName));
56
57 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
58 dbg_msg(sys: "map_convert_07", fmt: "%s: Tile layer \"%s\" uses image \"%s\" with width %d, height %d, which is not divisible by 16. This is not supported in Teeworlds 0.7. Please scale the image and replace it manually.", pFilename, aTileLayerName, pName == nullptr ? "(error)" : pName, pImgItem->m_Width, pImgItem->m_Height);
59 return false;
60}
61
62static void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewImgItem)
63{
64 if(!pImgItem->m_External)
65 return pImgItem;
66
67 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
68 if(pName == nullptr || pName[0] == '\0')
69 {
70 dbg_msg(sys: "map_convert_07", fmt: "failed to load name of image %d", Index);
71 return pImgItem;
72 }
73
74 dbg_msg(sys: "map_convert_07", fmt: "embedding image '%s'", pName);
75
76 char aStr[IO_MAX_PATH_LENGTH];
77 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "data/mapres/%s.png", pName);
78
79 CImageInfo ImgInfo;
80 int PngliteIncompatible;
81 if(!CImageLoader::LoadPng(File: io_open(filename: aStr, flags: IOFLAG_READ), pFilename: aStr, Image&: ImgInfo, PngliteIncompatible))
82 return pImgItem; // keep as external if we don't have a mapres to replace
83
84 const size_t MaxImageDimension = 1 << 13;
85 if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA || ImgInfo.m_Width > MaxImageDimension || ImgInfo.m_Height > MaxImageDimension)
86 {
87 dbg_msg(sys: "map_convert_07", fmt: "ERROR: only RGBA PNG images with maximum width/height %" PRIzu " are supported", MaxImageDimension);
88 ImgInfo.Free();
89 return pImgItem;
90 }
91
92 *pNewImgItem = *pImgItem;
93
94 pNewImgItem->m_Width = ImgInfo.m_Width;
95 pNewImgItem->m_Height = ImgInfo.m_Height;
96 pNewImgItem->m_External = false;
97 pNewImgItem->m_ImageData = g_NextDataItemId++;
98
99 g_apNewData[g_Index] = ImgInfo.m_pData;
100 g_aNewDataSize[g_Index] = ImgInfo.DataSize();
101 g_Index++;
102
103 return (void *)pNewImgItem;
104}
105
106int main(int argc, const char **argv)
107{
108 CCmdlineFix CmdlineFix(&argc, &argv);
109 log_set_global_logger_default();
110
111 if(argc < 2 || argc > 3)
112 {
113 dbg_msg(sys: "map_convert_07", fmt: "Invalid arguments");
114 dbg_msg(sys: "map_convert_07", fmt: "Usage: map_convert_07 <source map filepath> [<dest map filepath>]");
115 return -1;
116 }
117
118 std::unique_ptr<IStorage> pStorage = std::unique_ptr<IStorage>(CreateStorage(InitializationType: IStorage::EInitializationType::BASIC, NumArgs: argc, ppArguments: argv));
119 if(!pStorage)
120 {
121 log_error("map_convert_07", "Error creating basic storage");
122 return -1;
123 }
124
125 const char *pSourceFilename = argv[1];
126 char aDestFilename[IO_MAX_PATH_LENGTH];
127
128 if(argc == 3)
129 {
130 str_copy(dst: aDestFilename, src: argv[2], dst_size: sizeof(aDestFilename));
131 }
132 else
133 {
134 char aBuf[IO_MAX_PATH_LENGTH];
135 IStorage::StripPathAndExtension(pFilename: pSourceFilename, pBuffer: aBuf, BufferSize: sizeof(aBuf));
136 str_format(buffer: aDestFilename, buffer_size: sizeof(aDestFilename), format: "data/maps7/%s.map", aBuf);
137 if(fs_makedir(path: "data") != 0)
138 {
139 dbg_msg(sys: "map_convert_07", fmt: "failed to create data directory");
140 return -1;
141 }
142
143 if(fs_makedir(path: "data/maps7") != 0)
144 {
145 dbg_msg(sys: "map_convert_07", fmt: "failed to create data/maps7 directory");
146 return -1;
147 }
148 }
149
150 if(!g_DataReader.Open(pStorage: pStorage.get(), pPath: pSourceFilename, StorageType: IStorage::TYPE_ABSOLUTE))
151 {
152 dbg_msg(sys: "map_convert_07", fmt: "failed to open source map. filename='%s'", pSourceFilename);
153 return -1;
154 }
155
156 if(!g_DataWriter.Open(pStorage: pStorage.get(), pFilename: aDestFilename, StorageType: IStorage::TYPE_ABSOLUTE))
157 {
158 dbg_msg(sys: "map_convert_07", fmt: "failed to open destination map. filename='%s'", aDestFilename);
159 return -1;
160 }
161
162 g_NextDataItemId = g_DataReader.NumData();
163
164 size_t i = 0;
165 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
166 {
167 int Type;
168 g_DataReader.GetItem(Index, pType: &Type);
169 if(Type == MAPITEMTYPE_IMAGE)
170 {
171 if(i >= MAX_MAPIMAGES)
172 {
173 dbg_msg(sys: "map_convert_07", fmt: "map uses more images than the client maximum of %" PRIzu ". filename='%s'", MAX_MAPIMAGES, pSourceFilename);
174 break;
175 }
176 g_aImageIds[i] = Index;
177 i++;
178 }
179 }
180
181 bool Success = true;
182
183 // add all items
184 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
185 {
186 int Type, Id;
187 CUuid Uuid;
188 void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
189
190 // Filter ITEMTYPE_EX items, they will be automatically added again.
191 if(Type == ITEMTYPE_EX)
192 {
193 continue;
194 }
195
196 int Size = g_DataReader.GetItemSize(Index);
197 Success &= CheckImageDimensions(pLayerItem: pItem, LayerType: Type, pFilename: pSourceFilename);
198
199 CMapItemImage NewImageItem;
200 if(Type == MAPITEMTYPE_IMAGE)
201 {
202 pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pNewImgItem: &NewImageItem);
203 if(!pItem)
204 return -1;
205 Size = sizeof(CMapItemImage);
206 NewImageItem.m_Version = 1;
207 }
208 g_DataWriter.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid);
209 }
210
211 // add all data
212 for(int Index = 0; Index < g_DataReader.NumData(); Index++)
213 {
214 void *pData = g_DataReader.GetData(Index);
215 int Size = g_DataReader.GetDataSize(Index);
216 g_DataWriter.AddData(Size, pData);
217 }
218
219 for(int Index = 0; Index < g_Index; Index++)
220 {
221 g_DataWriter.AddData(Size: g_aNewDataSize[Index], pData: g_apNewData[Index]);
222 }
223
224 g_DataReader.Close();
225 g_DataWriter.Finish();
226 return Success ? 0 : -1;
227}
228