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