7 #package Data::MPQ::SCM;
8 #package File::StarCraft::Map;
9 package Archive::MoPaQ;
19 $_[0] %= 0xFFFF_FFFF + 1;
24 my $seed = 0x0010_0001;
26 for my $i1 (0 .. 0x100) {
29 $seed = ($seed * 125 + 3) % 0x2AAAAB;
30 my $temp1 = ($seed & 0xFFFF) << 16;
32 $seed = ($seed * 125 + 3) % 0x2AAAAB;
33 my $temp2 = ($seed & 0xFFFF);
35 $crypttable[$i2] = ($temp1 | $temp2);
44 my ($str, $type) = @_;
45 my ($seed1, $seed2) = (0x7FED_7FED, 0xEEEE_EEEE);
46 for my $ch (unpack "C*", uc $str) {
48 $self->overflow($seed1);
49 $seed1 ^= $crypttable[$type*0x100 + $ch];
51 $seed2 += $ch + $seed1 + 3;
52 $self->overflow($seed2);
60 my $seed = 0xEEEE_EEEE;
62 my @out = unpack "V*", $in;
64 $seed += $crypttable[0x400 + ($key & 0xFF)];
65 $self->overflow($seed);
66 $ch ^= $self->overflow($key + $seed);
67 $key = $self->overflow((~$key << 21) + 0x1111_1111) | ($key >> 11);
71 return pack "V*", @out;
76 # Archive Header - File Data - File Data - Special Files - Hash Table - Block Table - Extended Block Table - Strong Digital signature
79 my ($class, $file) = @_;
80 my $fh = $file || \*STDIN;
88 my ($size, $seek) = @_;
90 seek *$fh, $seek, 0 if $seek;
91 read(*$fh, my $in, $size) eq $size or return undef;
98 local $_ = $self->_read(8) #, 0
99 and my ($magic, $headsize) = unpack "a4V", $_
100 or die "Couldn't read file header\n";
103 or die "File is not a valid MoPaQ archive\n";
106 or die "Unrecognized header size\n";
108 $self->{header} = $self->_read($headsize) #, 8
109 or die "Couldn't read header of $headsize bytes\n";
112 $archivesize, # Size of the whole archive, including the header. Does not include the strong digital signature, if present. This size is used, among other things, for determining the region to hash in computing the digital signature. This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive is calculated as the size from the beginning of the archive to the end of the hash table, block table, or extended block table (whichever is largest).
113 $version, # MoPaQ format version. MPQAPI will not open archives where this is negative. Known versions:
114 # 0000h: Original format. HeaderSize should be 20h, and large archives are not supported.
115 # 0001h: Burning Crusade format. Header size should be 2Ch, and large archives are supported.
116 $sectorsize, # Power of two exponent specifying the number of 512-byte disk sectors in each logical sector in the archive. The size of each logical sector in the archive is 512 * 2^SectorSizeShift. Bugs in the Storm library dictate that this should always be 3 (4096 byte sectors).
118 $hashoffset, # Offset to the beginning of the hash table, relative to the beginning of the archive.
119 $blockoffset, # Offset to the beginning of the block table, relative to the beginning of the archive.
120 $hashentries, # Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
121 $blockentries, # Number of entries in the block table.
122 ) = unpack "Vvcx VVVV", $self->{header};
125 $eblockoffset, # (>=BC) Offset to the beginning of the extended block table, relative to the beginning of the archive.
126 $hashoffsethb, # (>=BC) High 16 bits of the hash table offset for large archives.
127 $blockoffsethb, # (>=BC) High 16 bits of the block table offset for large archives.
128 ) = unpack "C8ss", substr $self->{header}, -4 if $version >= 1;
130 my $hashdata = $self->_read($hashentries * 16, $hashoffset)
133 map unpack("a11xV", $_),
134 unpack "(a16)*", $self->decrypt($hashdata, $self->hashstr('(hash table)', 3))
137 my $blockdata = $self->_read($blockentries * 16, $blockoffset)
140 unpack "(a16)*", $self->decrypt($blockdata, $self->hashstr('(block table)', 3))
150 $offset, # Offset of the beginning of the block, relative to the beginning of the archive.
151 $size, # Size of the block in the archive.
152 $filesize, # Size of the file data stored in the block. Only valid if the block is a file; otherwise meaningless, and should be 0. If the file is compressed, this is the size of the uncompressed file data.
154 ) = unpack "V4", $self->{block}[$index];
156 my $data = $self->_read($size, $offset) or die $!;
157 $self->decrypt($data, 0);
164 package Data::StarCraft::Map;
167 BWREP_HEADER_SIZE => 0x279,
171 my ($class, $file) = @_;
172 my $fh = $file || \*STDIN;
180 my ($size, $seek) = @_;
181 my $fh = $self->{fh};
182 seek *$fh, $seek, 0 if $seek;
183 read(*$fh, my $in, $size) eq $size or return undef;
192 $esi{m20} = func_esi28(myesi->m2234, myesi->m1C, myesi->m24);
193 $esi{m20} <= 4 and return 3;
194 $esi{m04} = (int)rep->src[0];
195 $esi{m0C} = (int)rep->src[1];
196 $esi{m14} = (int)rep->src[2];
199 $esi{m0C} < 4 || $esi{m0C} > 6 and return 1;
200 $esi{m10} = (1 << $esi{m0C}) - 1; # 2^n - 1
202 # /* if ($esi{m04} == 1) printf("Oops\n"); */ /* Should never be true */
203 $esi{m04} == 0 or return 2;
205 memcpy(myesi->m30F4, off_5071D0, sizeof(off_5071D0)); /* dst, src, len */
206 com1(sizeof(off_5071E0), myesi->m30F4, off_5071E0, myesi->m2B34); /* len, src, str, dst */
207 memcpy(myesi->m3104, off_5071A0, sizeof(off_5071A0)); /* dst, src, len */
208 memcpy(myesi->m3114, off_5071B0, sizeof(off_5071B0)); /* dst, src, len */
209 memcpy(myesi->m30B4, off_507120, sizeof(off_507120)); /* dst, src, len */
210 com1(sizeof(off_507160), myesi->m30B4, off_507160, myesi->m2A34); /* len, src, str, dst */
211 unpack_rep_chunk(myesi);
217 my ($self, $size) = @_;
223 # byte buffer[0x2000];
224 # int check, count, length, n, len=0, m1C, m20=0;
226 my $check = $self->_read(4) or return;
227 my $count = $self->_read(4) or return;
230 for (my $n = 0, $m1C = 0; $n < $count; $n++, $m1C += sizeof(buffer), $m20 += $len) {
231 my $length = $this->_read(4);
232 $length <= $size - $m20 or return;
233 my $result = $this->_read($length) or return;
234 continue if $length == min($size - $m1C, sizeof($buffer));
237 rep.src = (byte*)result;
242 rep.m14 = sizeof(buffer);
243 # unpack replay section
244 if (unpack_rep_section(&myesi, &rep) == 0 && rep.m0C <= sizeof(buffer)) len = rep.m0C; else len = 0;
245 if (len == 0 || len > size) return 4;
247 # Main decompression functions
249 unsigned long outlength = sizeof(buffer);
250 Explode4(buffer, &outlength, result, length);
251 len = (int)outlength;
254 memcpy(result, buffer, len);
264 local $_ = $self->_read(16)
265 and my ($magic, $headsize) = unpack "a16", $_
266 or die "Couldn't read file header\n";
268 $magic eq "\xA7\x7E\x7E\x2B\001\000\000\000\004\000\000\000reRS"
269 or die "File is not a valid starcraft replay\n";
271 $self->{header} = $self->unpack_section(BWREP_HEADER_SIZE)
272 or die "Couldn't read header of ".BWREP_HEADER_SIZE." bytes\n";
277 my $rep = Data::StarCraft::Map->new;
279 print join ",", map ord, split //, $rep->{header};
283 my $mpq = Archive::MoPaQ->new;
285 print $mpq->extract(0);
286 #print Dumper [ $mpq->extract(0) ];
288 #print Dumper pack('V*', $self->hashstr('(index)', 1));