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