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