Skip to content

Instantly share code, notes, and snippets.

@Tachiorz
Last active February 18, 2021 00:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Tachiorz/3a53abca4f42043bc152 to your computer and use it in GitHub Desktop.
Save Tachiorz/3a53abca4f42043bc152 to your computer and use it in GitHub Desktop.
TOS ipf unpack
import sys
import os
import struct
import zlib
__author__ = 'Tachi'
class TOSZipDecrypter:
@staticmethod
def _GenerateCRCTable():
"""Generate a CRC-32 table.
ZIP encryption uses the CRC32 one-byte primitive for scrambling some
internal keys. We noticed that a direct implementation is faster than
relying on binascii.crc32().
"""
poly = 0xedb88320
table = [0] * 256
for i in range(256):
crc = i
for j in range(8):
if crc & 1:
crc = ((crc >> 1) & 0x7FFFFFFF) ^ poly
else:
crc = ((crc >> 1) & 0x7FFFFFFF)
table[i] = crc
return table
crctable = _GenerateCRCTable.__func__()
def _crc32(self, ch, crc):
"""Compute the CRC32 primitive on one byte."""
return ((crc >> 8) & 0xffffff) ^ self.crctable[(crc ^ ord(ch)) & 0xff]
def __init__(self, pwd):
self.key0 = 305419896
self.key1 = 591751049
self.key2 = 878082192
for p in pwd:
self._UpdateKeys(p)
def _UpdateKeys(self, c):
self.key0 = self._crc32(c, self.key0)
self.key1 = (self.key1 + (self.key0 & 0xFF)) & 0xFFFFFFFF
self.key1 = (self.key1 * 134775813 + 1) & 0xFFFFFFFF
self.key2 = self._crc32(chr((self.key1 >> 24) & 0xFF), self.key2)
def decrypt(self, buf):
result = list()
for i in range(len(buf)):
if not i % 2:
result.append(self(buf[i]))
else:
result.append(buf[i])
return ''.join(result)
def __call__(self, c):
"""Decrypt a single character."""
c = ord(c)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
c = chr(c)
self._UpdateKeys(c)
return c
class File(object):
NSIZE = 0
CRC = 0
ZSIZE = 0
SIZE = 0
OFFSET = 0
CSIZE = 0
comment = ""
name = ""
def __init__(self, NSIZE, CRC, ZSIZE, SIZE, OFFSET, CSIZE, comment, name):
self.NSIZE = NSIZE
self.CRC = CRC
self.ZSIZE = ZSIZE
self.SIZE = SIZE
self.OFFSET = OFFSET
self.CSIZE = CSIZE
self.comment = comment
self.name = name
files = list()
if __name__ == "__main__":
if len(sys.argv) < 2:
print "need argument"
exit()
with open(sys.argv[1], 'rb') as f:
f.seek(0, os.SEEK_END)
filesize = f.tell()
f.seek(-0x18, os.SEEK_END)
file_numb, table_offset, flag = struct.unpack("<HIH", f.read(8))
f.seek(table_offset, os.SEEK_SET)
for i in range(file_numb):
field = struct.unpack("<HIIIIH", f.read(20))
field = list(field)
field += struct.unpack("{0}s{1}s".format(field[5], field[0]), f.read(field[0]+field[5]))
files.append(File(*field))
for i in files:
print i.comment, i.name
f.seek(i.OFFSET, os.SEEK_SET)
buf = f.read(i.ZSIZE)
if i.ZSIZE != i.SIZE:
dec = TOSZipDecrypter("ofO1a0ueXA? [\xFFs h %?")
buf = dec.decrypt(buf)
buf = zlib.decompress(buf, -zlib.MAX_WBITS)
name = "{1}/{2}".format(sys.argv[1][:-4],i.comment[:-4],i.name)
if '/' in name:
if not os.path.exists(os.path.dirname(name)):
os.makedirs(os.path.dirname(name))
with open(name, 'wb') as out:
out.write(buf)
@hiderdes
Copy link

Hellow. You can write a script for a new version of the client Tree of Savior.

@WillTheDoggy
Copy link

It works for both kToS and the iToS at the time of this comment.

@AirShark
Copy link

yup it's work, need use python v2

@Mokitdown
Copy link

Where did this come from "ofO1a0ueXA? [\xFFs h %?"
I know it represents the password used to decrypt the files, but I can't find out how it was discovered?

@logabot
Copy link

logabot commented May 4, 2016

It works fine for the iToS at the time of this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment