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/system.h> |
6 | #include <engine/gfx/image_loader.h> |
7 | #include <engine/graphics.h> |
8 | #include <engine/shared/datafile.h> |
9 | #include <engine/storage.h> |
10 | #include <game/gamecore.h> |
11 | #include <game/mapitems.h> |
12 | /* |
13 | Usage: map_convert_07 <source map filepath> <dest map filepath> |
14 | */ |
15 | |
16 | CDataFileReader g_DataReader; |
17 | CDataFileWriter g_DataWriter; |
18 | |
19 | // global new image data (set by ReplaceImageItem) |
20 | int g_aNewDataSize[MAX_MAPIMAGES]; |
21 | void *g_apNewData[MAX_MAPIMAGES]; |
22 | |
23 | int g_Index = 0; |
24 | int g_NextDataItemId = -1; |
25 | |
26 | int g_aImageIds[MAX_MAPIMAGES]; |
27 | |
28 | int LoadPng(CImageInfo *pImg, const char *pFilename) |
29 | { |
30 | IOHANDLE File = io_open(filename: pFilename, flags: IOFLAG_READ); |
31 | if(File) |
32 | { |
33 | io_seek(io: File, offset: 0, origin: IOSEEK_END); |
34 | long int FileSize = io_tell(io: File); |
35 | if(FileSize <= 0) |
36 | { |
37 | io_close(io: File); |
38 | dbg_msg(sys: "map_convert_07" , fmt: "failed to get file size (%ld). filename='%s'" , FileSize, pFilename); |
39 | return false; |
40 | } |
41 | io_seek(io: File, offset: 0, origin: IOSEEK_START); |
42 | TImageByteBuffer ByteBuffer; |
43 | SImageByteBuffer ImageByteBuffer(&ByteBuffer); |
44 | |
45 | ByteBuffer.resize(new_size: FileSize); |
46 | io_read(io: File, buffer: &ByteBuffer.front(), size: FileSize); |
47 | |
48 | io_close(io: File); |
49 | |
50 | uint8_t *pImgBuffer = NULL; |
51 | EImageFormat ImageFormat; |
52 | int PngliteIncompatible; |
53 | if(LoadPng(ByteLoader&: ImageByteBuffer, pFileName: pFilename, PngliteIncompatible, Width&: pImg->m_Width, Height&: pImg->m_Height, pImageBuff&: pImgBuffer, ImageFormat)) |
54 | { |
55 | pImg->m_pData = pImgBuffer; |
56 | |
57 | if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13)) |
58 | { |
59 | pImg->m_Format = CImageInfo::FORMAT_RGBA; |
60 | } |
61 | else |
62 | { |
63 | dbg_msg(sys: "map_convert_07" , fmt: "invalid image format. filename='%s'" , pFilename); |
64 | return 0; |
65 | } |
66 | } |
67 | else |
68 | return 0; |
69 | } |
70 | else |
71 | return 0; |
72 | return 1; |
73 | } |
74 | |
75 | bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename) |
76 | { |
77 | if(LayerType != MAPITEMTYPE_LAYER) |
78 | return true; |
79 | |
80 | CMapItemLayer *pImgLayer = (CMapItemLayer *)pLayerItem; |
81 | if(pImgLayer->m_Type != LAYERTYPE_TILES) |
82 | return true; |
83 | |
84 | CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pImgLayer; |
85 | if(pTMap->m_Image == -1) |
86 | return true; |
87 | |
88 | int Type; |
89 | void *pItem = g_DataReader.GetItem(Index: g_aImageIds[pTMap->m_Image], pType: &Type); |
90 | if(Type != MAPITEMTYPE_IMAGE) |
91 | return true; |
92 | |
93 | CMapItemImage *pImgItem = (CMapItemImage *)pItem; |
94 | |
95 | if(pImgItem->m_Width % 16 == 0 && pImgItem->m_Height % 16 == 0 && pImgItem->m_Width > 0 && pImgItem->m_Height > 0) |
96 | return true; |
97 | |
98 | char aTileLayerName[12]; |
99 | IntsToStr(pInts: pTMap->m_aName, NumInts: std::size(pTMap->m_aName), pStr: aTileLayerName, StrSize: std::size(aTileLayerName)); |
100 | |
101 | const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName); |
102 | 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); |
103 | return false; |
104 | } |
105 | |
106 | void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewImgItem) |
107 | { |
108 | if(!pImgItem->m_External) |
109 | return pImgItem; |
110 | |
111 | const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName); |
112 | if(pName == nullptr || pName[0] == '\0') |
113 | { |
114 | dbg_msg(sys: "map_convert_07" , fmt: "failed to load name of image %d" , Index); |
115 | return pImgItem; |
116 | } |
117 | |
118 | dbg_msg(sys: "map_convert_07" , fmt: "embedding image '%s'" , pName); |
119 | |
120 | CImageInfo ImgInfo; |
121 | char aStr[IO_MAX_PATH_LENGTH]; |
122 | str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "data/mapres/%s.png" , pName); |
123 | if(!LoadPng(pImg: &ImgInfo, pFilename: aStr)) |
124 | return pImgItem; // keep as external if we don't have a mapres to replace |
125 | |
126 | if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA) |
127 | { |
128 | dbg_msg(sys: "map_convert_07" , fmt: "image '%s' is not in RGBA format" , aStr); |
129 | return pImgItem; |
130 | } |
131 | |
132 | *pNewImgItem = *pImgItem; |
133 | |
134 | pNewImgItem->m_Width = ImgInfo.m_Width; |
135 | pNewImgItem->m_Height = ImgInfo.m_Height; |
136 | pNewImgItem->m_External = false; |
137 | pNewImgItem->m_ImageData = g_NextDataItemId++; |
138 | |
139 | g_apNewData[g_Index] = ImgInfo.m_pData; |
140 | g_aNewDataSize[g_Index] = ImgInfo.DataSize(); |
141 | g_Index++; |
142 | |
143 | return (void *)pNewImgItem; |
144 | } |
145 | |
146 | int main(int argc, const char **argv) |
147 | { |
148 | CCmdlineFix CmdlineFix(&argc, &argv); |
149 | log_set_global_logger_default(); |
150 | |
151 | if(argc < 2 || argc > 3) |
152 | { |
153 | dbg_msg(sys: "map_convert_07" , fmt: "Invalid arguments" ); |
154 | dbg_msg(sys: "map_convert_07" , fmt: "Usage: map_convert_07 <source map filepath> [<dest map filepath>]" ); |
155 | return -1; |
156 | } |
157 | |
158 | IStorage *pStorage = CreateStorage(StorageType: IStorage::STORAGETYPE_BASIC, NumArgs: argc, ppArguments: argv); |
159 | if(!pStorage) |
160 | { |
161 | dbg_msg(sys: "map_convert_07" , fmt: "error loading storage" ); |
162 | return -1; |
163 | } |
164 | |
165 | const char *pSourceFileName = argv[1]; |
166 | char aDestFileName[IO_MAX_PATH_LENGTH]; |
167 | |
168 | if(argc == 3) |
169 | { |
170 | str_copy(dst: aDestFileName, src: argv[2], dst_size: sizeof(aDestFileName)); |
171 | } |
172 | else |
173 | { |
174 | char aBuf[IO_MAX_PATH_LENGTH]; |
175 | IStorage::StripPathAndExtension(pFilename: pSourceFileName, pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
176 | str_format(buffer: aDestFileName, buffer_size: sizeof(aDestFileName), format: "data/maps7/%s.map" , aBuf); |
177 | if(fs_makedir(path: "data" ) != 0) |
178 | { |
179 | dbg_msg(sys: "map_convert_07" , fmt: "failed to create data directory" ); |
180 | return -1; |
181 | } |
182 | |
183 | if(fs_makedir(path: "data/maps7" ) != 0) |
184 | { |
185 | dbg_msg(sys: "map_convert_07" , fmt: "failed to create data/maps7 directory" ); |
186 | return -1; |
187 | } |
188 | } |
189 | |
190 | if(!g_DataReader.Open(pStorage, pFilename: pSourceFileName, StorageType: IStorage::TYPE_ABSOLUTE)) |
191 | { |
192 | dbg_msg(sys: "map_convert_07" , fmt: "failed to open source map. filename='%s'" , pSourceFileName); |
193 | return -1; |
194 | } |
195 | |
196 | if(!g_DataWriter.Open(pStorage, pFilename: aDestFileName, StorageType: IStorage::TYPE_ABSOLUTE)) |
197 | { |
198 | dbg_msg(sys: "map_convert_07" , fmt: "failed to open destination map. filename='%s'" , aDestFileName); |
199 | return -1; |
200 | } |
201 | |
202 | g_NextDataItemId = g_DataReader.NumData(); |
203 | |
204 | size_t i = 0; |
205 | for(int Index = 0; Index < g_DataReader.NumItems(); Index++) |
206 | { |
207 | int Type; |
208 | g_DataReader.GetItem(Index, pType: &Type); |
209 | if(Type == MAPITEMTYPE_IMAGE) |
210 | { |
211 | if(i >= MAX_MAPIMAGES) |
212 | { |
213 | dbg_msg(sys: "map_convert_07" , fmt: "map uses more images than the client maximum of %" PRIzu ". filename='%s'" , MAX_MAPIMAGES, pSourceFileName); |
214 | break; |
215 | } |
216 | g_aImageIds[i] = Index; |
217 | i++; |
218 | } |
219 | } |
220 | |
221 | bool Success = true; |
222 | |
223 | // add all items |
224 | for(int Index = 0; Index < g_DataReader.NumItems(); Index++) |
225 | { |
226 | int Type, Id; |
227 | CUuid Uuid; |
228 | void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid); |
229 | |
230 | // Filter ITEMTYPE_EX items, they will be automatically added again. |
231 | if(Type == ITEMTYPE_EX) |
232 | { |
233 | continue; |
234 | } |
235 | |
236 | int Size = g_DataReader.GetItemSize(Index); |
237 | Success &= CheckImageDimensions(pLayerItem: pItem, LayerType: Type, pFilename: pSourceFileName); |
238 | |
239 | CMapItemImage NewImageItem; |
240 | if(Type == MAPITEMTYPE_IMAGE) |
241 | { |
242 | pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pNewImgItem: &NewImageItem); |
243 | if(!pItem) |
244 | return -1; |
245 | Size = sizeof(CMapItemImage); |
246 | NewImageItem.m_Version = CMapItemImage::CURRENT_VERSION; |
247 | } |
248 | g_DataWriter.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid); |
249 | } |
250 | |
251 | // add all data |
252 | for(int Index = 0; Index < g_DataReader.NumData(); Index++) |
253 | { |
254 | void *pData = g_DataReader.GetData(Index); |
255 | int Size = g_DataReader.GetDataSize(Index); |
256 | g_DataWriter.AddData(Size, pData); |
257 | } |
258 | |
259 | for(int Index = 0; Index < g_Index; Index++) |
260 | { |
261 | g_DataWriter.AddData(Size: g_aNewDataSize[Index], pData: g_apNewData[Index]); |
262 | } |
263 | |
264 | g_DataReader.Close(); |
265 | g_DataWriter.Finish(); |
266 | return Success ? 0 : -1; |
267 | } |
268 | |