From 3c8eec038cc6ec1e0eea4b0e634cfcaccf33459f Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Fri, 27 Oct 2023 14:46:30 -0400 Subject: [PATCH] Use a FileHandle instead of loading whole PE --- Whisky/Models/Bottle.swift | 4 +- WhiskyKit/Extensions/DataExtensions.swift | 33 ++--- WhiskyKit/PE/BitmapInfo.swift | 54 ++++---- WhiskyKit/PE/PortableExecutable.swift | 150 ++++++++++++---------- WhiskyKit/PE/ResourceSection.swift | 60 +++++---- WhiskyKit/PE/ShellLink.swift | 52 ++++---- WhiskyKit/Whisky/Program.swift | 2 +- WhiskyThumbnail/ThumbnailProvider.swift | 2 +- 8 files changed, 182 insertions(+), 175 deletions(-) diff --git a/Whisky/Models/Bottle.swift b/Whisky/Models/Bottle.swift index 0d05f0e24..2cb5ef5c5 100644 --- a/Whisky/Models/Bottle.swift +++ b/Whisky/Models/Bottle.swift @@ -68,7 +68,9 @@ extension Bottle { for link in linkURLs { do { - if let program = ShellLinkHeader.getProgram(url: link, data: try Data(contentsOf: link), bottle: self) { + if let program = ShellLinkHeader.getProgram(url: link, + handle: try FileHandle(forReadingFrom: link), + bottle: self) { if !startMenuPrograms.contains(where: { $0.url == program.url }) { startMenuPrograms.append(program) try FileManager.default.removeItem(at: link) diff --git a/WhiskyKit/Extensions/DataExtensions.swift b/WhiskyKit/Extensions/DataExtensions.swift index c981ac56c..4e46ae2d3 100644 --- a/WhiskyKit/Extensions/DataExtensions.swift +++ b/WhiskyKit/Extensions/DataExtensions.swift @@ -18,34 +18,17 @@ import Foundation -extension Data { +extension FileHandle { public func extract(_ type: T.Type, offset: Int = 0) -> T? { - if offset + MemoryLayout.size < self.count { - let data = self[offset...size] - return data.withUnsafeBytes { $0.loadUnaligned(as: T.self) } - } else { + do { + try self.seek(toOffset: UInt64(offset)) + if let data = try self.read(upToCount: MemoryLayout.size) { + return data.withUnsafeBytes { $0.loadUnaligned(as: T.self)} + } + } catch { return nil } - } - // Thanks ChatGPT - public func nullTerminatedStrings(using encoding: String.Encoding = .utf8) -> [String] { - var strings = [String]() - self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in - if let baseAddress = ptr.baseAddress { - var strStart = baseAddress - let strEnd = baseAddress + self.count - while strStart < strEnd { - let strPtr = strStart.assumingMemoryBound(to: CChar.self) - let strLen = strnlen(strPtr, self.count) - let strData = Data(bytes: strPtr, count: strLen) - if let str = String(data: strData, encoding: encoding) { - strings.append(str) - } - strStart = strStart.advanced(by: strLen + 1) - } - } - } - return strings + return nil } } diff --git a/WhiskyKit/PE/BitmapInfo.swift b/WhiskyKit/PE/BitmapInfo.swift index ed8b5c0f3..e19626bd6 100644 --- a/WhiskyKit/PE/BitmapInfo.swift +++ b/WhiskyKit/PE/BitmapInfo.swift @@ -35,29 +35,29 @@ public struct BitmapInfoHeader: Hashable { public let originDirection: BitmapOriginDirection public let colorFormat: ColorFormat - init(data: Data, offset: Int) { + init(handle: FileHandle, offset: Int) { var offset = offset - self.size = data.extract(UInt32.self, offset: offset) ?? 0 + self.size = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.width = data.extract(Int32.self, offset: offset) ?? 0 + self.width = handle.extract(Int32.self, offset: offset) ?? 0 offset += 4 - self.height = data.extract(Int32.self, offset: offset) ?? 0 + self.height = handle.extract(Int32.self, offset: offset) ?? 0 offset += 4 - self.planes = data.extract(UInt16.self, offset: offset) ?? 0 + self.planes = handle.extract(UInt16.self, offset: offset) ?? 0 offset += 2 - self.bitCount = data.extract(UInt16.self, offset: offset) ?? 0 + self.bitCount = handle.extract(UInt16.self, offset: offset) ?? 0 offset += 2 - self.compression = BitmapCompression(rawValue: data.extract(UInt32.self, offset: offset) ?? 0) ?? .rgb + self.compression = BitmapCompression(rawValue: handle.extract(UInt32.self, offset: offset) ?? 0) ?? .rgb offset += 4 - self.sizeImage = data.extract(UInt32.self, offset: offset) ?? 0 + self.sizeImage = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.xPelsPerMeter = data.extract(Int32.self, offset: offset) ?? 0 + self.xPelsPerMeter = handle.extract(Int32.self, offset: offset) ?? 0 offset += 4 - self.yPelsPerMeter = data.extract(Int32.self, offset: offset) ?? 0 + self.yPelsPerMeter = handle.extract(Int32.self, offset: offset) ?? 0 offset += 4 - self.clrUsed = data.extract(UInt32.self, offset: offset) ?? 0 + self.clrUsed = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.clrImportant = data.extract(UInt32.self, offset: offset) ?? 0 + self.clrImportant = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 self.originDirection = self.height < 0 ? .upperLeft : .bottomLeft @@ -65,9 +65,9 @@ public struct BitmapInfoHeader: Hashable { } // swiftlint:disable:next cyclomatic_complexity function_body_length - func renderBitmap(data: Data, offset: Int) -> NSImage { + func renderBitmap(handle: FileHandle, offset: Int) -> NSImage { var offset = offset - let colorTable = buildColorTable(offset: &offset, data: data) + let colorTable = buildColorTable(offset: &offset, handle: handle) var pixels: [ColorQuad] = [] @@ -91,7 +91,7 @@ public struct BitmapInfoHeader: Hashable { // Ditto .indexed1 break case .indexed8: - let index = Int(data.extract(UInt8.self, offset: offset) ?? 0) + let index = Int(handle.extract(UInt8.self, offset: offset) ?? 0) if index >= colorTable.count { pixelRow.append(ColorQuad(red: 0, green: 0, blue: 0, alpha: 0)) } else { @@ -99,7 +99,7 @@ public struct BitmapInfoHeader: Hashable { } offset += 1 case .sampled16: - let sample = data.extract(UInt16.self, offset: offset) ?? 0 + let sample = handle.extract(UInt16.self, offset: offset) ?? 0 let red = sample & 0x001F let green = (sample & 0x03E0) >> 5 let blue = (sample & 0x7C00) >> 10 @@ -109,24 +109,24 @@ public struct BitmapInfoHeader: Hashable { alpha: 1)) offset += 2 case .sampled24: - let blue = data.extract(UInt8.self, offset: offset) ?? 0 + let blue = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 - let green = data.extract(UInt8.self, offset: offset) ?? 0 + let green = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 - let red = data.extract(UInt8.self, offset: offset) ?? 0 + let red = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 pixelRow.append(ColorQuad(red: red, green: green, blue: blue, alpha: 1)) case .sampled32: - let blue = data.extract(UInt8.self, offset: offset) ?? 0 + let blue = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 - let green = data.extract(UInt8.self, offset: offset) ?? 0 + let green = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 - let red = data.extract(UInt8.self, offset: offset) ?? 0 + let red = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 - let alpha = data.extract(UInt8.self, offset: offset) ?? 0 + let alpha = handle.extract(UInt8.self, offset: offset) ?? 0 offset += 1 pixelRow.append(ColorQuad(red: red, green: green, @@ -147,15 +147,15 @@ public struct BitmapInfoHeader: Hashable { return constructImage(pixels: pixels) } - func buildColorTable(offset: inout Int, data: Data) -> [ColorQuad] { + func buildColorTable(offset: inout Int, handle: FileHandle) -> [ColorQuad] { var colorTable: [ColorQuad] = [] for _ in 0.. 0 { - let dataOffset = Int(pointerToRawData) - self.data = data.subdata(in: dataOffset.. 0 { +// let dataOffset = Int(pointerToRawData) +// self.data = data.subdata(in: dataOffset.. NSImage? { diff --git a/WhiskyKit/PE/ResourceSection.swift b/WhiskyKit/PE/ResourceSection.swift index 0ff77eb18..09e599c73 100644 --- a/WhiskyKit/PE/ResourceSection.swift +++ b/WhiskyKit/PE/ResourceSection.swift @@ -25,12 +25,12 @@ public struct ResourceDirectoryEntry: Hashable { public let offsetToSubdirectory: UInt32 public let dataIsDirectory: Bool - init(data: Data, offset: Int) { + init(handle: FileHandle, offset: Int) { var offset = offset // Can be name or ID - self.id = data.extract(UInt32.self, offset: offset) ?? 0 + self.id = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.offsetToData = data.extract(UInt32.self, offset: offset) ?? 0 + self.offsetToData = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 self.dataIsDirectory = (offsetToData & 0x80000000) != 0 @@ -45,29 +45,35 @@ public struct ResourceDataEntry: Hashable { public let reserved: UInt32 public let icon: NSImage - init(data: Data, offset: Int, sectionTable: SectionTable) { + init(handle: FileHandle, offset: Int, sectionTable: SectionTable) { var offset = offset var icon = NSImage() - self.dataRVA = data.extract(UInt32.self, offset: offset) ?? 0 + self.dataRVA = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.size = data.extract(UInt32.self, offset: offset) ?? 0 + self.size = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.codePage = data.extract(UInt32.self, offset: offset) ?? 0 + self.codePage = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - self.reserved = data.extract(UInt32.self, offset: offset) ?? 0 + self.reserved = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 if let offsetToData = ResourceDataEntry.resolveRVA(rva: dataRVA, sectionTable: sectionTable) { - let bitmapInfo = BitmapInfoHeader(data: data, offset: Int(offsetToData)) + let bitmapInfo = BitmapInfoHeader(handle: handle, offset: Int(offsetToData)) if bitmapInfo.size != 40 { - let iconData = data.subdata(in: Int(offsetToData).. Program? { + public static func getProgram(url: URL, handle: FileHandle, bottle: Bottle) -> Program? { var offset: Int = 0 - let headerSize = data.extract(UInt32.self) ?? 0 + let headerSize = handle.extract(UInt32.self) ?? 0 // Move past headerSize and linkCLSID offset += 4 + 16 - let rawLinkFlags = data.extract(UInt32.self, offset: offset) ?? 0 + let rawLinkFlags = handle.extract(UInt32.self, offset: offset) ?? 0 let linkFlags = LinkFlags(rawValue: rawLinkFlags) offset = Int(headerSize) if linkFlags.contains(.hasLinkTargetIDList) { // We don't need this section so just get the size, and skip ahead - offset += Int(data.extract(UInt16.self, offset: offset) ?? 0) + 2 + offset += Int(handle.extract(UInt16.self, offset: offset) ?? 0) + 2 } if linkFlags.contains(.hasLinkInfo) { - let linkInfo = LinkInfo(data: data, + let linkInfo = LinkInfo(handle: handle, bottle: bottle, offset: &offset) return linkInfo.program @@ -61,34 +61,34 @@ public struct LinkInfo: Hashable { public var linkInfoFlags: LinkInfoFlags public var program: Program? - public init(data: Data, bottle: Bottle, offset: inout Int) { + public init(handle: FileHandle, bottle: Bottle, offset: inout Int) { let startOfSection = offset - let linkInfoSize = data.extract(UInt32.self, offset: offset) ?? 0 + let linkInfoSize = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - let linkInfoHeaderSize = data.extract(UInt32.self, offset: offset) ?? 0 + let linkInfoHeaderSize = handle.extract(UInt32.self, offset: offset) ?? 0 offset += 4 - let rawLinkInfoFlags = data.extract(UInt32.self, offset: offset) ?? 0 + let rawLinkInfoFlags = handle.extract(UInt32.self, offset: offset) ?? 0 linkInfoFlags = LinkInfoFlags(rawValue: rawLinkInfoFlags) if linkInfoFlags.contains(.volumeIDAndLocalBasePath) { if linkInfoHeaderSize >= 0x00000024 { offset += 20 - let localBasePathOffsetUnicode = data.extract(UInt32.self, offset: offset) ?? 0 + let localBasePathOffsetUnicode = handle.extract(UInt32.self, offset: offset) ?? 0 let localPathOffset = startOfSection + Int(localBasePathOffsetUnicode) - program = getProgram(data: data, + program = getProgram(handle: handle, offset: localPathOffset, bottle: bottle, unicode: true) } else { offset += 8 - let localBasePathOffset = data.extract(UInt32.self, offset: offset) ?? 0 + let localBasePathOffset = handle.extract(UInt32.self, offset: offset) ?? 0 let localPathOffset = startOfSection + Int(localBasePathOffset) - program = getProgram(data: data, + program = getProgram(handle: handle, offset: localPathOffset, bottle: bottle, unicode: false) @@ -98,17 +98,23 @@ public struct LinkInfo: Hashable { offset = startOfSection + Int(linkInfoSize) } - func getProgram(data: Data, offset: Int, bottle: Bottle, unicode: Bool) -> Program? { - let pathData = data[offset...] - if let nullRange = pathData.firstIndex(of: 0) { - let encoding: String.Encoding = unicode ? .utf16 : .windowsCP1254 - if var string = String(data: pathData[.. Program? { + do { + try handle.seek(toOffset: UInt64(offset)) + if let pathData = try handle.readToEnd() { + if let nullRange = pathData.firstIndex(of: 0) { + let encoding: String.Encoding = unicode ? .utf16 : .windowsCP1254 + if var string = String(data: pathData[.. Bool in