Skip to content

Instantly share code, notes, and snippets.

@dcramer
Created April 22, 2010 21:07
Show Gist options
  • Save dcramer/375813 to your computer and use it in GitHub Desktop.
Save dcramer/375813 to your computer and use it in GitHub Desktop.
# usage: python <thisfile.py> help -- downloads the files to the current directory
# requires libmpq
# Attempts to brute force and download specific patches
# e.g. python sc2patch.py --current=0.10.0.14803 --next=0.11.0.15097
# This would find and download any patches relevant, the next argument is optional,
# but allows you to provide a starting point. This stops at the first patch it finds,
# so you would need to continue with a new starting point if you wanted a different patch.
# e.g. if you didnt specify next in this example, it would stop at the initial 0.11 build,
# which wasnt publicly released
import urllib2
import tempfile
import mpq
import math
import re
import optparse
decimal_match = re.compile('\d')
def bdecode(data):
'''Main function to decode bencoded data'''
def _dechunk(chunks):
item = chunks.pop()
if (item == 'd'):
item = chunks.pop()
hash = {}
while (item != 'e'):
chunks.append(item)
key = _dechunk(chunks)
hash[key] = _dechunk(chunks)
item = chunks.pop()
return hash
elif (item == 'l'):
item = chunks.pop()
list = []
while (item != 'e'):
chunks.append(item)
list.append(_dechunk(chunks))
item = chunks.pop()
return list
elif (item == 'i'):
item = chunks.pop()
num = ''
while (item != 'e'):
num += item
item = chunks.pop()
return int(num)
elif (decimal_match.findall(item)):
num = ''
while decimal_match.findall(item):
num += item
item = chunks.pop()
line = ''
for i in range(1, int(num) + 1):
line += chunks.pop()
return line
raise ValueError("Invalid input!")
chunks = list(data)
chunks.reverse()
root = _dechunk(chunks)
return root
def fetch_updates(old_version, new_version, new_build, filename, locale='enUS'):
def download(base_url, fname, parts=None):
def _get_chunk(fp, p, t=None):
if t:
print "Downloading %s part %s @ %s%%" % (fname, p, round(float(p)/parts*100,2))
else:
print "Downloading %s part %s" % (fname, p)
try:
remote = urllib2.urlopen("%s/%s/%s" % (base_url, fname, p))
for l in remote:
fp.write(l)
except urllib2.HTTPError:
# assume its done
return
out = open(fname, 'wb')
if parts:
for p in xrange(0, parts):
_get_chunk(out, p, parts)
else:
while 1:
_get_chunk(out, p)
out.close()
def _fetch_component(name, old_version, new_version):
url = 'http://dist.blizzard.com.edgesuite.net/sc2patch/update/B%sB/sc2-%s-%s-x86-Win-enUS-%s-component-dl' % (new_build, old_version, new_version, name)
fname = url.rsplit('/', 1)[-1]
out = open(fname, 'wb')
remote = urllib2.urlopen(url)
for l in remote:
out.write(l)
out.close()
x = mpq.Archive(str(fname))
data = x['prepatch.lst']
fname = str(data).split('\n')[0].split(' ', 1)[-1].strip()
meta = bdecode(str(x[str(fname)]))
chunk_size = meta['info']['piece length']
files = []
for fileinfo in meta['info']['files']:
fname = fileinfo['path'][0]
parts = int(math.ceil(float(fileinfo['length'])/chunk_size))
files.append((fname, parts))
print "Found %s (%s chunks)" % (fname, parts)
base_url = 'http://dist.blizzard.com.edgesuite.net/sc2patch/patches/B%sB' % (new_build,)
for fname, parts in files:
download(base_url, fname, parts)
_fetch_component(filename, old_version, new_version)
# download('sc2-%s-%s-x86-Win-%s-locale' % (old_version, new_version, locale))
#http://dist.blizzard.com.edgesuite.net/sc2patch/update/B14803B/sc2-0.9.0.14621-0.10.0.14803-x86-Win-enUS-game-component-dl;sc2-0.9.0.14621-0.10.0.14803-x86-Win-enUS-game-component-dl;13EE085001134AE95E4DFC9D21E4FE67;14803
# where do these come from?
# download('sc2-1.0.0.18720-1.0.0.19468-x86-Win-enUS-bnet-bin')
# download('sc2-1.0.0.4947-1.0.0.5194-x86-Win-enUS-bnet-base')
def find_next_version(old_version, base_version, build=None, component='game', base_build=None, verbose=True):
if base_build is None:
base_build = int(old_version.rsplit('.', 1)[-1])
if build is None:
build = base_build
while True:
base_build += 1
url = 'http://dist.blizzard.com.edgesuite.net/sc2patch/update/B%sB/sc2-%s-%s.%s-x86-Win-enUS-%s-component-dl' % (build, old_version, base_version, base_build, component)
fname = url.rsplit('/', 1)[-1]
out = open(fname, 'wb')
try:
remote = urllib2.urlopen(url)
except urllib2.HTTPError:
if verbose:
print 'No component found for %s at %s.%s' % (component, base_version, base_build)
continue
else:
if verbose:
print 'Successfully found %s at %s.%s' % (component, base_version, base_build)
return '%s.%s' % (base_version, base_build)
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option('-c', '--current', dest='current', help="Current build version (e.g. 0.10.0.14803)")
parser.add_option('-n', '--next', dest='next', help="Version to download (e.g. 0.11.0.15090)")
(options, args) = parser.parse_args()
current = options.current
if not current:
raise SyntaxError('You must specifiy your current build version with --current.')
next = options.next
if not next:
# hackish
base = current.split('.', 3)
base = '%s.%s.%s' % (base[0], int(base[1])+1, base[2])
next = find_next_version(current, base)
# bnet-bin and bnet-build both have different build numbers
build = int(next.rsplit('.', 1)[-1])
# this doesnt solve fetching the bnet files, as we still dont know their version and need to guess
fetch_updates(current, next, build, 'game')
fetch_updates(current, next, build, 'locale')
# Now we need to find the build numbers for bnet-bin and bnet-base
bnet_bin = find_next_version(current, base, build, 'bnet-bin')
fetch_updates(current, bnet_bin, build, 'bnet-bin')
bnet_base = find_next_version(current, base, build, 'bnet-base')
fetch_updates(current, bnet_base, build, 'bnet-base')
# now take each file, one at a time and place it in the Support folder within your install
# directory. Rename it to sc2-patch.mpq, and run Blizzard Updater.exe. Repeat for each file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment