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