Commits

Anonymous committed e555782

At last, something that begins to read sc2 replays ^^

Comments (0)

Files changed (6)

+^.*\.pyc
+# -*- coding: utf-8 -*-
+
+from definitions import *
+from decryptor import *
+
+class Archive(object):
+    def __init__(self, fname):
+        self.fname = fname       
+        self.hashtable = []
+        self.blocktable = []
+        self.archive_offset = 0
+
+        self.fp = None
+
+        self.__load_from_file(fname)
+
+    def __del__(self):
+        if self.fp is not None:
+            self.fp.close()
+
+    def __load_table(self, dest, buffer, typ, count):
+        table = getattr(self, dest)
+        start = 0
+        for nb in xrange(0, count):
+            entry = typ()
+            end = start + len(entry)
+            entry.load_from_buffer(buffer[start:end])
+            table.append(entry)
+            start += len(entry)
+
+    def __load_from_file(self, fname):
+        self.fp = open(fname, "rb")
+        m = Magic()
+        m.load_from_file(self.fp)
+        if m.type.hex() == Magic.shunt:
+            self.userdata = UserData()
+            self.userdata.load_from_file(self.fp)
+            self.archive_offset += self.userdata.header_offset.int()
+            self.fp.seek(self.archive_offset)
+        m.load_from_file(self.fp)
+        if m.type.hex() == Magic.header:
+            self.header = Header()
+            self.header.load_from_file(self.fp)
+            if self.header.format_version.hex() == Header.bcformat:
+                self.extra_header = ExtraHeader()
+                self.extra_header.load_from_file(self.fp)
+
+        # A refaire pour tester la validité du header
+        self.d = Decryptor()
+
+        hasht_size = self.header.hashtable_entries.int() * len(HashTableEntry())
+        self.fp.seek(self.header.hashtable_offset.int() + self.archive_offset)
+        key = self.d.hashstring("(hash table)", MPQ_HASH_FILE_KEY)
+        buffer = self.d.decrypt(self.fp, hasht_size, key)
+        self.__load_table("hashtable", buffer, HashTableEntry, 
+                          self.header.hashtable_entries.int())
+
+        blockt_size = self.header.blocktable_entries.int() * len(BlockTableEntry())
+        self.fp.seek(self.header.blocktable_offset.int() + self.archive_offset)
+        key = self.d.hashstring("(block table)", MPQ_HASH_FILE_KEY)
+        buffer = self.d.decrypt(self.fp, blockt_size, key)
+        self.__load_table("blocktable", buffer, BlockTableEntry, 
+                          self.header.blocktable_entries.int())
+            
+
+    def findfile(self, fname):
+        key = self.d.hashstring(fname, MPQ_HASH_TABLE_OFFSET) \
+            % len(self.hashtable)
+        hash1 = self.d.hashstring(fname, MPQ_HASH_NAME_A)
+        hash2 = self.d.hashstring(fname, MPQ_HASH_NAME_B)
+        it = key
+        while True:
+            e = self.hashtable[it]
+            if not e.isempty() and not e.isdeleted():
+                if hash1 == e.fpath_hash_1.hex() and hash2 == e.fpath_hash_2.hex():
+                    return e
+            it = (it + 1) % len(self.hashtable)
+            if it == key:
+                break
+        return None
+
+    def listfiles(self):
+        f = self.findfile("(listfile)")
+        if f is None:
+            return None
+        blk = self.blocktable[f.block_index.int()]
+        self.fp.seek(self.archive_offset + blk.file_offset.int())
+        if blk.iscompressed():
+            ct = self.fp.read(1)
+            content = self.fp.read(blk.compressed_size.int() - 1)
+            if ct == "\x10":
+                import bz2
+                print bz2.decompress(content)
 
 from array import array
 
+MPQ_HASH_TABLE_OFFSET   = 0x000
+MPQ_HASH_NAME_A         = 0x100
+MPQ_HASH_NAME_B         = 0x200
+MPQ_HASH_FILE_KEY       = 0x300
+
 def u32(x):
     return x & 0xFFFFFFFFL
 
 
     def decrypt(self, fp, length, key):
         a = array("I")
-        a.fromfile(fp, length / a.itemsize)
+        buffer = fp.read(length)
+        a.fromstring(buffer)
+        fp = open("output", "wb")
+        fp.write(buffer)
+        fp.close()
         ret = array("I")
         seed = 0xEEEEEEEEL
         for cpt in xrange(0, len(a)):
         seed2 = 0xEEEEEEEEL
         for i in xrange(0, len(src)):
             ch = ord(src[i].upper())
