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