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 | |
10 | bool 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 | |
112 | int 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 | |