-            index = (hashtype << 8) + ch
-            seed1 = self._table[(hashtype << 8) + ch] ^ (u32_add(seed1, seed2))
+            seed1 = self._table[hashtype + ch] ^ (u32_add(seed1, seed2))
             seed2 = u32(ch + u32_add(seed1, seed2) + u32_ls(seed2, 5) + 3)
         return seed1
 
+# -*- coding: utf-8 -*-
+
+import struct
+
+class Type(object):
+    _fmt = None
+
+    def __init__(self, count=1, value=None):
+        self.value = value
+        self.count = count
+        self.fmt = self.count > 1 and "%d%s" % (self.count, self._fmt) \
+            or self._fmt
+
+    def __len__(self):
+        return struct.calcsize(self.fmt)
+
+    def hex(self):
+        if isinstance(self.value, str):
+            return int(self.value.encode('hex'), 16)
+        return self.value
+
+    def int(self):
+        return int(self.value)
+
+class Integer(Type):
+    _fmt = "I"
+
+class Integer64(Type):
+    _fmt = "Q"
+
+class Char(Type):
+    _fmt = "s"
+
+class Short(Type):
+    _fmt = "H"
+
+class Byte(Type):
+    _fmt = "B"
+
+class Struct(object):
+    def __init__(self, **kwargs):
+        for fdef in self.fields:
+            args = fdef[2:]
+            newattr = fdef[1](*args)
+            setattr(self, fdef[0], newattr)
+
+        self.__compute_format()
+
+    def pack(self, **kwargs):
+        return struct.pack(self._fmt, **kwargs)
+
+    def load_from_buffer(self, buffer):
+        ret = struct.unpack_from(self.__fmt, buffer)
+        cpt = 0
+        for fdef in self.fields:
+            getattr(self, fdef[0]).value = ret[cpt]
+            cpt += 1
+
+    def load_from_file(self, fp):
+        buffer = fp.read(len(self))
+        self.load_from_buffer(buffer)
+
+    def __compute_format(self):
+        self.__fmt = ""
+        for fdef in self.fields:
+            self.__fmt += getattr(self, fdef[0]).fmt
+
+    def __len__(self):
+        res = 0
+        for fdef in self.fields:
+            res += len(getattr(self, fdef[0]))
+        return res
+
+    def __repr__(self):
+        res = "%s :\n" % self.__class__.__name__
+        for fdef in self.fields:
+            attr = getattr(self, fdef[0])
+            value = ""
+            for c in [Short, Integer, Integer64]:
+                if isinstance(attr, c):
+                    value = "%d" % attr.int()
+            if value == "":
+                value = attr.value
+            res += " %s = %s\n" % (fdef[0], value)
+        return res
+
+
+class Magic(Struct):
+    """
+    Description :
+     * magic vaut toujours MPQ
+     * type indique si la position du header : 
+      - 1A => directement à la suite
+      - 1B => qq part dans le fichier, voir ShuntHdr pour la suite
+    """
+    fields = (
+        ('magic', Char, 3),
+        ('type', Char)
+        )
+
+    # Valeurs connues pour type
+    header = 0x1A
+    shunt = 0x1B
+        
+class UserData(Struct):
+    """
+    Description:
+     * unknown : 4 octets non utilisés ?
+     * header_offset : indique l'endroit du header par rapport au
+       début de ce header
+    """
+    fields = (
+        ('user_data_size', Integer),
+        ('header_offset', Integer),
+        ('user_data_header_size', Integer)
+        )
+
+class Header(Struct):
+    """Header du fichier
+
+    Pas forcément au début du fichier
+
+    Description :
+     * block_size : pour calculer la taille d'un bloc : 512 * 2 ^ block_size
+    """
+    fields = (
+        ('header_size', Integer),
+        ('archive_size', Integer),
+        ('format_version', Short),
+        ('block_size', Short),
+        ('hashtable_offset', Integer),
+        ('blocktable_offset', Integer),
+        ('hashtable_entries', Integer),
+        ('blocktable_entries', Integer)
+        )
+
+    # Valeurs connues pour format_version
+    oformat = 0x0000
+    bcformat = 0x0001
+
+class ExtraHeader(Struct):
+    fields = (
+        ('extended_blocktable_offset', Integer64),
+        ('hashtable_offset_high', Short),
+        ('blocktable_offset_high', Short),
+        )
+
+class HashTableEntry(Struct):
+    fields = (
+        ('fpath_hash_1', Integer),
+        ('fpath_hash_2', Integer),
+        ('language', Short),
+        ('platform', Short),
+        ('block_index', Integer)
+        )
+
+    def isempty(self):
+        return self.block_index.hex() == 0xFFFFFFFF
+
+    def isdeleted(self):
+        return self.block_index.hex() == 0xFFFFFFFE
+
+class BlockTableEntry(Struct):
+    fields = (
+        ('file_offset', Integer),
+        ('compressed_size', Integer),
+        ('uncompressed_size', Integer),
+        ('flags', Integer)
+        )
+
+    implode        = 0x00000100
+    compress       = 0x00000200
+    encrypted      = 0x00010000
+    fix_key        = 0x00020000
+    patch_file     = 0x00100000
+    single_unit    = 0x01000000
+    delete_marker  = 0x02000000
+    sector_crc     = 0x04000000
+    file_exists    = 0x80000000
+
+    def __check_flag(self, flag):
+        return (self.flags.hex() & getattr(self, flag)) and True or False
+
+    def isfile(self):
+        return self.__check_flag("file_exists")
+
+    def iscompressed(self):
+        return self.__check_flag("compress")
+
+    def haschecksums(self):
+        return self.__check_flag("sector_crc")
+
+    def issingleunit(self):
+        return self.__check_flag("single_unit")
+
+    def isencrypted(self):
+        return self.__check_flag("encrypted")
+
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-import os, sys
-import struct
-
-from decryptor import Decryptor
-
-class Type(object):
-    _fmt = None
-
-    def __init__(self, count=1, value=None):
-        self.value = value
-        self.count = count
-        self.fmt = self.count > 1 and "%d%s" % (self.count, self._fmt) \
-            or self._fmt
-
-    def __len__(self):
-        return struct.calcsize(self.fmt)
-
-    def hex(self):
-        if isinstance(self.value, str):
-            return int(self.value.encode('hex'), 16)
-        return self.value
-
-    def int(self):
-        return int(self.value)
-
-class Integer(Type):
-    _fmt = "i"
-
-class Integer64(Type):
-    _fmt = "q"
-
-class Char(Type):
-    _fmt = "s"
-
-class Short(Type):
-    _fmt = "h"
-
-class Byte(Type):
-    _fmt = "b"
-
-class Struct(object):
-    def __init__(self, **kwargs):
-        for fdef in self.fields:
-            args = fdef[2:]
-            newattr = fdef[1](*args)
-            setattr(self, fdef[0], newattr)
-
-        self.__compute_format()
-
-    def pack(self, **kwargs):
-        return struct.pack(self._fmt, **kwargs)
-
-    def load_from_buffer(self, buffer):
-        ret = struct.unpack_from(self.__fmt, buffer)
-        cpt = 0
-        for fdef in self.fields:
-            getattr(self, fdef[0]).value = ret[cpt]
-            cpt += 1
-
-    def __compute_format(self):
-        self.__fmt = ""
-        for fdef in self.fields:
-            self.__fmt += getattr(self, fdef[0]).fmt
-
-    def __len__(self):
-        res = 0
-        for fdef in self.fields:
-            res += len(getattr(self, fdef[0]))
-        return res
-
-    def __repr__(self):
-        res = "%s :\n" % self.__class__.__name__
-        for fdef in self.fields:
-            res += " %s = %s\n" % (fdef[0], getattr(self, fdef[0]).value)
-        return res
-
-
-class MagicHdr(Struct):
-    """
-    Description :
-     * magic vaut toujours MPQ
-     * type indique si la position du header : 
-      - 1A => directement à la suite
-      - 1B => qq part dans le fichier, voir ShuntHdr pour la suite
-    """
-    fields = (
-        ('magic', Char, 3),
-        ('type', Char)
-        )
-
-    # Valeurs connues pour type
-    header = 0x1A
-    shunt = 0x1B
-        
-class ShuntHdr(Struct):
-    """
-    Description:
-     * unknown : 4 octets non utilisés ?
-     * header_offset : indique l'endroit du header par rapport au
-       début de ce header
-    """
-    fields = (
-        ('unknown', Integer),
-        ('header_offset', Integer)
-        )
-
-class Header(Struct):
-    """Header du fichier
-
-    Pas forcément au début du fichier
-
-    Description :
-     * block_size : pour calculer la taille d'un bloc : 512 * 2 ^ block_size
-    """
-    fields = (
-        ('header_size', Integer),
-        ('archive_size', Integer),
-        ('format_version', Short),
-        ('block_size', Short),
-        ('hashtable_offset', Integer),
-        ('blocktable_offset', Integer),
-        ('hashtable_entries', Integer),
-        ('blocktable_entries', Integer)
-        )
-
-    # Valeurs connues pour format_version
-    oformat = 0x0000
-    bcformat = 0x0001
-
-class PostBCHeader(Struct):
-    fields = (
-        ('extended_blocktable_offset', Integer64),
-        ('hashtable_offset_high', Short),
-        ('blocktable_offset_high', Short),
-        )
-
-class HashTableEntry(Struct):
-    fields = (
-        ('fpath_hash_1', Integer),
-        ('fpath_hash_2', Integer),
-        ('language', Short),
-        ('platform', Short),
-        ('block_index', Integer)
-        )
+import sys
+from archive import *
 
 if __name__ == "__main__":
