1// Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery
2
3#include <base/fs.h>
4#include <base/io.h>
5#include <base/logger.h>
6#include <base/os.h>
7#include <base/str.h>
8
9#include <engine/gfx/image_loader.h>
10#include <engine/shared/datafile.h>
11#include <engine/storage.h>
12
13#include <game/mapitems.h>
14
15static void PrintMapInfo(CDataFileReader &Reader)
16{
17 const CMapItemInfo *pInfo = static_cast<CMapItemInfo *>(Reader.FindItem(Type: MAPITEMTYPE_INFO, Id: 0));
18 if(pInfo)
19 {
20 const char *pAuthor = Reader.GetDataString(Index: pInfo->m_Author);
21 log_info("map_extract", "author: %s", pAuthor == nullptr ? "(error)" : pAuthor);
22 const char *pMapVersion = Reader.GetDataString(Index: pInfo->m_MapVersion);
23 log_info("map_extract", "version: %s", pMapVersion == nullptr ? "(error)" : pMapVersion);
24 const char *pCredits = Reader.GetDataString(Index: pInfo->m_Credits);
25 log_info("map_extract", "credits: %s", pCredits == nullptr ? "(error)" : pCredits);
26 const char *pLicense = Reader.GetDataString(Index: pInfo->m_License);
27 log_info("map_extract", "license: %s", pLicense == nullptr ? "(error)" : pLicense);
28 }
29}
30
31static void ExtractMapImages(CDataFileReader &Reader, const char *pPathSave)
32{
33 int Start, Num;
34 Reader.GetType(Type: MAPITEMTYPE_IMAGE, pStart: &Start, pNum: &Num);
35 for(int i = 0; i < Num; i++)
36 {
37 const CMapItemImage_v2 *pItem = static_cast<CMapItemImage_v2 *>(Reader.GetItem(Index: Start + i));
38 if(pItem->m_External)
39 continue;
40
41 const char *pName = Reader.GetDataString(Index: pItem->m_ImageName);
42 if(pName == nullptr || pName[0] == '\0')
43 {
44 log_error("map_extract", "failed to load name of image %d", i);
45 continue;
46 }
47
48 char aBuf[IO_MAX_PATH_LENGTH];
49 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/%s.png", pPathSave, pName);
50 Reader.UnloadData(Index: pItem->m_ImageName);
51
52 if(pItem->m_Version >= 2 && pItem->m_MustBe1 != 1)
53 {
54 log_error("map_extract", "ignoring image '%s' with unknown format %d", aBuf, pItem->m_MustBe1);
55 continue;
56 }
57
58 CImageInfo Image;
59 Image.m_Width = pItem->m_Width;
60 Image.m_Height = pItem->m_Height;
61 Image.m_Format = CImageInfo::FORMAT_RGBA;
62 Image.m_pData = static_cast<uint8_t *>(Reader.GetData(Index: pItem->m_ImageData));
63
64 log_info("map_extract", "writing image: %s (%dx%d)", aBuf, pItem->m_Width, pItem->m_Height);
65 if(!CImageLoader::SavePng(File: io_open(filename: aBuf, flags: IOFLAG_WRITE), pFilename: aBuf, Image))
66 {
67 log_error("map_extract", "failed to write image file. filename='%s'", aBuf);
68 }
69 Reader.UnloadData(Index: pItem->m_ImageData);
70 }
71}
72
73static void ExtractMapSounds(CDataFileReader &Reader, const char *pPathSave)
74{
75 int Start, Num;
76 Reader.GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &Num);
77 for(int i = 0; i < Num; i++)
78 {
79 const CMapItemSound *pItem = static_cast<CMapItemSound *>(Reader.GetItem(Index: Start + i));
80 if(pItem->m_External)
81 continue;
82
83 const char *pName = Reader.GetDataString(Index: pItem->m_SoundName);
84 if(pName == nullptr || pName[0] == '\0')
85 {
86 log_error("map_extract", "failed to load name of sound %d", i);
87 continue;
88 }
89
90 const int SoundDataSize = Reader.GetDataSize(Index: pItem->m_SoundData);
91 char aBuf[IO_MAX_PATH_LENGTH];
92 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/%s.opus", pPathSave, pName);
93 Reader.UnloadData(Index: pItem->m_SoundName);
94
95 IOHANDLE Opus = io_open(filename: aBuf, flags: IOFLAG_WRITE);
96 if(Opus)
97 {
98 log_info("map_extract", "writing sound: %s (%d B)", aBuf, SoundDataSize);
99 io_write(io: Opus, buffer: Reader.GetData(Index: pItem->m_SoundData), size: SoundDataSize);
100 io_close(io: Opus);
101 Reader.UnloadData(Index: pItem->m_SoundData);
102 }
103 else
104 {
105 log_error("map_extract", "failed to open sound file for writing. filename='%s'", aBuf);
106 }
107 }
108}
109
110static bool ExtractMap(IStorage *pStorage, const char *pMapName, const char *pPathSave)
111{
112 CDataFileReader Reader;
113 if(!Reader.Open(pStorage, pPath: pMapName, StorageType: IStorage::TYPE_ABSOLUTE))
114 {
115 log_error("map_extract", "error opening map '%s'", pMapName);
116 return false;
117 }
118
119 const CMapItemVersion *pVersion = static_cast<CMapItemVersion *>(Reader.FindItem(Type: MAPITEMTYPE_VERSION, Id: 0));
120 if(pVersion == nullptr || pVersion->m_Version != 1)
121 {
122 log_error("map_extract", "unsupported map version '%s'", pMapName);
123 return false;
124 }
125
126 log_info("map_extract", "Make sure you have the permission to use these images and sounds in your own maps");
127
128 PrintMapInfo(Reader);
129 ExtractMapImages(Reader, pPathSave);
130 ExtractMapSounds(Reader, pPathSave);
131
132 Reader.Close();
133 return true;
134}
135
136int main(int argc, const char *argv[])
137{
138 CCmdlineFix CmdlineFix(&argc, &argv);
139 log_set_global_logger_default();
140
141 std::unique_ptr<IStorage> pStorage = CreateLocalStorage();
142 if(!pStorage)
143 {
144 log_error("map_extract", "Error creating local storage");
145 return -1;
146 }
147
148 const char *pDir;
149 if(argc == 2)
150 {
151 pDir = ".";
152 }
153 else if(argc == 3)
154 {
155 pDir = argv[2];
156 }
157 else
158 {
159 log_error("map_extract", "usage: %s <map> [directory]", argv[0]);
160 return -1;
161 }
162
163 if(!fs_is_dir(path: pDir))
164 {
165 log_error("map_extract", "directory '%s' does not exist", pDir);
166 return -1;
167 }
168
169 return ExtractMap(pStorage: pStorage.get(), pMapName: argv[1], pPathSave: pDir) ? 0 : 1;
170}
171