12 #include <type_traits>
15 #include "../SourceS/file_util.h"
16 #include "../3rdParty/Storm/Source/storm.h"
23 #define CAN_SEEKP_BEYOND_EOF
30 template <std::
size_t A, std::
size_t B>
31 struct assert_eq : std::true_type {
32 static_assert(A == B,
"");
34 template <std::
size_t A, std::
size_t B>
35 struct assert_lte : std::true_type {
36 static_assert(A <= B,
"");
38 template <
typename T, std::
size_t S>
39 struct check_size : assert_eq<sizeof(T), S>, assert_lte<alignof(T), sizeof(T)> {
45 static_assert(check_size<_HASHENTRY, 4 * 4>::value,
"");
46 static_assert(check_size<_BLOCKENTRY, 4 * 4>::value,
"");
48 const char *DirToString(std::ios::seekdir dir)
52 return "std::ios::beg";
54 return "std::ios::end";
56 return "std::ios::cur";
62 std::string OpenModeToString(std::ios::openmode mode)
65 if ((mode & std::ios::app) != 0)
66 result.append(
"std::ios::app | ");
67 if ((mode & std::ios::ate) != 0)
68 result.append(
"std::ios::ate | ");
69 if ((mode & std::ios::binary) != 0)
70 result.append(
"std::ios::binary | ");
71 if ((mode & std::ios::in) != 0)
72 result.append(
"std::ios::in | ");
73 if ((mode & std::ios::out) != 0)
74 result.append(
"std::ios::out | ");
75 if ((mode & std::ios::trunc) != 0)
76 result.append(
"std::ios::trunc | ");
78 result.resize(result.size() - 3);
82 struct FStreamWrapper {
84 bool Open(
const char *path, std::ios::openmode mode)
86 s_.reset(
new std::fstream(path, mode));
87 return CheckError(
"new std::fstream(\"%s\", %s)", path, OpenModeToString(mode).c_str());
100 bool seekg(std::streampos pos)
103 return CheckError(
"seekg(%" PRIuMAX
")",
static_cast<std::uintmax_t
>(pos));
106 bool seekg(std::streamoff pos, std::ios::seekdir dir)
109 return CheckError(
"seekg(%" PRIdMAX
", %s)",
static_cast<std::intmax_t
>(pos), DirToString(dir));
112 bool seekp(std::streampos pos)
115 return CheckError(
"seekp(%" PRIuMAX
")",
static_cast<std::uintmax_t
>(pos));
118 bool seekp(std::streamoff pos, std::ios::seekdir dir)
121 return CheckError(
"seekp(%" PRIdMAX
", %s)",
static_cast<std::intmax_t
>(pos), DirToString(dir));
124 bool tellg(std::streampos *result)
126 *result = s_->tellg();
127 return CheckError(
"tellg() = %" PRIuMAX,
static_cast<std::uintmax_t
>(*result));
130 bool tellp(std::streampos *result)
132 *result = s_->tellp();
133 return CheckError(
"tellp() = %" PRIuMAX,
static_cast<std::uintmax_t
>(*result));
136 bool write(
const char *data, std::streamsize size)
138 s_->write(data, size);
139 return CheckError(
"write(data, %" PRIuMAX
")",
static_cast<std::uintmax_t
>(size));
142 bool read(
char *out, std::streamsize size)
145 return CheckError(
"read(out, %" PRIuMAX
")",
static_cast<std::uintmax_t
>(size));
149 template <
typename... PrintFArgs>
150 bool CheckError(
const char *fmt, PrintFArgs... args)
153 std::string fmt_with_error = fmt;
154 fmt_with_error.append(
": failed with \"%s\"");
155 const char *error_message = std::strerror(errno);
156 if (error_message ==
nullptr)
158 SDL_Log(fmt_with_error.c_str(), args..., error_message);
161 SDL_Log(fmt, args...);
167 std::unique_ptr<std::fstream> s_;
170 constexpr std::size_t kBlockEntrySize = 0x8000;
171 constexpr std::size_t kHashEntrySize = 0x8000;
172 constexpr std::ios::off_type kMpqBlockEntryOffset =
sizeof(
_FILEHEADER);
173 constexpr std::ios::off_type kMpqHashEntryOffset = kMpqBlockEntryOffset + kBlockEntrySize;
176 FStreamWrapper stream;
182 #ifndef CAN_SEEKP_BEYOND_EOF
183 std::streampos stream_begin;
189 bool Open(
const char *name)
193 SDL_Log(
"Opening %s", name);
195 exists = FileExists(name);
196 std::ios::openmode mode = std::ios::in | std::ios::out | std::ios::binary;
198 if (GetFileSize(name, &size) == 0) {
199 SDL_Log(
"GetFileSize(\"%s\") failed with \"%s\"", name, std::strerror(errno));
203 SDL_Log(
"GetFileSize(\"%s\") = %" PRIuMAX, name, size);
207 mode |= std::ios::trunc;
209 if (!stream.Open(name, mode)) {
219 bool Close(
bool clear_tables =
true)
221 if (!stream.IsOpen())
224 SDL_Log(
"Closing %s", name.c_str());
228 if (modified && !(stream.seekp(0, std::ios::beg) && WriteHeaderAndTables()))
231 if (modified && result && size != 0) {
233 SDL_Log(
"ResizeFile(\"%s\", %" PRIuMAX
")", name.c_str(), size);
235 result = ResizeFile(name.c_str(), size);
240 sgpHashTbl =
nullptr;
241 delete[] sgpBlockTbl;
242 sgpBlockTbl =
nullptr;
247 bool WriteHeaderAndTables() {
248 return WriteHeader() && WriteBlockTable() && WriteHashTable();
261 memset(&fhdr, 0,
sizeof(fhdr));
262 fhdr.
signature = SDL_SwapLE32(
'\x1AQPM');
264 fhdr.
filesize = SDL_SwapLE32(
static_cast<std::uint32_t
>(size));
265 fhdr.
version = SDL_SwapLE16(0);
267 fhdr.
hashoffset = SDL_SwapLE32(
static_cast<std::uint32_t
>(kMpqHashEntryOffset));
268 fhdr.
blockoffset = SDL_SwapLE32(
static_cast<std::uint32_t
>(kMpqBlockEntryOffset));
272 if (!stream.write(
reinterpret_cast<const char *
>(&fhdr),
sizeof(fhdr)))
277 bool WriteBlockTable()
279 Encrypt(sgpBlockTbl, kBlockEntrySize,
Hash(
"(block table)", 3));
280 const bool success = stream.write(
reinterpret_cast<const char *
>(sgpBlockTbl), kBlockEntrySize);
281 Decrypt(sgpBlockTbl, kBlockEntrySize,
Hash(
"(block table)", 3));
285 bool WriteHashTable()
287 Encrypt(sgpHashTbl, kHashEntrySize,
Hash(
"(hash table)", 3));
288 const bool success = stream.write(
reinterpret_cast<const char *
>(sgpHashTbl), kHashEntrySize);
289 Decrypt(sgpHashTbl, kHashEntrySize,
Hash(
"(hash table)", 3));
309 void InitDefaultMpqHeader(Archive *archive,
_FILEHEADER *hdr)
311 std::memset(hdr, 0,
sizeof(*hdr));
316 archive->size = kMpqHashEntryOffset + kHashEntrySize;
317 archive->modified =
true;
320 bool IsValidMPQHeader(
const Archive &archive,
_FILEHEADER *hdr)
333 bool ReadMPQHeader(Archive *archive,
_FILEHEADER *hdr)
335 const bool has_hdr = archive->size >=
sizeof(*hdr);
337 if (!archive->stream.read(
reinterpret_cast<char *
>(hdr),
sizeof(*hdr)))
341 if (!has_hdr || !IsValidMPQHeader(*archive, hdr)) {
342 InitDefaultMpqHeader(archive, hdr);
353 int hIdx, block_offset, block_size;
357 pHashTbl = &cur_archive.sgpHashTbl[hIdx];
358 blockEntry = &cur_archive.sgpBlockTbl[pHashTbl->
block];
359 pHashTbl->
block = -2;
360 block_offset = blockEntry->
offset;
362 memset(blockEntry, 0,
sizeof(*blockEntry));
364 cur_archive.modified =
true;
373 block = cur_archive.sgpBlockTbl;
378 block_offset = block->
offset;
384 if (block_offset + block_size == block->
offset) {
393 if (block_offset + block_size > cur_archive.size) {
396 if (block_offset + block_size == cur_archive.size) {
397 cur_archive.size = block_offset;
400 block->
offset = block_offset;
412 blockEntry = cur_archive.sgpBlockTbl;
439 for (idx = index & 0x7FF; cur_archive.sgpHashTbl[idx].block != -1; idx = (idx + 1) & 0x7FF) {
442 if (cur_archive.sgpHashTbl[idx].hashcheck[0] == hash_a && cur_archive.sgpHashTbl[idx].hashcheck[1] == hash_b
443 && cur_archive.sgpHashTbl[idx].lcid == locale
444 && cur_archive.sgpHashTbl[idx].block != -2)
454 char pszFileName[MAX_PATH];
457 for (i = fnGetName(0, pszFileName); i; i = fnGetName(dwIndex++, pszFileName)) {
466 cur_archive.modified =
true;
481 h1 =
Hash(pszName, 0);
482 h2 =
Hash(pszName, 1);
483 h3 =
Hash(pszName, 2);
485 app_fatal(
"Hash collision between \"%s\" and existing file\n", pszName);
489 if (cur_archive.sgpHashTbl[hIdx].block == -1 || cur_archive.sgpHashTbl[hIdx].block == -2)
491 hIdx = (hIdx + 1) & 0x7FF;
498 cur_archive.sgpHashTbl[hIdx].hashcheck[0] = h2;
499 cur_archive.sgpHashTbl[hIdx].hashcheck[1] = h3;
500 cur_archive.sgpHashTbl[hIdx].lcid = 0;
501 cur_archive.sgpHashTbl[hIdx].block = block_index;
508 const char *str_ptr = pszName;
510 while ((tmp = strchr(str_ptr,
':')))
512 while ((tmp = strchr(str_ptr,
'\\')))
516 constexpr std::uint32_t kSectorSize = 4096;
517 const std::uint32_t num_sectors = (dwLen + (kSectorSize - 1)) / kSectorSize;
518 const std::uint32_t offset_table_bytesize =
sizeof(std::uint32_t) * (num_sectors + 1);
521 pBlk->
flags = 0x80000100;
526 std::unique_ptr<std::uint32_t[]> sectoroffsettable(
new std::uint32_t[num_sectors + 1]);
528 #ifdef CAN_SEEKP_BEYOND_EOF
529 if (!cur_archive.stream.seekp(pBlk->
offset + offset_table_bytesize, std::ios::beg))
533 std::streampos stream_end;
534 if (!cur_archive.stream.seekp(0, std::ios::end) || !cur_archive.stream.tellp(&stream_end))
536 const std::uintmax_t cur_size = stream_end - cur_archive.stream_begin;
537 if (cur_size < pBlk->offset + offset_table_bytesize) {
538 if (cur_size < pBlk->offset) {
539 std::unique_ptr<char[]> filler(
new char[pBlk->
offset - cur_size]);
540 if (!cur_archive.stream.write(filler.get(), pBlk->
offset - cur_size))
543 if (!cur_archive.stream.write(
reinterpret_cast<const char *
>(sectoroffsettable.get()), offset_table_bytesize))
546 if (!cur_archive.stream.seekp(pBlk->
offset + offset_table_bytesize, std::ios::beg))
551 const BYTE *src = pbData;
552 std::uint32_t destsize = offset_table_bytesize;
553 char mpq_buf[kSectorSize];
554 std::size_t cur_sector = 0;
556 std::uint32_t len = std::min(dwLen, kSectorSize);
557 memcpy(mpq_buf, src, len);
560 if (!cur_archive.stream.write(mpq_buf, len))
562 sectoroffsettable[cur_sector++] =
SwapLE32(destsize);
564 if (dwLen > kSectorSize)
565 dwLen -= kSectorSize;
570 sectoroffsettable[num_sectors] =
SwapLE32(destsize);
571 if (!cur_archive.stream.seekp(pBlk->
offset, std::ios::beg))
573 if (!cur_archive.stream.write(
reinterpret_cast<const char *
>(sectoroffsettable.get()), offset_table_bytesize))
575 if (!cur_archive.stream.seekp(destsize - offset_table_bytesize, std::ios::cur))
578 if (destsize < pBlk->sizealloc) {
579 const std::uint32_t block_size = pBlk->
sizealloc - destsize;
580 if (block_size >= 1024) {
593 pBlockTbl = cur_archive.sgpBlockTbl;
602 result = cur_archive.size;
603 cur_archive.size += size;
608 result = pBlockTbl->
offset;
610 pBlockTbl->
offset += size;
614 memset(pBlockTbl, 0,
sizeof(*pBlockTbl));
627 hashEntry = &cur_archive.sgpHashTbl[index];
628 block = hashEntry->
block;
629 blockEntry = &cur_archive.sgpBlockTbl[block];
630 hashEntry->
block = -2;
632 cur_archive.modified =
true;
641 BOOL
OpenMPQ(
const char *pszArchive, DWORD dwChar)
643 DWORD dwFlagsAndAttributes;
649 if (!cur_archive.Open(pszArchive)) {
652 if (cur_archive.sgpBlockTbl == NULL || cur_archive.sgpHashTbl == NULL) {
653 if (!cur_archive.exists) {
654 InitDefaultMpqHeader(&cur_archive, &fhdr);
655 }
else if (!ReadMPQHeader(&cur_archive, &fhdr)) {
659 std::memset(cur_archive.sgpBlockTbl, 0, kBlockEntrySize);
661 if (!cur_archive.stream.read(
reinterpret_cast<char *
>(cur_archive.sgpBlockTbl), kBlockEntrySize))
663 key =
Hash(
"(block table)", 3);
664 Decrypt(cur_archive.sgpBlockTbl, kBlockEntrySize, key);
667 std::memset(cur_archive.sgpHashTbl, 255, kHashEntrySize);
669 if (!cur_archive.stream.read(
reinterpret_cast<char *
>(cur_archive.sgpHashTbl), kHashEntrySize))
671 key =
Hash(
"(hash table)", 3);
672 Decrypt(cur_archive.sgpHashTbl, kHashEntrySize, key);
675 #ifndef CAN_SEEKP_BEYOND_EOF
676 if (!cur_archive.stream.seekp(0, std::ios::beg))
680 if (!cur_archive.stream.tellp(&cur_archive.stream_begin))
685 if (!cur_archive.exists)
686 cur_archive.WriteHeaderAndTables();
691 cur_archive.Close(
true);
697 return cur_archive.Close(bFree);