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
7#include <engine/gfx/image_loader.h>
8#include <engine/shared/datafile.h>
9#include <engine/storage.h>
10
11#include <game/mapitems.h>
12
13/*
14 Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>
15 Notes: map filepath must be relative to user default teeworlds folder
16 new image filepath must be absolute or relative to the current position
17*/
18
19static CDataFileReader g_DataReader;
20
21// global new image data (set by ReplaceImageItem)
22static int g_NewNameId = -1;
23static char g_aNewName[128];
24static int g_NewDataId = -1;
25static int g_NewDataSize = 0;
26static void *g_pNewData = nullptr;
27
28static void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName, const char *pImgFile, CMapItemImage *pNewImgItem)
29{
30 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
31 if(pName == nullptr || pName[0] == '\0')
32 {
33 dbg_msg(sys: "map_replace_image", fmt: "failed to load name of image %d", Index);
34 return pImgItem;
35 }
36
37 if(str_comp(a: pImgName, b: pName) != 0)
38 return pImgItem;
39
40 dbg_msg(sys: "map_replace_image", fmt: "found image '%s'", pImgName);
41
42 CImageInfo ImgInfo;
43 int PngliteIncompatible;
44 if(!CImageLoader::LoadPng(File: io_open(filename: pImgName, flags: IOFLAG_READ), pFilename: pImgName, Image&: ImgInfo, PngliteIncompatible))
45 return nullptr;
46
47 if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
48 {
49 dbg_msg(sys: "map_replace_image", fmt: "ERROR: only RGBA PNG images are supported");
50 ImgInfo.Free();
51 return nullptr;
52 }
53
54 *pNewImgItem = *pImgItem;
55
56 pNewImgItem->m_Width = ImgInfo.m_Width;
57 pNewImgItem->m_Height = ImgInfo.m_Height;
58
59 g_NewNameId = pImgItem->m_ImageName;
60 IStorage::StripPathAndExtension(pFilename: pImgFile, pBuffer: g_aNewName, BufferSize: sizeof(g_aNewName));
61 g_NewDataId = pImgItem->m_ImageData;
62 g_pNewData = ImgInfo.m_pData;
63 g_NewDataSize = ImgInfo.DataSize();
64
65 return (void *)pNewImgItem;
66}
67
68int main(int argc, const char **argv)
69{
70 CCmdlineFix CmdlineFix(&argc, &argv);
71 log_set_global_logger_default();
72
73 if(argc != 5)
74 {
75 dbg_msg(sys: "map_replace_image", fmt: "Invalid arguments");
76 dbg_msg(sys: "map_replace_image", fmt: "Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>");
77 dbg_msg(sys: "map_replace_image", fmt: "Notes: map filepath must be relative to user default teeworlds folder");
78 dbg_msg(sys: "map_replace_image", fmt: " new image filepath must be absolute or relative to the current position");
79 return -1;
80 }
81
82 std::unique_ptr<IStorage> pStorage = std::unique_ptr<IStorage>(CreateStorage(InitializationType: IStorage::EInitializationType::BASIC, NumArgs: argc, ppArguments: argv));
83 if(!pStorage)
84 {
85 log_error("map_replace_image", "Error creating basic storage");
86 return -1;
87 }
88
89 const char *pSourceFilename = argv[1];
90 const char *pDestFilename = argv[2];
91 const char *pImageName = argv[3];
92 const char *pImageFile = argv[4];
93
94 if(!g_DataReader.Open(pStorage: pStorage.get(), pFilename: pSourceFilename, StorageType: IStorage::TYPE_ALL))
95 {
96 dbg_msg(sys: "map_replace_image", fmt: "failed to open source map. filename='%s'", pSourceFilename);
97 return -1;
98 }
99
100 CDataFileWriter Writer;
101 if(!Writer.Open(pStorage: pStorage.get(), pFilename: pDestFilename))
102 {
103 dbg_msg(sys: "map_replace_image", fmt: "failed to open destination map. filename='%s'", pDestFilename);
104 return -1;
105 }
106
107 // add all items
108 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
109 {
110 int Type, Id;
111 CUuid Uuid;
112 void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
113
114 // Filter ITEMTYPE_EX items, they will be automatically added again.
115 if(Type == ITEMTYPE_EX)
116 {
117 continue;
118 }
119
120 int Size = g_DataReader.GetItemSize(Index);
121
122 CMapItemImage NewImageItem;
123 if(Type == MAPITEMTYPE_IMAGE)
124 {
125 pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pImgName: pImageName, pImgFile: pImageFile, pNewImgItem: &NewImageItem);
126 if(!pItem)
127 return -1;
128 Size = sizeof(CMapItemImage);
129 NewImageItem.m_Version = 1;
130 }
131
132 Writer.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid);
133 }
134
135 if(g_NewDataId == -1)
136 {
137 dbg_msg(sys: "map_replace_image", fmt: "image '%s' not found on source map '%s'.", pImageName, pSourceFilename);
138 return -1;
139 }
140
141 // add all data
142 for(int Index = 0; Index < g_DataReader.NumData(); Index++)
143 {
144 void *pData;
145 int Size;
146 if(Index == g_NewDataId)
147 {
148 pData = g_pNewData;
149 Size = g_NewDataSize;
150 }
151 else if(Index == g_NewNameId)
152 {
153 pData = (void *)g_aNewName;
154 Size = str_length(str: g_aNewName) + 1;
155 }
156 else
157 {
158 pData = g_DataReader.GetData(Index);
159 Size = g_DataReader.GetDataSize(Index);
160 }
161
162 Writer.AddData(Size, pData);
163 }
164
165 g_DataReader.Close();
166 Writer.Finish();
167
168 dbg_msg(sys: "map_replace_image", fmt: "image '%s' replaced", pImageName);
169 return 0;
170}
171