-    fname = sys.argv[1]
-    fp = open(fname, "rb")
-    hdr = MagicHdr()
-    hdr.load_from_buffer(fp.read(len(hdr)))
-    print hdr
-    if hdr.type.hex() == MagicHdr.shunt:
-        hdr = ShuntHdr()
-        hdr.load_from_buffer(fp.read(len(hdr)))
-        print hdr
-        fp.seek(hdr.header_offset.int())
-    hdr = MagicHdr()
-    hdr.load_from_buffer(fp.read(len(hdr)))
-    print hdr
-    if hdr.type.hex() == MagicHdr.header:
-        file_hdr = Header()
-        file_hdr.load_from_buffer(fp.read(len(file_hdr)))
-        print file_hdr
-        if file_hdr.format_version.hex() == Header.bcformat:
-            hdr = PostBCHeader()
-            hdr.load_from_buffer(fp.read(len(hdr)))
-            print hdr
-        start = file_hdr.hashtable_offset.int()
-        fp.seek(start)
+    from optparse import OptionParser
 
-        d = Decryptor()
-        key = d.hashstring("(hash table)", 3)
-        for i in xrange(1, file_hdr.hashtable_entries.int()):
-            fentry = HashTableEntry()
-            fentry.load_from_buffer(d.decrypt(fp, len(fentry), key))
-            print fentry
-            break
+    p = OptionParser(usage="usage: %prog [options] archive")
 
