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 if(pItem->m_Version >= 2 && pItem->m_MustBe1 != 1)
65 {
66 log_error("map_extract", "ignoring image '%s' with unknown format %d", aBuf, pItem->m_MustBe1);
67 continue;
68 }
69
70 // copy image data
71 IOHANDLE File = io_open(filename: aBuf, flags: IOFLAG_WRITE);
72 if(File)
73 {
74 TImageByteBuffer ByteBuffer;
75 SImageByteBuffer ImageByteBuffer(&ByteBuffer);
76
77 if(SavePng(ImageFormat: IMAGE_FORMAT_RGBA, pRawBuffer: (const uint8_t *)Reader.GetData(Index: pItem->m_ImageData), WrittenBytes&: ImageByteBuffer, Width: pItem->m_Width, Height: pItem->m_Height))
78 io_write(io: File, buffer: &ByteBuffer.front(), size: ByteBuffer.size());
79 io_close(io: File);
80 }
81 }
82
83 // load sounds
84 Reader.GetType(Type: MAPITEMTYPE_SOUND, pStart: &Start, pNum: &Num);
85
86 for(int i = 0; i < Num; i++)
87 {
88 CMapItemSound *pItem = (CMapItemSound *)Reader.GetItem(Index: Start + i);
89 if(pItem->m_External)
90 continue;
91
92 const char *pName = Reader.GetDataString(Index: pItem->m_SoundName);
93 if(pName == nullptr || pName[0] == '\0')
94 {
95 dbg_msg(sys: "map_extract", fmt: "failed to load name of sound %d", i);
96 continue;
97 }
98
99 const int SoundDataSize = Reader.GetDataSize(Index: pItem->m_SoundData);
100 char aBuf[IO_MAX_PATH_LENGTH];
101 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/%s.opus", pPathSave, pName);
102 dbg_msg(sys: "map_extract", fmt: "writing sound: %s (%d B)", aBuf, SoundDataSize);
103
104 IOHANDLE Opus = io_open(filename: aBuf, flags: IOFLAG_WRITE);
105 io_write(io: Opus, buffer: Reader.GetData(Index: pItem->m_SoundData), size: SoundDataSize);
106 io_close(io: Opus);
107 }
108
109 return Reader.Close();
110}
111
112int main(int argc, const char *argv[])
113{
114 CCmdlineFix CmdlineFix(&argc, &argv);
115 log_set_global_logger_default();
116
117 IStorage *pStorage = CreateLocalStorage();
118 if(!pStorage)
119 return -1;
120
121 const char *pDir;
122 if(argc == 2)
123 {
124 pDir = ".";
125 }
126 else if(argc == 3)
127 {
128 pDir = argv[2];
129 }
130 else
131 {
132 dbg_msg(sys: "usage", fmt: "%s map [directory]", argv[0]);
133 return -1;
134 }
135
136 if(!fs_is_dir(path: pDir))
137 {
138 dbg_msg(sys: "usage", fmt: "directory '%s' does not exist", pDir);
139 return -1;
140 }
141
142 int Result = Process(pStorage, pMapName: argv[1], pPathSave: pDir) ? 0 : 1;
143 return Result;
144}
145