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/mapitems.h>
11/*
12 Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>
13 Notes: map filepath must be relative to user default teeworlds folder
14 new image filepath must be absolute or relative to the current position
15*/
16
17CDataFileReader g_DataReader;
18
19// global new image data (set by ReplaceImageItem)
20int g_NewNameId = -1;
21char g_aNewName[128];
22int g_NewDataId = -1;
23int g_NewDataSize = 0;
24void *g_pNewData = nullptr;
25
26bool LoadPng(CImageInfo *pImg, const char *pFilename)
27{
28 IOHANDLE File = io_open(filename: pFilename, flags: IOFLAG_READ);
29 if(File)
30 {
31 io_seek(io: File, offset: 0, origin: IOSEEK_END);
32 long int FileSize = io_tell(io: File);
33 if(FileSize <= 0)
34 {
35 io_close(io: File);
36 return false;
37 }
38 io_seek(io: File, offset: 0, origin: IOSEEK_START);
39 TImageByteBuffer ByteBuffer;
40 SImageByteBuffer ImageByteBuffer(&ByteBuffer);
41
42 ByteBuffer.resize(new_size: FileSize);
43 io_read(io: File, buffer: &ByteBuffer.front(), size: FileSize);
44
45 io_close(io: File);
46
47 uint8_t *pImgBuffer = NULL;
48 EImageFormat ImageFormat;
49 int PngliteIncompatible;
50 if(LoadPng(ByteLoader&: ImageByteBuffer, pFileName: pFilename, PngliteIncompatible, Width&: pImg->m_Width, Height&: pImg->m_Height, pImageBuff&: pImgBuffer, ImageFormat))
51 {
52 if((ImageFormat == IMAGE_FORMAT_RGBA || ImageFormat == IMAGE_FORMAT_RGB) && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
53 {
54 pImg->m_pData = pImgBuffer;
55
56 if(ImageFormat == IMAGE_FORMAT_RGB)
57 pImg->m_Format = CImageInfo::FORMAT_RGB;
58 else if(ImageFormat == IMAGE_FORMAT_RGBA)
59 pImg->m_Format = CImageInfo::FORMAT_RGBA;
60 else
61 {
62 free(ptr: pImgBuffer);
63 return false;
64 }
65 }
66 }
67 else
68 return false;
69 }
70 else
71 return false;
72 return true;
73}
74
75void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName, const char *pImgFile, CMapItemImage *pNewImgItem)
76{
77 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
78 if(pName == nullptr || pName[0] == '\0')
79 {
80 dbg_msg(sys: "map_replace_image", fmt: "failed to load name of image %d", Index);
81 return pImgItem;
82 }
83
84 if(str_comp(a: pImgName, b: pName) != 0)
85 return pImgItem;
86
87 dbg_msg(sys: "map_replace_image", fmt: "found image '%s'", pImgName);
88
89 CImageInfo ImgInfo;
90 if(!LoadPng(pImg: &ImgInfo, pFilename: pImgFile))
91 return 0;
92
93 if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
94 {
95 dbg_msg(sys: "map_replace_image", fmt: "image '%s' is not in RGBA format", pImgName);
96 return 0;
97 }
98
99 *pNewImgItem = *pImgItem;
100
101 pNewImgItem->m_Width = ImgInfo.m_Width;
102 pNewImgItem->m_Height = ImgInfo.m_Height;
103
104 g_NewNameId = pImgItem->m_ImageName;
105 IStorage::StripPathAndExtension(pFilename: pImgFile, pBuffer: g_aNewName, BufferSize: sizeof(g_aNewName));
106 g_NewDataId = pImgItem->m_ImageData;
107 g_pNewData = ImgInfo.m_pData;
108 g_NewDataSize = ImgInfo.DataSize();
109
110 return (void *)pNewImgItem;
111}
112
113int main(int argc, const char **argv)
114{
115 CCmdlineFix CmdlineFix(&argc, &argv);
116 log_set_global_logger_default();
117
118 if(argc != 5)
119 {
120 dbg_msg(sys: "map_replace_image", fmt: "Invalid arguments");
121 dbg_msg(sys: "map_replace_image", fmt: "Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>");
122 dbg_msg(sys: "map_replace_image", fmt: "Notes: map filepath must be relative to user default teeworlds folder");
123 dbg_msg(sys: "map_replace_image", fmt: " new image filepath must be absolute or relative to the current position");
124 return -1;
125 }
126
127 IStorage *pStorage = CreateStorage(StorageType: IStorage::STORAGETYPE_BASIC, NumArgs: argc, ppArguments: argv);
128 if(!pStorage)
129 {
130 dbg_msg(sys: "map_replace_image", fmt: "error loading storage");
131 return -1;
132 }
133
134 const char *pSourceFileName = argv[1];
135 const char *pDestFileName = argv[2];
136 const char *pImageName = argv[3];
137 const char *pImageFile = argv[4];
138
139 if(!g_DataReader.Open(pStorage, pFilename: pSourceFileName, StorageType: IStorage::TYPE_ALL))
140 {
141 dbg_msg(sys: "map_replace_image", fmt: "failed to open source map. filename='%s'", pSourceFileName);
142 return -1;
143 }
144
145 CDataFileWriter Writer;
146 if(!Writer.Open(pStorage, pFilename: pDestFileName))
147 {
148 dbg_msg(sys: "map_replace_image", fmt: "failed to open destination map. filename='%s'", pDestFileName);
149 return -1;
150 }
151
152 // add all items
153 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
154 {
155 int Type, Id;
156 CUuid Uuid;
157 void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
158
159 // Filter ITEMTYPE_EX items, they will be automatically added again.
160 if(Type == ITEMTYPE_EX)
161 {
162 continue;
163 }
164
165 int Size = g_DataReader.GetItemSize(Index);
166
167 CMapItemImage NewImageItem;
168 if(Type == MAPITEMTYPE_IMAGE)
169 {
170 pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pImgName: pImageName, pImgFile: pImageFile, pNewImgItem: &NewImageItem);
171 if(!pItem)
172 return -1;
173 Size = sizeof(CMapItemImage);
174 NewImageItem.m_Version = CMapItemImage::CURRENT_VERSION;
175 }
176
177 Writer.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid);
178 }
179
180 if(g_NewDataId == -1)
181 {
182 dbg_msg(sys: "map_replace_image", fmt: "image '%s' not found on source map '%s'.", pImageName, pSourceFileName);
183 return -1;
184 }
185
186 // add all data
187 for(int Index = 0; Index < g_DataReader.NumData(); Index++)
188 {
189 void *pData;
190 int Size;
191 if(Index == g_NewDataId)
192 {
193 pData = g_pNewData;
194 Size = g_NewDataSize;
195 }
196 else if(Index == g_NewNameId)
197 {
198 pData = (void *)g_aNewName;
199 Size = str_length(str: g_aNewName) + 1;
200 }
201 else
202 {
203 pData = g_DataReader.GetData(Index);
204 Size = g_DataReader.GetDataSize(Index);
205 }
206
207 Writer.AddData(Size, pData);
208 }
209
210 g_DataReader.Close();
211 Writer.Finish();
212
213 dbg_msg(sys: "map_replace_image", fmt: "image '%s' replaced", pImageName);
214 return 0;
215}
216