-    fp.close()
+    options, args = p.parse_args()
+    if not len(args):
+        print p.usage
+        sys.exit(1)
 
-                       
+    a = Archive(args[0])
+    print a.listfiles()
+    
 
 #include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
 
 #define MPQ_HASH_TABLE_OFFSET	0
 #define MPQ_HASH_NAME_A	1
 #define MPQ_HASH_NAME_B	2
 #define MPQ_HASH_FILE_KEY	3
 
+typedef struct hashtable_entry_s {
+  unsigned int hash_a;
+  unsigned int hash_b;
+  unsigned short locale;
+  unsigned short platform;
+  unsigned int blocktable_index;
+} __attribute__((packed)) hashtable_entry_t;
+
 unsigned int dwCryptTable[0x500];
 
 /* The encryption and hashing functions use a number table in their
   while (*lpszString != 0) {
     ch = toupper(*lpszString++);
     seed1 = dwCryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
-    printf("seed1 => %u\n", seed1);
     seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
-    printf("seed2 => %u\n", seed2);
   }
   return seed1;
 }
 
+void DecryptData(unsigned int *buffer, unsigned int len, unsigned int key) {
+  unsigned int seed = 0xEEEEEEEE;
+  unsigned int ch;
+
+  for (; len >= 4; len -= 4) {
+    seed += dwCryptTable[0x400 + (key & 0xFF)];
+    ch = *buffer ^ (key + seed);
+    key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
+    seed = ch + seed + (seed << 5) + 3;
+    *buffer++ = ch;
+  }
+}
 
 int main(int argc, char **argv) {
   const char *test1 = "unit\\neutral\\acritter.grp";
   InitializeCryptTable();
   printf("Hash of '%s' => 0x%X\n", test2, HashString(test2, MPQ_HASH_TABLE_OFFSET));
   printf("Hash of '%s' => 0x%X\n", test1, HashString(test1, MPQ_HASH_TABLE_OFFSET));
-  printf("Hash of '%s' => 0x%X\n", test3, HashString(test3, MPQ_HASH_FILE_KEY));
+
+  FILE *fp = fopen("replays/3585532_20100821_Morr.sc2replay", "rb");
+  unsigned int buffer[4];
+
+  fseek(fp, 238460, SEEK_SET);
+  fread(buffer, 4, sizeof (hashtable_entry_t) / 4, fp);
+
+  DecryptData(buffer, sizeof (hashtable_entry_t),
+              HashString("(hash table)", MPQ_HASH_FILE_KEY));
+  fclose(fp);
+
+  hashtable_entry_t entry;
+
+  memset(&entry, 0, sizeof (hashtable_entry_t));
+  memcpy(&entry, buffer, sizeof (hashtable_entry_t));
+  printf("hash_a: %u\nhash_b: %u\nlocale: 0x%X\nPlatform: %u\nbt_index: %u\n",
+         entry.hash_a, entry.hash_b, entry.locale, entry.platform,
+         entry.blocktable_index);
 }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.