Skip to content

Instantly share code, notes, and snippets.

@KasparNagu
Last active June 28, 2025 19:13
Show Gist options
  • Save KasparNagu/9ee02cb62d81d9e4c7a833518a710d6e to your computer and use it in GitHub Desktop.
Save KasparNagu/9ee02cb62d81d9e4c7a833518a710d6e to your computer and use it in GitHub Desktop.
Script to extract Advanced Installer Exes
#!/usr/bin/env python
import sys
import struct
import os
#inspired by https://aluigi.altervista.org/bms/advanced_installer.bms
#with some additionaly reverse engeneering, quite heursitic (footer search, xor guessing etc)
#licence: public domain
class AdvancedInstallerFileInfo:
def __init__(self, name, size, offset, xorSize):
self.name = name
self.size = size
self.offset = offset
self.xorSize = xorSize
def __repr__(self):
return "[%s size=%d offset=%d]" % (self.name, self.size, self.offset)
class AdvancedInstallerFileReader:
def __init__(self,filehandle,size,keepOpen,xorLength):
self.filehandle = filehandle
self.size = size
self.xorLength = xorLength
self.pos = 0
self.keepOpen = keepOpen
def xorFF(self,block):
if isinstance(block,str):
return "".join([chr(ord(i)^0xff) for i in block])
else:
return bytes([i^0xff for i in block])
def read(self,size = None):
if size is None:
return self.read(self.size - self.pos)
if self.pos < self.xorLength:
xorLen = min(self.xorLength - self.pos, size)
xorBlock = self.filehandle.read(xorLen)
xorLenEffective = len(xorBlock)
self.pos += xorLenEffective
xorBlock = self.xorFF(xorBlock)
if xorLenEffective < size:
return xorBlock + self.read(size - xorLenEffective)
return xorBlock
blk = self.filehandle.read(min(size,self.size - self.pos))
self.pos += len(blk)
return blk
def close(self):
if not self.keepOpen:
self.filehandle.close()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
class AdvancedInstallerReader:
def __init__(self,filename,debug=None):
self.filename = filename
self.filehandle = open(filename,"rb")
self.search_back = 10000
self.xorSize = 0x200
self.footer_position = None
self.debug = debug
self.threadsafeReopen = False
self.files = []
def close(self):
self.filehandle.close()
def search_footer(self):
for i in range(0,10000):
self.filehandle.seek(-i,os.SEEK_END)
magic = self.filehandle.read(10)
if magic == b"ADVINSTSFX":
self.footer_position = i + 0x48 - 12
break
if self.footer_position is None:
raise Exception("ADVINSTSFX not found")
def read_footer(self):
if self.footer_position is None:
self.search_footer()
self.filehandle.seek(-self.footer_position,os.SEEK_END)
footer = self.filehandle.read(0x48)
offset, self.nfiles, dummy1, offset1, self.info_off, file_off, hexhash, dummy2, name, = struct.unpack("<llllll32sl12s", footer)
if self.debug:
self.debug.write("offset=%d files=%d offset1=%d info_off=%d file_off=%d hexhash=%s name=%s\n" % (offset,self.nfiles,offset1,self.info_off,file_off,hexhash,name))
def read_info(self):
self.read_footer()
self.files = []
self.filehandle.seek(self.info_off,os.SEEK_SET)
for i in range(0,self.nfiles):
info = self.filehandle.read(24)
dummy1, dummy2, dummy3, size, offset, namesize = struct.unpack("<llllll",info)
if self.debug:
self.debug.write(" size=%d offset=%d namesize=%d dummy1=0x%x dummy2=0x%x dummy3=0x%x\n" % (size,offset,namesize,dummy1,dummy2,dummy3))
if namesize < 0xFFFF:
name = self.filehandle.read(namesize*2)
name = name.decode("UTF-16")
if self.debug:
self.debug.write(" name=%s\n" % name)
self.files.append(AdvancedInstallerFileInfo(name,size,offset,self.xorSize if dummy3 == 2 else 0))
else:
raise Exception("Invalid name size %d" % namesize)
def open(self,infoFile):
if isinstance(infoFile,AdvancedInstallerFileInfo):
if self.threadsafeReopen:
fh = open(self.filename,"rb")
else:
fh = self.filehandle
fh.seek(infoFile.offset,os.SEEK_SET)
return AdvancedInstallerFileReader(fh,infoFile.size,not self.threadsafeReopen,infoFile.xorSize)
else:
if not self.files:
self.read_info()
for f in files:
if f.name == infoFile:
return self.open(f)
return None
def infolist(self):
if not self.files:
self.read_info()
return self.files
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def __repr__(self):
return "[path=%s footer=%s nFiles=%d]" % (self.filename,self.footer_position,len(self.files))
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Advanced Installer Extractor")
parser.add_argument('file', type=str, help="Advanced Installer to open")
parser.add_argument('files', type=str,nargs="*", help="Files to consider")
parser.add_argument('-x','--extract', default=False, action="store_true",help="Extract to current directory")
parser.add_argument('-l','--list', default=False, action="store_true",help="List files")
parser.add_argument('-v','--verbose', default=False, action="store_true",help="Debug output")
args = parser.parse_args();
considerFiles = set(args.files)
with AdvancedInstallerReader(args.file,sys.stdout if args.verbose else None) as ar:
for f in ar.infolist():
if not considerFiles or f.name in considerFiles:
if args.list:
print(f)
if args.extract:
path = f.name.replace("\\","/")
dirname = os.path.dirname(path)
if dirname:
if not os.path.exists(dirname):
os.makedirs(dirname)
with ar.open(f) as inf, open(path,"wb") as out:
while True:
blk = inf.read(1<<16)
if len(blk) == 0:
break
out.write(blk)
if args.verbose:
print(ar)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment