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