You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
6.8 KiB
218 lines
6.8 KiB
import sys |
|
from struct import pack, unpack |
|
|
|
|
|
class CabFormatError(Exception): |
|
def __init__(self, msg): |
|
super().__init__(msg) |
|
|
|
|
|
class PatchLengthError(Exception): |
|
def __init__(self, msg): |
|
super().__init__(msg) |
|
|
|
|
|
class Cab: |
|
def __init__(self, data): |
|
self.CFHEADER = CFHeader(data) |
|
self.CFFOLDER = CFFolder(data) |
|
self.CFFILE = CFFile(data) |
|
self.CFFDATA = CFFData(data, start=self.CFFILE.end_offset) |
|
|
|
@staticmethod |
|
def seek_null(data, start=0, chunk_size=24): |
|
chunk = data[start:start + chunk_size] |
|
index = chunk.find(b"\x00") |
|
return start + index if index > 0 else -1 |
|
|
|
def change_set_id(self, value: int): |
|
self.CFHEADER.setID = value |
|
|
|
def zero_out_signature(self): |
|
self.CFFDATA.csum = 0 |
|
|
|
def change_coff_cab_start(self, value: int): |
|
self.CFFOLDER.coffCabStart = value |
|
|
|
def change_ccfdata_count(self, value: int): |
|
self.CFFOLDER.cCFData = value |
|
|
|
def change_cffile_cbfile(self, value: int): |
|
self.CFFILE.cbFile = value |
|
|
|
def make_file_read_only(self): |
|
self.CFFILE.attribs |= 0x1 |
|
self.CFFILE.attribs |= 0x2 |
|
self.CFFILE.attribs |= 0x4 |
|
|
|
def change_bytes(self, offset, size, value): |
|
if len(value) < size: |
|
raise PatchLengthError |
|
_bytes = bytearray(self.to_bytes()) |
|
_bytes[offset:offset + size] = value[:size] |
|
return bytes(_bytes) |
|
|
|
def to_string(self): |
|
return rf""" |
|
CFHeader: {self.CFHEADER.to_string()} |
|
CFFolder: {self.CFFOLDER.to_string()} |
|
CFFile: {self.CFFILE.to_string()} |
|
CFFData: {self.CFFDATA.to_string()} |
|
""" |
|
|
|
def to_bytes(self): |
|
return self.CFHEADER.to_bytes() + self.CFFOLDER.to_bytes() + self.CFFILE.to_bytes() + self.CFFDATA.to_bytes() |
|
|
|
|
|
class CFHeader: |
|
def __init__(self, data): |
|
self.raw = data[:24] |
|
self.signature_display = data[:4].decode() |
|
self.signature = unpack("BBBB", data[:4]) |
|
if self.signature_display != "MSCF": |
|
raise CabFormatError("Unknown signature") |
|
self.reserved1 = unpack("<I", data[4:8])[0] |
|
self.cbCabinet = unpack("<I", data[0x8:0xC])[0] |
|
self.reserved2 = unpack("<I", data[0xC:0x10])[0] |
|
self.coffFiles = unpack("<I", data[0x10:0x14])[0] |
|
self.reserved3 = unpack("<I", data[0x14:0x18])[0] |
|
self.versionMajor, self.versionMinor = unpack("BB", data[0x18:0x1A]) |
|
self.cFolders = unpack("H", data[0x1A:0x1C])[0] |
|
self.cFiles = unpack("H", data[0x1C:0x1E])[0] |
|
self.flags = unpack("H", data[0x1E:0x20])[0] |
|
self.setID = unpack("H", data[0x20:0x22])[0] |
|
self.iCabinet = unpack("H", data[0x22:0x24])[0] |
|
|
|
def to_bytes(self): |
|
_to_bytes = pack("BBBB", self.signature[0], self.signature[1], self.signature[2], self.signature[3]) |
|
_to_bytes += pack("<I", self.reserved1) |
|
_to_bytes += pack("<I", self.cbCabinet) |
|
_to_bytes += pack("<I", self.reserved2) |
|
_to_bytes += pack("<I", self.coffFiles) |
|
_to_bytes += pack("<I", self.reserved3) |
|
_to_bytes += pack("B", self.versionMajor) |
|
_to_bytes += pack("B", self.versionMinor) |
|
_to_bytes += pack("H", self.cFolders) |
|
_to_bytes += pack("H", self.cFiles) |
|
_to_bytes += pack("H", self.flags) |
|
_to_bytes += pack("H", self.setID) |
|
_to_bytes += pack("H", self.iCabinet) |
|
return _to_bytes |
|
|
|
def to_string(self): |
|
return rf""" |
|
Signature: {self.signature_display} |
|
Reserved1: {self.reserved1} |
|
CbCabinet: {self.cbCabinet} |
|
Reserved2: {self.reserved2} |
|
CoffFiles: {self.coffFiles} |
|
Reserved3: {self.reserved3} |
|
Version: {self.versionMajor}.{self.versionMinor} |
|
CFolders: {self.cFolders} |
|
CFiles: {self.cFiles} |
|
Flags: {self.flags} |
|
SetID: {self.setID} |
|
ICabinet: {self.iCabinet} |
|
""" |
|
|
|
|
|
class CFFolder: |
|
def __init__(self, data): |
|
self.raw = data[0x24:0x2B] |
|
self.coffCabStart = unpack("<I", data[0x24:0x28])[0] |
|
self.cCFData = unpack("H", data[0x28:0x2A])[0] |
|
self.typeCompress = unpack("H", data[0x2A:0x2C])[0] |
|
|
|
def to_bytes(self): |
|
_to_bytes = pack("<I", self.coffCabStart) |
|
_to_bytes += pack("H", self.cCFData) |
|
_to_bytes += pack("H", self.typeCompress) |
|
return _to_bytes |
|
|
|
def to_string(self): |
|
return rf""" |
|
CoffCabStart: {self.coffCabStart} |
|
CCFData: {self.cCFData} |
|
TypeCompress: {self.typeCompress} |
|
""" |
|
|
|
|
|
class CFFile: |
|
def __init__(self, data): |
|
self.raw = data[0x2C:0x44] |
|
self.cbFile = unpack("<I", data[0x2C:0x30])[0] |
|
self.uoffFolderStart = unpack("<I", data[0x30:0x34])[0] |
|
self.iFolder = unpack("H", data[0x34:0x36])[0] |
|
self.date = unpack("H", data[0x36:0x38])[0] |
|
self.time = unpack("H", data[0x38:0x3A])[0] |
|
self.attribs = unpack("H", data[0x3A:0x3C])[0] |
|
self.end_offset = Cab.seek_null(data, start=0x3C + 0x1, chunk_size=128) + 1 |
|
self.szName = data[0x3C:self.end_offset].decode() |
|
|
|
def to_bytes(self): |
|
_to_bytes = pack("<I", self.cbFile) |
|
_to_bytes += pack("<I", self.uoffFolderStart) |
|
_to_bytes += pack("H", self.iFolder) |
|
_to_bytes += pack("H", self.date) |
|
_to_bytes += pack("H", self.time) |
|
_to_bytes += pack("H", self.attribs) |
|
_to_bytes += self.szName.encode() |
|
return _to_bytes |
|
|
|
def to_string(self): |
|
return rf""" |
|
CbFile: {self.cbFile} |
|
UoffFolderStart: {self.uoffFolderStart} |
|
IFolder: {self.iFolder} |
|
Date: {self.date} |
|
Time: {self.time} |
|
Attribs: {self.attribs} |
|
SzName: {self.szName} |
|
""" |
|
|
|
|
|
class CFFData: |
|
def __init__(self, data, start): |
|
self.raw = data[start:] |
|
self.csum = unpack("<I", data[start:start + 4])[0] |
|
self.cbData = unpack("H", data[start + 4:start + 6])[0] |
|
self.cbUncomp = unpack("H", data[start + 6:start + 8])[0] |
|
self.ab_display = data[start + 8:start + 18] + data[-10:] |
|
self.ab = self.raw[8:] |
|
|
|
def to_bytes(self): |
|
_to_bytes = pack("<I", self.csum) |
|
_to_bytes += pack("H", self.cbData) |
|
_to_bytes += pack("H", self.cbUncomp) |
|
_to_bytes += self.raw[8:] |
|
return _to_bytes |
|
|
|
def to_string(self): |
|
return rf""" |
|
Checksum: {self.csum} |
|
CbData: {self.cbData} |
|
CbUncompressed: {self.cbUncomp} |
|
Ab: {self.ab_display} |
|
""" |
|
|
|
|
|
def parse(file): |
|
data = open(file, "rb").read() |
|
cab = Cab(data=data) |
|
print(cab.to_string()) |
|
|
|
|
|
def change_e_magic(cab: Cab, value: bytes): |
|
if not value or not isinstance(value, bytes) or len(value) != 4: |
|
return |
|
new_cab = cab.change_bytes(offset=0x58, size=4, value=b"MZ\x90\x00") |
|
print(Cab(new_cab).to_string()) |
|
|
|
|
|
def save(cab: Cab, file: str): |
|
with open(file, "wb") as out: |
|
out.write(cab.to_bytes()) |
|
|
|
|
|
if __name__ == "__main__": |
|
parse(file=sys.argv[1])
|
|
|