Devilution
Diablo devolved - magic behind the 1996 computer game
mpqapi.cpp
Go to the documentation of this file.
1 
6 #include <cerrno>
7 #include <cinttypes>
8 #include <cstdint>
9 #include <cstring>
10 #include <fstream>
11 #include <memory>
12 #include <type_traits>
13 
14 #include "all.h"
15 #include "../SourceS/file_util.h"
16 #include "../3rdParty/Storm/Source/storm.h"
17 
19 
20 // Amiga cannot seekp beyond EOF.
21 // See https://github.com/bebbo/libnix/issues/30
22 #ifndef __AMIGA__
23 #define CAN_SEEKP_BEYOND_EOF
24 #endif
25 
26 namespace {
27 
28 // Validates that a Type is of a particular size and that its alignment is <= the size of the type.
29 // Done with templates so that error messages include actual size.
30 template <std::size_t A, std::size_t B>
31 struct assert_eq : std::true_type {
32  static_assert(A == B, "");
33 };
34 template <std::size_t A, std::size_t B>
35 struct assert_lte : std::true_type {
36  static_assert(A <= B, "");
37 };
38 template <typename T, std::size_t S>
39 struct check_size : assert_eq<sizeof(T), S>, assert_lte<alignof(T), sizeof(T)> {
40 };
41 
42 // Check sizes and alignments of the structs that we decrypt and encrypt.
43 // The decryption algorithm treats them as a stream of 32-bit uints, so the
44 // sizes must be exact as there cannot be any padding.
45 static_assert(check_size<_HASHENTRY, 4 * 4>::value, "");
46 static_assert(check_size<_BLOCKENTRY, 4 * 4>::value, "");
47 
48 const char *DirToString(std::ios::seekdir dir)
49 {
50  switch (dir) {
51  case std::ios::beg:
52  return "std::ios::beg";
53  case std::ios::end:
54  return "std::ios::end";
55  case std::ios::cur:
56  return "std::ios::cur";
57  default:
58  return "invalid";
59  }
60 }
61 
62 std::string OpenModeToString(std::ios::openmode mode)
63 {
64  std::string result;
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 | ");
77  if (!result.empty())
78  result.resize(result.size() - 3);
79  return result;
80 }
81 
82 struct FStreamWrapper {
83 public:
84  bool Open(const char *path, std::ios::openmode mode)
85  {
86  s_.reset(new std::fstream(path, mode));
87  return CheckError("new std::fstream(\"%s\", %s)", path, OpenModeToString(mode).c_str());
88  }
89 
90  void Close()
91  {
92  s_ = nullptr;
93  }
94 
95  bool IsOpen() const
96  {
97  return s_ != nullptr;
98  }
99 
100  bool seekg(std::streampos pos)
101  {
102  s_->seekg(pos);
103  return CheckError("seekg(%" PRIuMAX ")", static_cast<std::uintmax_t>(pos));
104  }
105 
106  bool seekg(std::streamoff pos, std::ios::seekdir dir)
107  {
108  s_->seekg(pos, dir);
109  return CheckError("seekg(%" PRIdMAX ", %s)", static_cast<std::intmax_t>(pos), DirToString(dir));
110  }
111 
112  bool seekp(std::streampos pos)
113  {
114  s_->seekp(pos);
115  return CheckError("seekp(%" PRIuMAX ")", static_cast<std::uintmax_t>(pos));
116  }
117 
118  bool seekp(std::streamoff pos, std::ios::seekdir dir)
119  {
120  s_->seekp(pos, dir);
121  return CheckError("seekp(%" PRIdMAX ", %s)", static_cast<std::intmax_t>(pos), DirToString(dir));
122  }
123 
124  bool tellg(std::streampos *result)
125  {
126  *result = s_->tellg();
127  return CheckError("tellg() = %" PRIuMAX, static_cast<std::uintmax_t>(*result));
128  }
129 
130  bool tellp(std::streampos *result)
131  {
132  *result = s_->tellp();
133  return CheckError("tellp() = %" PRIuMAX, static_cast<std::uintmax_t>(*result));
134  }
135 
136  bool write(const char *data, std::streamsize size)
137  {
138  s_->write(data, size);
139  return CheckError("write(data, %" PRIuMAX ")", static_cast<std::uintmax_t>(size));
140  }
141 
142  bool read(char *out, std::streamsize size)
143  {
144  s_->read(out, size);
145  return CheckError("read(out, %" PRIuMAX ")", static_cast<std::uintmax_t>(size));
146  }
147 
148 private:
149  template <typename... PrintFArgs>
150  bool CheckError(const char *fmt, PrintFArgs... args)
151  {
152  if (s_->fail()) {
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)
157  error_message = "";
158  SDL_Log(fmt_with_error.c_str(), args..., error_message);
159 #ifdef _DEBUG
160  } else {
161  SDL_Log(fmt, args...);
162 #endif
163  }
164  return !s_->fail();
165  }
166 
167  std::unique_ptr<std::fstream> s_;
168 };
169 
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;
174 
175 struct Archive {
176  FStreamWrapper stream;
177  std::string name;
178  std::uintmax_t size;
179  bool modified;
180  bool exists;
181 
182 #ifndef CAN_SEEKP_BEYOND_EOF
183  std::streampos stream_begin;
184 #endif
185 
186  _HASHENTRY *sgpHashTbl;
187  _BLOCKENTRY *sgpBlockTbl;
188 
189  bool Open(const char *name)
190  {
191  Close();
192 #ifdef _DEBUG
193  SDL_Log("Opening %s", name);
194 #endif
195  exists = FileExists(name);
196  std::ios::openmode mode = std::ios::in | std::ios::out | std::ios::binary;
197  if (exists) {
198  if (GetFileSize(name, &size) == 0) {
199  SDL_Log("GetFileSize(\"%s\") failed with \"%s\"", name, std::strerror(errno));
200  return false;
201 #ifdef _DEBUG
202  } else {
203  SDL_Log("GetFileSize(\"%s\") = %" PRIuMAX, name, size);
204 #endif
205  }
206  } else {
207  mode |= std::ios::trunc;
208  }
209  if (!stream.Open(name, mode)) {
210  stream.Close();
211  return false;
212  }
213  modified = !exists;
214 
215  this->name = name;
216  return true;
217  }
218 
219  bool Close(bool clear_tables = true)
220  {
221  if (!stream.IsOpen())
222  return true;
223 #ifdef _DEBUG
224  SDL_Log("Closing %s", name.c_str());
225 #endif
226 
227  bool result = true;
228  if (modified && !(stream.seekp(0, std::ios::beg) && WriteHeaderAndTables()))
229  result = false;
230  stream.Close();
231  if (modified && result && size != 0) {
232 #ifdef _DEBUG
233  SDL_Log("ResizeFile(\"%s\", %" PRIuMAX ")", name.c_str(), size);
234 #endif
235  result = ResizeFile(name.c_str(), size);
236  }
237  name.clear();
238  if (clear_tables) {
239  delete[] sgpHashTbl;
240  sgpHashTbl = nullptr;
241  delete[] sgpBlockTbl;
242  sgpBlockTbl = nullptr;
243  }
244  return result;
245  }
246 
247  bool WriteHeaderAndTables() {
248  return WriteHeader() && WriteBlockTable() && WriteHashTable();
249  }
250 
251  ~Archive()
252  {
253  Close();
254  }
255 
256 private:
257  bool WriteHeader()
258  {
259  _FILEHEADER fhdr;
260 
261  memset(&fhdr, 0, sizeof(fhdr));
262  fhdr.signature = SDL_SwapLE32('\x1AQPM');
263  fhdr.headersize = SDL_SwapLE32(32);
264  fhdr.filesize = SDL_SwapLE32(static_cast<std::uint32_t>(size));
265  fhdr.version = SDL_SwapLE16(0);
266  fhdr.sectorsizeid = SDL_SwapLE16(3);
267  fhdr.hashoffset = SDL_SwapLE32(static_cast<std::uint32_t>(kMpqHashEntryOffset));
268  fhdr.blockoffset = SDL_SwapLE32(static_cast<std::uint32_t>(kMpqBlockEntryOffset));
269  fhdr.hashcount = SDL_SwapLE32(2048);
270  fhdr.blockcount = SDL_SwapLE32(2048);
271 
272  if (!stream.write(reinterpret_cast<const char *>(&fhdr), sizeof(fhdr)))
273  return false;
274  return true;
275  }
276 
277  bool WriteBlockTable()
278  {
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));
282  return success;
283  }
284 
285  bool WriteHashTable()
286  {
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));
290  return success;
291  }
292 };
293 
294 Archive cur_archive;
295 
296 void ByteSwapHdr(_FILEHEADER *hdr)
297 {
298  hdr->signature = SDL_SwapLE32(hdr->signature);
299  hdr->headersize = SDL_SwapLE32(hdr->headersize);
300  hdr->filesize = SDL_SwapLE32(hdr->filesize);
301  hdr->version = SDL_SwapLE16(hdr->version);
302  hdr->sectorsizeid = SDL_SwapLE16(hdr->sectorsizeid);
303  hdr->hashoffset = SDL_SwapLE32(hdr->hashoffset);
304  hdr->blockoffset = SDL_SwapLE32(hdr->blockoffset);
305  hdr->hashcount = SDL_SwapLE32(hdr->hashcount);
306  hdr->blockcount = SDL_SwapLE32(hdr->blockcount);
307 }
308 
309 void InitDefaultMpqHeader(Archive *archive, _FILEHEADER *hdr)
310 {
311  std::memset(hdr, 0, sizeof(*hdr));
312  hdr->signature = '\x1AQPM';
313  hdr->headersize = 32;
314  hdr->sectorsizeid = 3;
315  hdr->version = 0;
316  archive->size = kMpqHashEntryOffset + kHashEntrySize;
317  archive->modified = true;
318 }
319 
320 bool IsValidMPQHeader(const Archive &archive, _FILEHEADER *hdr)
321 {
322  return hdr->signature == '\x1AQPM'
323  && hdr->headersize == 32
324  && hdr->version <= 0
325  && hdr->sectorsizeid == 3
326  && hdr->filesize == archive.size
327  && hdr->hashoffset == kMpqHashEntryOffset
328  && hdr->blockoffset == sizeof(_FILEHEADER)
329  && hdr->hashcount == 2048
330  && hdr->blockcount == 2048;
331 }
332 
333 bool ReadMPQHeader(Archive *archive, _FILEHEADER *hdr)
334 {
335  const bool has_hdr = archive->size >= sizeof(*hdr);
336  if (has_hdr) {
337  if (!archive->stream.read(reinterpret_cast<char *>(hdr), sizeof(*hdr)))
338  return false;
339  ByteSwapHdr(hdr);
340  }
341  if (!has_hdr || !IsValidMPQHeader(*archive, hdr)) {
342  InitDefaultMpqHeader(archive, hdr);
343  }
344  return true;
345 }
346 
347 } // namespace
348 
349 void mpqapi_remove_hash_entry(const char *pszName)
350 {
351  _HASHENTRY *pHashTbl;
352  _BLOCKENTRY *blockEntry;
353  int hIdx, block_offset, block_size;
354 
355  hIdx = FetchHandle(pszName);
356  if (hIdx != -1) {
357  pHashTbl = &cur_archive.sgpHashTbl[hIdx];
358  blockEntry = &cur_archive.sgpBlockTbl[pHashTbl->block];
359  pHashTbl->block = -2;
360  block_offset = blockEntry->offset;
361  block_size = blockEntry->sizealloc;
362  memset(blockEntry, 0, sizeof(*blockEntry));
363  mpqapi_alloc_block(block_offset, block_size);
364  cur_archive.modified = true;
365  }
366 }
367 
368 void mpqapi_alloc_block(uint32_t block_offset, uint32_t block_size)
369 {
370  _BLOCKENTRY *block;
371  int i;
372 
373  block = cur_archive.sgpBlockTbl;
374  i = 2048;
375  while (i-- != 0) {
376  if (block->offset && !block->flags && !block->sizefile) {
377  if (block->offset + block->sizealloc == block_offset) {
378  block_offset = block->offset;
379  block_size += block->sizealloc;
380  memset(block, 0, sizeof(_BLOCKENTRY));
381  mpqapi_alloc_block(block_offset, block_size);
382  return;
383  }
384  if (block_offset + block_size == block->offset) {
385  block_size += block->sizealloc;
386  memset(block, 0, sizeof(_BLOCKENTRY));
387  mpqapi_alloc_block(block_offset, block_size);
388  return;
389  }
390  }
391  block++;
392  }
393  if (block_offset + block_size > cur_archive.size) {
394  app_fatal("MPQ free list error");
395  }
396  if (block_offset + block_size == cur_archive.size) {
397  cur_archive.size = block_offset;
398  } else {
399  block = mpqapi_new_block(NULL);
400  block->offset = block_offset;
401  block->sizealloc = block_size;
402  block->sizefile = 0;
403  block->flags = 0;
404  }
405 }
406 
407 _BLOCKENTRY *mpqapi_new_block(int *block_index)
408 {
409  _BLOCKENTRY *blockEntry;
410  DWORD i;
411 
412  blockEntry = cur_archive.sgpBlockTbl;
413 
414  i = 0;
415  while (blockEntry->offset || blockEntry->sizealloc || blockEntry->flags || blockEntry->sizefile) {
416  i++;
417  blockEntry++;
418  if (i >= 2048) {
419  app_fatal("Out of free block entries");
420  return NULL;
421  }
422  }
423  if (block_index)
424  *block_index = i;
425 
426  return blockEntry;
427 }
428 
429 int FetchHandle(const char *pszName)
430 {
431  return mpqapi_get_hash_index(Hash(pszName, 0), Hash(pszName, 1), Hash(pszName, 2), 0);
432 }
433 
434 int mpqapi_get_hash_index(short index, int hash_a, int hash_b, int locale)
435 {
436  int idx, i;
437 
438  i = 2048;
439  for (idx = index & 0x7FF; cur_archive.sgpHashTbl[idx].block != -1; idx = (idx + 1) & 0x7FF) {
440  if (!i--)
441  break;
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)
445  return idx;
446  }
447 
448  return -1;
449 }
450 
451 void mpqapi_remove_hash_entries(BOOL (*fnGetName)(DWORD, char *))
452 {
453  DWORD dwIndex, i;
454  char pszFileName[MAX_PATH];
455 
456  dwIndex = 1;
457  for (i = fnGetName(0, pszFileName); i; i = fnGetName(dwIndex++, pszFileName)) {
458  mpqapi_remove_hash_entry(pszFileName);
459  }
460 }
461 
462 BOOL mpqapi_write_file(const char *pszName, const BYTE *pbData, DWORD dwLen)
463 {
464  _BLOCKENTRY *blockEntry;
465 
466  cur_archive.modified = true;
467  mpqapi_remove_hash_entry(pszName);
468  blockEntry = mpqapi_add_file(pszName, 0, 0);
469  if (!mpqapi_write_file_contents(pszName, pbData, dwLen, blockEntry)) {
470  mpqapi_remove_hash_entry(pszName);
471  return FALSE;
472  }
473  return TRUE;
474 }
475 
476 _BLOCKENTRY *mpqapi_add_file(const char *pszName, _BLOCKENTRY *pBlk, int block_index)
477 {
478  DWORD h1, h2, h3;
479  int i, hIdx;
480 
481  h1 = Hash(pszName, 0);
482  h2 = Hash(pszName, 1);
483  h3 = Hash(pszName, 2);
484  if (mpqapi_get_hash_index(h1, h2, h3, 0) != -1)
485  app_fatal("Hash collision between \"%s\" and existing file\n", pszName);
486  hIdx = h1 & 0x7FF;
487  i = 2048;
488  while (i--) {
489  if (cur_archive.sgpHashTbl[hIdx].block == -1 || cur_archive.sgpHashTbl[hIdx].block == -2)
490  break;
491  hIdx = (hIdx + 1) & 0x7FF;
492  }
493  if (i < 0)
494  app_fatal("Out of hash space");
495  if (!pBlk)
496  pBlk = mpqapi_new_block(&block_index);
497 
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;
502 
503  return pBlk;
504 }
505 
506 BOOL mpqapi_write_file_contents(const char *pszName, const BYTE *pbData, DWORD dwLen, _BLOCKENTRY *pBlk)
507 {
508  const char *str_ptr = pszName;
509  const char *tmp;
510  while ((tmp = strchr(str_ptr, ':')))
511  str_ptr = tmp + 1;
512  while ((tmp = strchr(str_ptr, '\\')))
513  str_ptr = tmp + 1;
514  Hash(str_ptr, 3);
515 
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);
519  pBlk->offset = mpqapi_find_free_block(dwLen + offset_table_bytesize, &pBlk->sizealloc);
520  pBlk->sizefile = dwLen;
521  pBlk->flags = 0x80000100;
522 
523  // We populate the table of sector offset while we write the data.
524  // We can't pre-populate it because we don't know the compressed sector sizes yet.
525  // First offset is the start of the first sector, last offset is the end of the last sector.
526  std::unique_ptr<std::uint32_t[]> sectoroffsettable(new std::uint32_t[num_sectors + 1]);
527 
528 #ifdef CAN_SEEKP_BEYOND_EOF
529  if (!cur_archive.stream.seekp(pBlk->offset + offset_table_bytesize, std::ios::beg))
530  return FALSE;
531 #else
532  // Ensure we do not seekp beyond EOF by filling the missing space.
533  std::streampos stream_end;
534  if (!cur_archive.stream.seekp(0, std::ios::end) || !cur_archive.stream.tellp(&stream_end))
535  return FALSE;
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))
541  return FALSE;
542  }
543  if (!cur_archive.stream.write(reinterpret_cast<const char *>(sectoroffsettable.get()), offset_table_bytesize))
544  return FALSE;
545  } else {
546  if (!cur_archive.stream.seekp(pBlk->offset + offset_table_bytesize, std::ios::beg))
547  return FALSE;
548  }
549 #endif
550 
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;
555  while (true) {
556  std::uint32_t len = std::min(dwLen, kSectorSize);
557  memcpy(mpq_buf, src, len);
558  src += len;
559  len = PkwareCompress(mpq_buf, len);
560  if (!cur_archive.stream.write(mpq_buf, len))
561  return FALSE;
562  sectoroffsettable[cur_sector++] = SwapLE32(destsize);
563  destsize += len; // compressed length
564  if (dwLen > kSectorSize)
565  dwLen -= kSectorSize;
566  else
567  break;
568  }
569 
570  sectoroffsettable[num_sectors] = SwapLE32(destsize);
571  if (!cur_archive.stream.seekp(pBlk->offset, std::ios::beg))
572  return FALSE;
573  if (!cur_archive.stream.write(reinterpret_cast<const char *>(sectoroffsettable.get()), offset_table_bytesize))
574  return FALSE;
575  if (!cur_archive.stream.seekp(destsize - offset_table_bytesize, std::ios::cur))
576  return FALSE;
577 
578  if (destsize < pBlk->sizealloc) {
579  const std::uint32_t block_size = pBlk->sizealloc - destsize;
580  if (block_size >= 1024) {
581  pBlk->sizealloc = destsize;
582  mpqapi_alloc_block(pBlk->sizealloc + pBlk->offset, block_size);
583  }
584  }
585  return TRUE;
586 }
587 
588 int mpqapi_find_free_block(uint32_t size, uint32_t *block_size)
589 {
590  _BLOCKENTRY *pBlockTbl;
591  int i, result;
592 
593  pBlockTbl = cur_archive.sgpBlockTbl;
594  i = 2048;
595  while (1) {
596  i--;
597  if (pBlockTbl->offset && !pBlockTbl->flags && !pBlockTbl->sizefile && (DWORD)pBlockTbl->sizealloc >= size)
598  break;
599  pBlockTbl++;
600  if (!i) {
601  *block_size = size;
602  result = cur_archive.size;
603  cur_archive.size += size;
604  return result;
605  }
606  }
607 
608  result = pBlockTbl->offset;
609  *block_size = size;
610  pBlockTbl->offset += size;
611  pBlockTbl->sizealloc -= size;
612 
613  if (!pBlockTbl->sizealloc)
614  memset(pBlockTbl, 0, sizeof(*pBlockTbl));
615 
616  return result;
617 }
618 
619 void mpqapi_rename(char *pszOld, char *pszNew)
620 {
621  int index, block;
622  _HASHENTRY *hashEntry;
623  _BLOCKENTRY *blockEntry;
624 
625  index = FetchHandle(pszOld);
626  if (index != -1) {
627  hashEntry = &cur_archive.sgpHashTbl[index];
628  block = hashEntry->block;
629  blockEntry = &cur_archive.sgpBlockTbl[block];
630  hashEntry->block = -2;
631  mpqapi_add_file(pszNew, blockEntry, block);
632  cur_archive.modified = true;
633  }
634 }
635 
636 BOOL mpqapi_has_file(const char *pszName)
637 {
638  return FetchHandle(pszName) != -1;
639 }
640 
641 BOOL OpenMPQ(const char *pszArchive, DWORD dwChar)
642 {
643  DWORD dwFlagsAndAttributes;
644  DWORD key;
645  _FILEHEADER fhdr;
646 
647  InitHash();
648 
649  if (!cur_archive.Open(pszArchive)) {
650  return FALSE;
651  }
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)) {
656  goto on_error;
657  }
658  cur_archive.sgpBlockTbl = new _BLOCKENTRY[kBlockEntrySize / sizeof(_BLOCKENTRY)];
659  std::memset(cur_archive.sgpBlockTbl, 0, kBlockEntrySize);
660  if (fhdr.blockcount) {
661  if (!cur_archive.stream.read(reinterpret_cast<char *>(cur_archive.sgpBlockTbl), kBlockEntrySize))
662  goto on_error;
663  key = Hash("(block table)", 3);
664  Decrypt(cur_archive.sgpBlockTbl, kBlockEntrySize, key);
665  }
666  cur_archive.sgpHashTbl = new _HASHENTRY[kHashEntrySize / sizeof(_HASHENTRY)];
667  std::memset(cur_archive.sgpHashTbl, 255, kHashEntrySize);
668  if (fhdr.hashcount) {
669  if (!cur_archive.stream.read(reinterpret_cast<char *>(cur_archive.sgpHashTbl), kHashEntrySize))
670  goto on_error;
671  key = Hash("(hash table)", 3);
672  Decrypt(cur_archive.sgpHashTbl, kHashEntrySize, key);
673  }
674 
675 #ifndef CAN_SEEKP_BEYOND_EOF
676  if (!cur_archive.stream.seekp(0, std::ios::beg))
677  goto on_error;
678 
679  // Memorize stream begin, we'll need it for calculations later.
680  if (!cur_archive.stream.tellp(&cur_archive.stream_begin))
681  goto on_error;
682 
683  // Write garbage header and tables because some platforms cannot `seekp` beyond EOF.
684  // The data is incorrect at this point, it will be overwritten on Close.
685  if (!cur_archive.exists)
686  cur_archive.WriteHeaderAndTables();
687 #endif
688  }
689  return TRUE;
690 on_error:
691  cur_archive.Close(/*clear_tables=*/true);
692  return FALSE;
693 }
694 
695 BOOL mpqapi_flush_and_close(const char *pszArchive, BOOL bFree, DWORD dwChar)
696 {
697  return cur_archive.Close(/*clear_tables=*/bFree);
698 }
699 
_FILEHEADER::blockcount
int blockcount
Definition: structs.h:1481
_FILEHEADER
Definition: structs.h:1472
_FILEHEADER::version
WORD version
Definition: structs.h:1476
_BLOCKENTRY::flags
uint32_t flags
Definition: structs.h:1495
mpqapi_flush_and_close
BOOL mpqapi_flush_and_close(const char *pszArchive, BOOL bFree, DWORD dwChar)
Definition: mpqapi.cpp:695
_FILEHEADER::hashoffset
int hashoffset
Definition: structs.h:1478
_HASHENTRY
struct _HASHENTRY _HASHENTRY
_FILEHEADER::hashcount
int hashcount
Definition: structs.h:1480
_BLOCKENTRY
struct _BLOCKENTRY _BLOCKENTRY
_HASHENTRY
Definition: structs.h:1485
Decrypt
void Decrypt(void *block, DWORD size, DWORD key)
Definition: encrypt.cpp:13
mpqapi_write_file_contents
BOOL mpqapi_write_file_contents(const char *pszName, const BYTE *pbData, DWORD dwLen, _BLOCKENTRY *pBlk)
Definition: mpqapi.cpp:506
all.h
_BLOCKENTRY::sizealloc
uint32_t sizealloc
Definition: structs.h:1493
mpqapi_get_hash_index
int mpqapi_get_hash_index(short index, int hash_a, int hash_b, int locale)
Definition: mpqapi.cpp:434
_FILEHEADER::headersize
int headersize
Definition: structs.h:1474
_BLOCKENTRY::offset
uint32_t offset
Definition: structs.h:1492
_FILEHEADER::filesize
int filesize
Definition: structs.h:1475
_FILEHEADER
struct _FILEHEADER _FILEHEADER
mpqapi_remove_hash_entries
void mpqapi_remove_hash_entries(BOOL(*fnGetName)(DWORD, char *))
Definition: mpqapi.cpp:451
app_fatal
void app_fatal(const char *pszFmt,...)
Definition: appfat.cpp:18
FetchHandle
int FetchHandle(const char *pszName)
Definition: mpqapi.cpp:429
DEVILUTION_END_NAMESPACE
#define DEVILUTION_END_NAMESPACE
Definition: types.h:10
OpenMPQ
BOOL OpenMPQ(const char *pszArchive, DWORD dwChar)
Definition: mpqapi.cpp:641
mpqapi_write_file
BOOL mpqapi_write_file(const char *pszName, const BYTE *pbData, DWORD dwLen)
Definition: mpqapi.cpp:462
InitHash
void InitHash()
Definition: encrypt.cpp:65
_HASHENTRY::block
uint32_t block
Definition: structs.h:1488
mpqapi_has_file
BOOL mpqapi_has_file(const char *pszName)
Definition: mpqapi.cpp:636
mpqapi_remove_hash_entry
void mpqapi_remove_hash_entry(const char *pszName)
Definition: mpqapi.cpp:349
mpqapi_new_block
_BLOCKENTRY * mpqapi_new_block(int *block_index)
Definition: mpqapi.cpp:407
mpqapi_alloc_block
void mpqapi_alloc_block(uint32_t block_offset, uint32_t block_size)
Definition: mpqapi.cpp:368
_FILEHEADER::signature
int signature
Definition: structs.h:1473
Hash
DWORD Hash(const char *s, int type)
Definition: encrypt.cpp:49
SwapLE32
#define SwapLE32
Definition: defs.h:182
_BLOCKENTRY
Definition: structs.h:1491
mpqapi_rename
void mpqapi_rename(char *pszOld, char *pszNew)
Definition: mpqapi.cpp:619
DEVILUTION_BEGIN_NAMESPACE
Definition: sha.cpp:10
PkwareCompress
int PkwareCompress(void *buf, int size)
Definition: encrypt.cpp:82
_FILEHEADER::sectorsizeid
short sectorsizeid
Definition: structs.h:1477
mpqapi_add_file
_BLOCKENTRY * mpqapi_add_file(const char *pszName, _BLOCKENTRY *pBlk, int block_index)
Definition: mpqapi.cpp:476
mpqapi_find_free_block
int mpqapi_find_free_block(uint32_t size, uint32_t *block_size)
Definition: mpqapi.cpp:588
_BLOCKENTRY::sizefile
uint32_t sizefile
Definition: structs.h:1494
_FILEHEADER::blockoffset
int blockoffset
Definition: structs.h:1479
Encrypt
void Encrypt(void *block, DWORD size, DWORD key)
Definition: encrypt.cpp:31