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 | |
17 | CDataFileReader g_DataReader; |
18 | |
19 | // global new image data (set by ReplaceImageItem) |
20 | int g_NewNameId = -1; |
21 | char g_aNewName[128]; |
22 | int g_NewDataId = -1; |
23 | int g_NewDataSize = 0; |
24 | void *g_pNewData = nullptr; |
25 | |
26 | bool 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 | |
75 | void *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 | |
113 | int 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 | |