From 7e2777582586addd70469a734bd2ab4a846cfdae Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Tue, 4 Dec 2018 14:44:09 +0100 Subject: [PATCH 01/11] Initial commit. --- .gitignore | 11 ++++ Compressed | Bin 0 -> 15828 bytes Expanded | Bin 0 -> 21174 bytes GreggDecompress.py | 126 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 22 ++++++++ ResDecompress.py | 53 +++++++++++++++++++ 6 files changed, 212 insertions(+) create mode 100644 .gitignore create mode 100644 Compressed create mode 100644 Expanded create mode 100644 GreggDecompress.py create mode 100644 README.md create mode 100644 ResDecompress.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..342438e --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Files to be ignored by Git. + +.git + +# Python auto-generated files and folders. +/__pycache__ +Dump + +# System files. +.DS_Store +Thumb.db diff --git a/Compressed b/Compressed new file mode 100644 index 0000000000000000000000000000000000000000..bdfce82f453f3562fe64a2ed9c51d4048120f367 GIT binary patch literal 15828 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 literal 0 HcmV?d00001 diff --git a/Expanded b/Expanded new file mode 100644 index 0000000000000000000000000000000000000000..7aaecedd9505abd1df0d84211f735e27060edf0b GIT binary patch literal 21174 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 literal 0 HcmV?d00001 diff --git a/GreggDecompress.py b/GreggDecompress.py new file mode 100644 index 0000000..690b7c8 --- /dev/null +++ b/GreggDecompress.py @@ -0,0 +1,126 @@ +''' + This script implements the GreggyBits decompression algorithm + found in the 'dcmp' 2 resource. +''' + +import struct + +# predefined lookup table of the most frequent words +GreggDefLUT = ( + 0x0000, 0x0008, 0x4EBA, 0x206E, 0x4E75, 0x000C, 0x0004, 0x7000, + 0x0010, 0x0002, 0x486E, 0xFFFC, 0x6000, 0x0001, 0x48E7, 0x2F2E, + 0x4E56, 0x0006, 0x4E5E, 0x2F00, 0x6100, 0xFFF8, 0x2F0B, 0xFFFF, + 0x0014, 0x000A, 0x0018, 0x205F, 0x000E, 0x2050, 0x3F3C, 0xFFF4, + 0x4CEE, 0x302E, 0x6700, 0x4CDF, 0x266E, 0x0012, 0x001C, 0x4267, + 0xFFF0, 0x303C, 0x2F0C, 0x0003, 0x4ED0, 0x0020, 0x7001, 0x0016, + 0x2D40, 0x48C0, 0x2078, 0x7200, 0x588F, 0x6600, 0x4FEF, 0x42A7, + 0x6706, 0xFFFA, 0x558F, 0x286E, 0x3F00, 0xFFFE, 0x2F3C, 0x6704, + 0x598F, 0x206B, 0x0024, 0x201F, 0x41FA, 0x81E1, 0x6604, 0x6708, + 0x001A, 0x4EB9, 0x508F, 0x202E, 0x0007, 0x4EB0, 0xFFF2, 0x3D40, + 0x001E, 0x2068, 0x6606, 0xFFF6, 0x4EF9, 0x0800, 0x0C40, 0x3D7C, + 0xFFEC, 0x0005, 0x203C, 0xFFE8, 0xDEFC, 0x4A2E, 0x0030, 0x0028, + 0x2F08, 0x200B, 0x6002, 0x426E, 0x2D48, 0x2053, 0x2040, 0x1800, + 0x6004, 0x41EE, 0x2F28, 0x2F01, 0x670A, 0x4840, 0x2007, 0x6608, + 0x0118, 0x2F07, 0x3028, 0x3F2E, 0x302B, 0x226E, 0x2F2B, 0x002C, + 0x670C, 0x225F, 0x6006, 0x00FF, 0x3007, 0xFFEE, 0x5340, 0x0040, + 0xFFE4, 0x4A40, 0x660A, 0x000F, 0x4EAD, 0x70FF, 0x22D8, 0x486B, + 0x0022, 0x204B, 0x670E, 0x4AAE, 0x4E90, 0xFFE0, 0xFFC0, 0x002A, + 0x2740, 0x6702, 0x51C8, 0x02B6, 0x487A, 0x2278, 0xB06E, 0xFFE6, + 0x0009, 0x322E, 0x3E00, 0x4841, 0xFFEA, 0x43EE, 0x4E71, 0x7400, + 0x2F2C, 0x206C, 0x003C, 0x0026, 0x0050, 0x1880, 0x301F, 0x2200, + 0x660C, 0xFFDA, 0x0038, 0x6602, 0x302C, 0x200C, 0x2D6E, 0x4240, + 0xFFE2, 0xA9F0, 0xFF00, 0x377C, 0xE580, 0xFFDC, 0x4868, 0x594F, + 0x0034, 0x3E1F, 0x6008, 0x2F06, 0xFFDE, 0x600A, 0x7002, 0x0032, + 0xFFCC, 0x0080, 0x2251, 0x101F, 0x317C, 0xA029, 0xFFD8, 0x5240, + 0x0100, 0x6710, 0xA023, 0xFFCE, 0xFFD4, 0x2006, 0x4878, 0x002E, + 0x504F, 0x43FA, 0x6712, 0x7600, 0x41E8, 0x4A6E, 0x20D9, 0x005A, + 0x7FFF, 0x51CA, 0x005C, 0x2E00, 0x0240, 0x48C7, 0x6714, 0x0C80, + 0x2E9F, 0xFFD6, 0x8000, 0x1000, 0x4842, 0x4A6B, 0xFFD2, 0x0048, + 0x4A47, 0x4ED1, 0x206F, 0x0041, 0x600C, 0x2A78, 0x422E, 0x3200, + 0x6574, 0x6716, 0x0044, 0x486D, 0x2008, 0x486C, 0x0B7C, 0x2640, + 0x0400, 0x0068, 0x206D, 0x000D, 0x2A40, 0x000B, 0x003E, 0x0220 +) + + +def DecodeMaskedWords(src, dst, pos, n, tab, mask): + '''Decode n words using the specified lookup table under control of mask. + Return new bitstream position. + ''' + if mask == 0: # all mask bits are zero? + nBytes = n * 2 + dst.extend(src[pos:pos+nBytes]) # copy over n*2 bytes + pos += nBytes + else: + for bn in range(7, 7 - n, -1): + if mask & (1 << bn): # mask bit set? + word = tab[src[pos]] # decode next word with LUT + dst.extend(word.to_bytes(2, 'big')) # write it to dst + pos += 1 + else: # otherwise, copy over that word + dst.extend(src[pos:pos+2]) + pos += 2 + return pos + + +def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): + '''Decompress resource data from src to dst. + + Parameters: + src source buffer containing compressed data + dst destination buffer, must be bytearray to work properly + unpackSize size in bytes of the unpacked resource data + tabSize size of the embedded lookup table + comprFlags compression flags from the extended resource header + ''' + hasDynamicTab = comprFlags & 1 + isBitmapped = comprFlags & 2 + print("tabSize: %d" % tabSize) + print("comprFlags: 0x%X, dynamic table: %s, bitmap data: %s" % (comprFlags, + "yes" if hasDynamicTab else "no", "yes" if isBitmapped else "no")) + + if hasDynamicTab: + nEntries = tabSize + 1 + pos = nEntries * 2 + dynamicLUT = struct.unpack(">" + str(nEntries) + "H", src[:pos]) + # dump dynamic LUT + if 0: + for idx, elem in enumerate(dynamicLUT): + if idx and not idx & 3: + print(",") + else: + print(", ", end="") + print("0x%04X" % elem, end="") + print("") + else: + pos = 0 + + LUT = dynamicLUT if hasDynamicTab else GreggDefLUT + nWords = unpackSize >> 1 + hasExtraByte = unpackSize & 1 + + if isBitmapped: + nRuns = nWords >> 3 + for idx in range(nRuns): + mask = src[pos] + pos += 1 + #print("Mask for run #%d: 0x%X" % (idx, bitmap)) + + pos = DecodeMaskedWords(src, dst, pos, 8, LUT, mask) + + if nWords & 7: + trailingWords = nWords & 7 + lastMask = src[pos] + pos += 1 + #print("Last mask: 0x%X, trailing words: %d" % (lastMask, trailingWords)) + pos = DecodeMaskedWords(src, dst, pos, trailingWords, LUT, lastMask) + else: + for i in range(nWords): + word = LUT[src[pos]] # decode next word with LUT + dst.extend(word.to_bytes(2, 'big')) # write it to dst + pos += 1 + + if hasExtraByte: # have a got an extra byte at the end? + dst.expand(src[pos]) # copy it over + pos += 1 + + #print("Last input position: %d" % pos) diff --git a/README.md b/README.md new file mode 100644 index 0000000..88cb5e4 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## About this repository + +This repository contains Python tools for dealing with compressed MacOS resources. + +It's also an attempt to document the undocumented “dcmp” mechanism in System 7 +including all required data structures and compression algorithms. + +For the moment being, the code accepts only binary files as input. This can be +easily updated to process data from streams and memory-based arrays. + +The following algorithms are currently supported: +- GreggyBits + +Requires Python 3. + +### Usage: + python ResDecompress.py [input file] + +If no input file was specified, the "Compressed" file resided in the same folder +will be processed by default. + +At the end, a "Dump" file will be generated containing decompressed data. diff --git a/ResDecompress.py b/ResDecompress.py new file mode 100644 index 0000000..54acc5f --- /dev/null +++ b/ResDecompress.py @@ -0,0 +1,53 @@ +''' + Handling of compressed MacOS resources. + Author: Max Poliakovski 2018 +''' +import struct +import sys + +from GreggDecompress import GreggDecompress + + +def DecompressResource(inf): + # get the extended resource header first + hdrFields = struct.unpack(">IHBBI", inf.read(12)) + if hdrFields[0] != 0xA89F6572: + print("Invalid extended resource header sig: 0x%X" % hdrFields[0]) + if hdrFields[1] != 18: + print("Suspicious extended resource header length: %d" % hdrFields[1]) + if hdrFields[2] != 8 and hdrFields[2] != 9: + print("Unknown ext res header format: %d" % hdrFields[2]) + if (hdrFields[3] & 1) == 0: + print("extAttributes,bit0 isn't set. Treat this res as uncompressed.") + + print("Uncompressed length: %d" % hdrFields[4]) + + if hdrFields[2] == 8: + DonnSpecific = struct.unpack(">BBHH", inf.read(6)) + print("DonnDecompress isn't supported yet.") + exit() + else: + GreggSpecific = struct.unpack(">HHBB", inf.read(6)) + + fsize = inf.seek(0, 2) + print("Compressed size: %d" % fsize) + inf.seek(hdrFields[1], 0) # rewind to the start of compressed data + + dstBuf = bytearray() + srcBuf = inf.read(fsize - hdrFields[1]) + + # invoke GreggyBits decompressor and pass over required header data + GreggDecompress(srcBuf, dstBuf, hdrFields[4], GreggSpecific[2], GreggSpecific[3]) + + with open("Dump", 'wb') as outstream: + outstream.write(dstBuf) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + file = "Compressed" + else: + file = sys.argv[1] + + with open(file, 'rb') as instream: + DecompressResource(instream) From 0d8c1f301331d37aa0e77f1c10ab5db217027ba1 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Tue, 4 Dec 2018 14:49:30 +0100 Subject: [PATCH 02/11] Fix table formatting of the previous commit. --- GreggDecompress.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/GreggDecompress.py b/GreggDecompress.py index 690b7c8..6687823 100644 --- a/GreggDecompress.py +++ b/GreggDecompress.py @@ -9,35 +9,35 @@ import struct GreggDefLUT = ( 0x0000, 0x0008, 0x4EBA, 0x206E, 0x4E75, 0x000C, 0x0004, 0x7000, 0x0010, 0x0002, 0x486E, 0xFFFC, 0x6000, 0x0001, 0x48E7, 0x2F2E, - 0x4E56, 0x0006, 0x4E5E, 0x2F00, 0x6100, 0xFFF8, 0x2F0B, 0xFFFF, + 0x4E56, 0x0006, 0x4E5E, 0x2F00, 0x6100, 0xFFF8, 0x2F0B, 0xFFFF, 0x0014, 0x000A, 0x0018, 0x205F, 0x000E, 0x2050, 0x3F3C, 0xFFF4, - 0x4CEE, 0x302E, 0x6700, 0x4CDF, 0x266E, 0x0012, 0x001C, 0x4267, + 0x4CEE, 0x302E, 0x6700, 0x4CDF, 0x266E, 0x0012, 0x001C, 0x4267, 0xFFF0, 0x303C, 0x2F0C, 0x0003, 0x4ED0, 0x0020, 0x7001, 0x0016, - 0x2D40, 0x48C0, 0x2078, 0x7200, 0x588F, 0x6600, 0x4FEF, 0x42A7, + 0x2D40, 0x48C0, 0x2078, 0x7200, 0x588F, 0x6600, 0x4FEF, 0x42A7, 0x6706, 0xFFFA, 0x558F, 0x286E, 0x3F00, 0xFFFE, 0x2F3C, 0x6704, - 0x598F, 0x206B, 0x0024, 0x201F, 0x41FA, 0x81E1, 0x6604, 0x6708, + 0x598F, 0x206B, 0x0024, 0x201F, 0x41FA, 0x81E1, 0x6604, 0x6708, 0x001A, 0x4EB9, 0x508F, 0x202E, 0x0007, 0x4EB0, 0xFFF2, 0x3D40, - 0x001E, 0x2068, 0x6606, 0xFFF6, 0x4EF9, 0x0800, 0x0C40, 0x3D7C, + 0x001E, 0x2068, 0x6606, 0xFFF6, 0x4EF9, 0x0800, 0x0C40, 0x3D7C, 0xFFEC, 0x0005, 0x203C, 0xFFE8, 0xDEFC, 0x4A2E, 0x0030, 0x0028, - 0x2F08, 0x200B, 0x6002, 0x426E, 0x2D48, 0x2053, 0x2040, 0x1800, + 0x2F08, 0x200B, 0x6002, 0x426E, 0x2D48, 0x2053, 0x2040, 0x1800, 0x6004, 0x41EE, 0x2F28, 0x2F01, 0x670A, 0x4840, 0x2007, 0x6608, - 0x0118, 0x2F07, 0x3028, 0x3F2E, 0x302B, 0x226E, 0x2F2B, 0x002C, + 0x0118, 0x2F07, 0x3028, 0x3F2E, 0x302B, 0x226E, 0x2F2B, 0x002C, 0x670C, 0x225F, 0x6006, 0x00FF, 0x3007, 0xFFEE, 0x5340, 0x0040, - 0xFFE4, 0x4A40, 0x660A, 0x000F, 0x4EAD, 0x70FF, 0x22D8, 0x486B, + 0xFFE4, 0x4A40, 0x660A, 0x000F, 0x4EAD, 0x70FF, 0x22D8, 0x486B, 0x0022, 0x204B, 0x670E, 0x4AAE, 0x4E90, 0xFFE0, 0xFFC0, 0x002A, - 0x2740, 0x6702, 0x51C8, 0x02B6, 0x487A, 0x2278, 0xB06E, 0xFFE6, + 0x2740, 0x6702, 0x51C8, 0x02B6, 0x487A, 0x2278, 0xB06E, 0xFFE6, 0x0009, 0x322E, 0x3E00, 0x4841, 0xFFEA, 0x43EE, 0x4E71, 0x7400, - 0x2F2C, 0x206C, 0x003C, 0x0026, 0x0050, 0x1880, 0x301F, 0x2200, + 0x2F2C, 0x206C, 0x003C, 0x0026, 0x0050, 0x1880, 0x301F, 0x2200, 0x660C, 0xFFDA, 0x0038, 0x6602, 0x302C, 0x200C, 0x2D6E, 0x4240, - 0xFFE2, 0xA9F0, 0xFF00, 0x377C, 0xE580, 0xFFDC, 0x4868, 0x594F, + 0xFFE2, 0xA9F0, 0xFF00, 0x377C, 0xE580, 0xFFDC, 0x4868, 0x594F, 0x0034, 0x3E1F, 0x6008, 0x2F06, 0xFFDE, 0x600A, 0x7002, 0x0032, - 0xFFCC, 0x0080, 0x2251, 0x101F, 0x317C, 0xA029, 0xFFD8, 0x5240, + 0xFFCC, 0x0080, 0x2251, 0x101F, 0x317C, 0xA029, 0xFFD8, 0x5240, 0x0100, 0x6710, 0xA023, 0xFFCE, 0xFFD4, 0x2006, 0x4878, 0x002E, - 0x504F, 0x43FA, 0x6712, 0x7600, 0x41E8, 0x4A6E, 0x20D9, 0x005A, + 0x504F, 0x43FA, 0x6712, 0x7600, 0x41E8, 0x4A6E, 0x20D9, 0x005A, 0x7FFF, 0x51CA, 0x005C, 0x2E00, 0x0240, 0x48C7, 0x6714, 0x0C80, - 0x2E9F, 0xFFD6, 0x8000, 0x1000, 0x4842, 0x4A6B, 0xFFD2, 0x0048, + 0x2E9F, 0xFFD6, 0x8000, 0x1000, 0x4842, 0x4A6B, 0xFFD2, 0x0048, 0x4A47, 0x4ED1, 0x206F, 0x0041, 0x600C, 0x2A78, 0x422E, 0x3200, - 0x6574, 0x6716, 0x0044, 0x486D, 0x2008, 0x486C, 0x0B7C, 0x2640, + 0x6574, 0x6716, 0x0044, 0x486D, 0x2008, 0x486C, 0x0B7C, 0x2640, 0x0400, 0x0068, 0x206D, 0x000D, 0x2A40, 0x000B, 0x003E, 0x0220 ) From 6c81808433d9eb6c696e330081c2c425add4d351 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Tue, 11 Dec 2018 17:12:06 +0100 Subject: [PATCH 03/11] Implement a compressor for GreggBits scheme. --- GreggDecompress.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++ ResDecompress.py | 17 ++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/GreggDecompress.py b/GreggDecompress.py index 6687823..67c05d5 100644 --- a/GreggDecompress.py +++ b/GreggDecompress.py @@ -42,6 +42,29 @@ GreggDefLUT = ( ) +def EncodeMaskedWords(src, dst, pos, n, tab): + mask = 0 + encoded = bytearray() + + for rPos in range(n): + mask <<= 1 + word = src[pos+rPos] + compressed = 1 if word in tab else 0 + mask |= compressed + if compressed: # replace word with table index + encoded.append(tab.index(word)) + else: # otherwise, just copy unencoded data over + encoded.extend(word.to_bytes(2, 'big')) + + if n < 8: # left-justify the mask for n < 8 + mask <<= 8 - n + + dst.append(mask) + dst.extend(encoded) + + return pos+n + + def DecodeMaskedWords(src, dst, pos, n, tab, mask): '''Decode n words using the specified lookup table under control of mask. Return new bitstream position. @@ -124,3 +147,67 @@ def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): pos += 1 #print("Last input position: %d" % pos) + + +def GreggCompress(src, dst, unpackSize, customTab=False, isBitmapped=False): + if customTab: + # convert input bytes into an array of words + nWords = unpackSize >> 1 + inWords = struct.unpack(">" + str(nWords) + "H", src[:nWords*2]) + + # count occurence of each word + from collections import Counter + wordsCounts = Counter(inWords) + + # grab word counts in descending order (most frequent comes first) + sortedCounts = list(set(wordsCounts.values())) + sortedCounts.sort(reverse=True) + + # now we're ready to construct a table of 256 most common words + embeddedTab = list() + nElems = 0 + + for cnt in sortedCounts: + # pick all words for a specific count and sort them in descending order + words = [key for (key, value) in wordsCounts.items() if value == cnt] + words.sort(reverse=True) + + # append them to the table, stop when tableMax is reached + if nElems + len(words) < 256: + embeddedTab.extend(words) + nElems += len(words) + else: + remainedElems = 256 - nElems + embeddedTab.extend(words[:remainedElems]) + nElems += remainedElems + + if nElems >= 256: + break + + if 0: # dump the resulting table + for idx, elem in enumerate(embeddedTab): + if idx and not idx & 3: + print(",") + else: + print(", ", end="") + print("0x%04X" % elem, end="") + print("") + + # write the constructed table into output + for word in embeddedTab: + dst.extend(word.to_bytes(2, 'big')) + + if isBitmapped: + pos = 0 + nRuns = nWords >> 3 + + for idx in range(nRuns): + pos = EncodeMaskedWords(inWords, dst, pos, 8, embeddedTab) + + if nWords & 7: + pos = EncodeMaskedWords(inWords, dst, pos, nWords & 7, embeddedTab) + + if unpackSize & 1: # copy over last byte in the case of odd length + dst.append(src[-1]) + else: + print("Non-bitmapped compression not yet implemented") diff --git a/ResDecompress.py b/ResDecompress.py index 54acc5f..8fd60fe 100644 --- a/ResDecompress.py +++ b/ResDecompress.py @@ -5,7 +5,7 @@ import struct import sys -from GreggDecompress import GreggDecompress +from GreggDecompress import GreggDecompress, GreggCompress def DecompressResource(inf): @@ -42,6 +42,21 @@ def DecompressResource(inf): with open("Dump", 'wb') as outstream: outstream.write(dstBuf) + # re-compress + recompBuf = bytearray() + + # re-create extended resource header + recompBuf.extend([0xA8, 0x9F, 0x65, 0x72, 0x00, 0x12, 0x09, 0x01]) + recompBuf.extend(hdrFields[4].to_bytes(4, 'big')) + recompBuf.extend([0x00, 0x02, 0x00, 0x00]) + recompBuf.append(GreggSpecific[2]) + recompBuf.append(GreggSpecific[3]) + + GreggCompress(dstBuf, recompBuf, hdrFields[4], customTab=True, isBitmapped=True) + + with open("RecompDump", 'wb') as outstream: + outstream.write(recompBuf) + if __name__ == "__main__": if len(sys.argv) < 2: From 2f94e88cec0a3ebf5e324cc2bdd210f358caaa3f Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Tue, 11 Dec 2018 17:15:05 +0100 Subject: [PATCH 04/11] Rename GreggDecompress.py to GreggBits.py. That's because both compression and decompression are supported for now. --- GreggDecompress.py => GreggBits.py | 0 ResDecompress.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename GreggDecompress.py => GreggBits.py (100%) diff --git a/GreggDecompress.py b/GreggBits.py similarity index 100% rename from GreggDecompress.py rename to GreggBits.py diff --git a/ResDecompress.py b/ResDecompress.py index 8fd60fe..27c9a5e 100644 --- a/ResDecompress.py +++ b/ResDecompress.py @@ -5,7 +5,7 @@ import struct import sys -from GreggDecompress import GreggDecompress, GreggCompress +from GreggBits import GreggDecompress, GreggCompress def DecompressResource(inf): From 7f14013fa3e8e598c16a04cc35345570cf429054 Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 12:45:47 +0800 Subject: [PATCH 05/11] Bugfix: use 'append' method for extra byte. --- GreggBits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GreggBits.py b/GreggBits.py index 67c05d5..2da25ee 100644 --- a/GreggBits.py +++ b/GreggBits.py @@ -143,7 +143,7 @@ def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): pos += 1 if hasExtraByte: # have a got an extra byte at the end? - dst.expand(src[pos]) # copy it over + dst.append(src[pos]) # copy it over pos += 1 #print("Last input position: %d" % pos) From eacebc8320857c76662e022686fddaf1fdb42174 Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 12:46:49 +0800 Subject: [PATCH 06/11] Let functions create/parse their own res header. --- GreggBits.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/GreggBits.py b/GreggBits.py index 2da25ee..86be828 100644 --- a/GreggBits.py +++ b/GreggBits.py @@ -85,16 +85,19 @@ def DecodeMaskedWords(src, dst, pos, n, tab, mask): return pos -def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): +def GreggDecompress(src, dst, unpackSize, pos=0): '''Decompress resource data from src to dst. Parameters: src source buffer containing compressed data dst destination buffer, must be bytearray to work properly unpackSize size in bytes of the unpacked resource data - tabSize size of the embedded lookup table - comprFlags compression flags from the extended resource header + pos offset to my Gregg-specific buffer in src ''' + + _dcmp, _slop, tabSize, comprFlags = struct.unpack_from(">HHBB", src, pos) + pos += 6 + hasDynamicTab = comprFlags & 1 isBitmapped = comprFlags & 2 print("tabSize: %d" % tabSize) @@ -103,8 +106,8 @@ def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): if hasDynamicTab: nEntries = tabSize + 1 - pos = nEntries * 2 - dynamicLUT = struct.unpack(">" + str(nEntries) + "H", src[:pos]) + dynamicLUT = struct.unpack_from(">" + str(nEntries) + "H", src, pos) + pos += nEntries * 2 # dump dynamic LUT if 0: for idx, elem in enumerate(dynamicLUT): @@ -114,8 +117,6 @@ def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): print(", ", end="") print("0x%04X" % elem, end="") print("") - else: - pos = 0 LUT = dynamicLUT if hasDynamicTab else GreggDefLUT nWords = unpackSize >> 1 @@ -149,10 +150,14 @@ def GreggDecompress(src, dst, unpackSize, tabSize, comprFlags): #print("Last input position: %d" % pos) -def GreggCompress(src, dst, unpackSize, customTab=False, isBitmapped=False): - if customTab: +def GreggCompress(src, dst, customTab='auto', isBitmapped='auto'): + # future addition + customTab = True # so the big code path gets tested! + isBitmapped = True # required for now + + if customTab: # calculate, and if necessary, resolve 'auto' # convert input bytes into an array of words - nWords = unpackSize >> 1 + nWords = len(src) >> 1 inWords = struct.unpack(">" + str(nWords) + "H", src[:nWords*2]) # count occurence of each word @@ -193,6 +198,14 @@ def GreggCompress(src, dst, unpackSize, customTab=False, isBitmapped=False): print("0x%04X" % elem, end="") print("") + # here, decide whether 'auto' customTab should be on or off! + + # write out the header + isBitmapped = True # will need to resolve this later + flags = customTab * 1 + isBitmapped * 2 + dst.extend(struct.pack(">HHBB", 2, 0, len(embeddedTab)-1, flags)) + + if customTab: # write the constructed table into output for word in embeddedTab: dst.extend(word.to_bytes(2, 'big')) @@ -207,7 +220,7 @@ def GreggCompress(src, dst, unpackSize, customTab=False, isBitmapped=False): if nWords & 7: pos = EncodeMaskedWords(inWords, dst, pos, nWords & 7, embeddedTab) - if unpackSize & 1: # copy over last byte in the case of odd length + if len(src) & 1: # copy over last byte in the case of odd length dst.append(src[-1]) else: print("Non-bitmapped compression not yet implemented") From ddfb909212f20319360ee02e09f67ed3d73447eb Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 12:50:40 +0800 Subject: [PATCH 07/11] Replace testbed with a friendly API. The GetEncoding function will allow macresources to cheaply and simply determine resource encoding without committing to a time-consuming extraction. --- ResDecompress.py | 102 +++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/ResDecompress.py b/ResDecompress.py index 27c9a5e..4f3783d 100644 --- a/ResDecompress.py +++ b/ResDecompress.py @@ -3,66 +3,64 @@ Author: Max Poliakovski 2018 ''' import struct -import sys from GreggBits import GreggDecompress, GreggCompress -def DecompressResource(inf): - # get the extended resource header first - hdrFields = struct.unpack(">IHBBI", inf.read(12)) - if hdrFields[0] != 0xA89F6572: - print("Invalid extended resource header sig: 0x%X" % hdrFields[0]) - if hdrFields[1] != 18: - print("Suspicious extended resource header length: %d" % hdrFields[1]) - if hdrFields[2] != 8 and hdrFields[2] != 9: - print("Unknown ext res header format: %d" % hdrFields[2]) - if (hdrFields[3] & 1) == 0: +def GetEncoding(dat): + sig, hdrlen, vers, attrs, biglen = struct.unpack_from(">IHBBI", dat) + if sig != 0xA89F6572: + print("Invalid extended resource header sig: 0x%X" % sig) + return 'UnknownCompression' + if vers not in (8, 9): + print("Unknown ext res header format: %d" % vers) + return 'UnknownCompression' + if attrs & 1 == 0: print("extAttributes,bit0 isn't set. Treat this res as uncompressed.") + return 'UnknownCompression' - print("Uncompressed length: %d" % hdrFields[4]) + print("Uncompressed length: %d" % biglen) - if hdrFields[2] == 8: - DonnSpecific = struct.unpack(">BBHH", inf.read(6)) - print("DonnDecompress isn't supported yet.") - exit() + if vers == 8: + print('Donn unimplemented!'); return 'UnknownCompression' + return 'DonnBits' + elif vers == 9: + if dat[12:14] == b'\x00\x02': + return 'GreggyBits' + else: + return 'UnknownCompression' else: - GreggSpecific = struct.unpack(">HHBB", inf.read(6)) - - fsize = inf.seek(0, 2) - print("Compressed size: %d" % fsize) - inf.seek(hdrFields[1], 0) # rewind to the start of compressed data - - dstBuf = bytearray() - srcBuf = inf.read(fsize - hdrFields[1]) - - # invoke GreggyBits decompressor and pass over required header data - GreggDecompress(srcBuf, dstBuf, hdrFields[4], GreggSpecific[2], GreggSpecific[3]) - - with open("Dump", 'wb') as outstream: - outstream.write(dstBuf) - - # re-compress - recompBuf = bytearray() - - # re-create extended resource header - recompBuf.extend([0xA8, 0x9F, 0x65, 0x72, 0x00, 0x12, 0x09, 0x01]) - recompBuf.extend(hdrFields[4].to_bytes(4, 'big')) - recompBuf.extend([0x00, 0x02, 0x00, 0x00]) - recompBuf.append(GreggSpecific[2]) - recompBuf.append(GreggSpecific[3]) - - GreggCompress(dstBuf, recompBuf, hdrFields[4], customTab=True, isBitmapped=True) - - with open("RecompDump", 'wb') as outstream: - outstream.write(recompBuf) + return 'UnknownCompression' -if __name__ == "__main__": - if len(sys.argv) < 2: - file = "Compressed" - else: - file = sys.argv[1] +def DecompressResource(dat): + encoding = GetEncoding(dat) + sig, hdrlen, vers, attrs, biglen = struct.unpack_from(">IHBBI", dat) - with open(file, 'rb') as instream: - DecompressResource(instream) + if encoding == 'DonnBits': + raise NotImplementedError('DonnBits') + + elif encoding == 'GreggyBits': + dst = bytearray() + GreggDecompress(dat, dst, unpackSize=biglen, pos=12) + return bytes(dst) + + elif encoding == 'UnknownCompression': + return dat # passthru + + +def CompressResource(dat, encoding): + if encoding == 'UnknownCompression': + return dat + + elif encoding == 'GreggyBits': + dst = bytearray() + + # re-create extended resource header + dst.extend([0xA8, 0x9F, 0x65, 0x72, 0x00, 0x12, 0x09, 0x01]) + dst.extend(len(dat).to_bytes(4, 'big')) + + # leave Gregg-specific header to the compressor + GreggCompress(dat, dst) + + return bytes(dst) From c752c70c6a72e1a39e612a8c47b746dc0effbee4 Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 12:51:38 +0800 Subject: [PATCH 08/11] Add simple tests (run ./pytest). --- test_everything.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test_everything.py diff --git a/test_everything.py b/test_everything.py new file mode 100644 index 0000000..580c550 --- /dev/null +++ b/test_everything.py @@ -0,0 +1,13 @@ +from ResDecompress import GetEncoding, DecompressResource, CompressResource + +def compress_then_extract(dat, encoding): + a = CompressResource(dat, encoding); + if encoding != 'UnknownCompression': assert a.startswith(b'\xA8\x9F\x65\x72') + assert GetEncoding(a) == encoding + b = DecompressResource(a) + assert b == dat + +def test_all(): + for enc in ['GreggyBits', 'UnknownCompression']: + compress_then_extract(bytes(100), encoding=enc) + compress_then_extract(b'The quick brown fox jumps over the lazy dog', encoding=enc) From 08b64758d45a6cad332c9faf887c0f85662309b0 Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 15:43:53 +0800 Subject: [PATCH 09/11] Format as git package. --- ResDecompress.py | 2 +- __init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 __init__.py diff --git a/ResDecompress.py b/ResDecompress.py index 4f3783d..d3a4b97 100644 --- a/ResDecompress.py +++ b/ResDecompress.py @@ -4,7 +4,7 @@ ''' import struct -from GreggBits import GreggDecompress, GreggCompress +from .GreggBits import GreggDecompress, GreggCompress def GetEncoding(dat): diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b74d8a5 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from .ResDecompress import GetEncoding, DecompressResource, CompressResource From b7739a449b39fa1294cfb6590bf3686188c1a4a4 Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 13 Dec 2018 15:48:04 +0800 Subject: [PATCH 10/11] Stop polluting stdout. --- GreggBits.py | 8 ++++---- ResDecompress.py | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/GreggBits.py b/GreggBits.py index 86be828..fdcff8a 100644 --- a/GreggBits.py +++ b/GreggBits.py @@ -100,9 +100,9 @@ def GreggDecompress(src, dst, unpackSize, pos=0): hasDynamicTab = comprFlags & 1 isBitmapped = comprFlags & 2 - print("tabSize: %d" % tabSize) - print("comprFlags: 0x%X, dynamic table: %s, bitmap data: %s" % (comprFlags, - "yes" if hasDynamicTab else "no", "yes" if isBitmapped else "no")) + # print("tabSize: %d" % tabSize) + # print("comprFlags: 0x%X, dynamic table: %s, bitmap data: %s" % (comprFlags, + # "yes" if hasDynamicTab else "no", "yes" if isBitmapped else "no")) if hasDynamicTab: nEntries = tabSize + 1 @@ -223,4 +223,4 @@ def GreggCompress(src, dst, customTab='auto', isBitmapped='auto'): if len(src) & 1: # copy over last byte in the case of odd length dst.append(src[-1]) else: - print("Non-bitmapped compression not yet implemented") + raise ValueError("Non-bitmapped compression not yet implemented") diff --git a/ResDecompress.py b/ResDecompress.py index d3a4b97..93095ea 100644 --- a/ResDecompress.py +++ b/ResDecompress.py @@ -10,20 +10,19 @@ from .GreggBits import GreggDecompress, GreggCompress def GetEncoding(dat): sig, hdrlen, vers, attrs, biglen = struct.unpack_from(">IHBBI", dat) if sig != 0xA89F6572: - print("Invalid extended resource header sig: 0x%X" % sig) + # print("Invalid extended resource header sig: 0x%X" % sig) return 'UnknownCompression' if vers not in (8, 9): - print("Unknown ext res header format: %d" % vers) + # print("Unknown ext res header format: %d" % vers) return 'UnknownCompression' if attrs & 1 == 0: - print("extAttributes,bit0 isn't set. Treat this res as uncompressed.") + # print("extAttributes,bit0 isn't set. Treat this res as uncompressed.") return 'UnknownCompression' - print("Uncompressed length: %d" % biglen) + # print("Uncompressed length: %d" % biglen) if vers == 8: - print('Donn unimplemented!'); return 'UnknownCompression' - return 'DonnBits' + return 'UnknownCompression' # return 'DonnBits' elif vers == 9: if dat[12:14] == b'\x00\x02': return 'GreggyBits' From 969493be9f6404e9ec1b3a609f95add4f38a285e Mon Sep 17 00:00:00 2001 From: Elliot Nunn Date: Thu, 10 Jan 2019 13:27:30 +0800 Subject: [PATCH 11/11] Prepare for merge into macresources --- .gitignore | 11 --------- Compressed | Bin 15828 -> 0 bytes Expanded | Bin 21174 -> 0 bytes README.md | 22 ------------------ __init__.py | 1 - GreggBits.py => macresources/GreggBits.py | 0 .../compression.py | 0 test_everything.py => test_compress.py | 2 +- 8 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 .gitignore delete mode 100644 Compressed delete mode 100644 Expanded delete mode 100644 README.md delete mode 100644 __init__.py rename GreggBits.py => macresources/GreggBits.py (100%) rename ResDecompress.py => macresources/compression.py (100%) rename test_everything.py => test_compress.py (84%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 342438e..0000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Files to be ignored by Git. - -.git - -# Python auto-generated files and folders. -/__pycache__ -Dump - -# System files. -.DS_Store -Thumb.db diff --git a/Compressed b/Compressed deleted file mode 100644 index bdfce82f453f3562fe64a2ed9c51d4048120f367..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15828 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 diff --git a/Expanded b/Expanded deleted file mode 100644 index 7aaecedd9505abd1df0d84211f735e27060edf0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21174 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 diff --git a/README.md b/README.md deleted file mode 100644 index 88cb5e4..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -## About this repository - -This repository contains Python tools for dealing with compressed MacOS resources. - -It's also an attempt to document the undocumented “dcmp” mechanism in System 7 -including all required data structures and compression algorithms. - -For the moment being, the code accepts only binary files as input. This can be -easily updated to process data from streams and memory-based arrays. - -The following algorithms are currently supported: -- GreggyBits - -Requires Python 3. - -### Usage: - python ResDecompress.py [input file] - -If no input file was specified, the "Compressed" file resided in the same folder -will be processed by default. - -At the end, a "Dump" file will be generated containing decompressed data. diff --git a/__init__.py b/__init__.py deleted file mode 100644 index b74d8a5..0000000 --- a/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ResDecompress import GetEncoding, DecompressResource, CompressResource diff --git a/GreggBits.py b/macresources/GreggBits.py similarity index 100% rename from GreggBits.py rename to macresources/GreggBits.py diff --git a/ResDecompress.py b/macresources/compression.py similarity index 100% rename from ResDecompress.py rename to macresources/compression.py diff --git a/test_everything.py b/test_compress.py similarity index 84% rename from test_everything.py rename to test_compress.py index 580c550..fb3e5ea 100644 --- a/test_everything.py +++ b/test_compress.py @@ -1,4 +1,4 @@ -from ResDecompress import GetEncoding, DecompressResource, CompressResource +from .macresources.compression import GetEncoding, DecompressResource, CompressResource def compress_then_extract(dat, encoding): a = CompressResource(dat, encoding);