Skip to content

Commit

Permalink
Improve PE File & Program Icon Loading (#670)
Browse files Browse the repository at this point in the history
* Refactor PEFile

* Async BestIcon

* Remove canImport(AppKit)
  • Loading branch information
divadretlaw authored Jan 12, 2024
1 parent 969d69e commit 643ad89
Show file tree
Hide file tree
Showing 15 changed files with 732 additions and 501 deletions.
15 changes: 8 additions & 7 deletions Whisky/Views/Bottle/Pins/PinView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct PinView: View {
@State var pin: PinnedProgram
@Binding var path: NavigationPath

@State private var image: NSImage?
@State private var image: Image?
@State private var showRenameSheet = false
@State private var name: String = ""
@State private var opening: Bool = false
Expand All @@ -34,7 +34,7 @@ struct PinView: View {
VStack {
Group {
if let image = image {
Image(nsImage: image)
image
.resizable()
} else {
Image(systemName: "app.dashed")
Expand Down Expand Up @@ -82,13 +82,14 @@ struct PinView: View {
name = newName
}
}
.onAppear {
.task {
name = pin.name
Task.detached { @MainActor in
if let peFile = program.peFile {
image = peFile.bestIcon()
}
guard let peFile = program.peFile else { return }
let task = Task<Image?, Never>.detached {
guard let image = peFile.bestIcon() else { return nil }
return Image(nsImage: image)
}
self.image = await task.value
}
.onChange(of: name) {
if let index = bottle.settings.pins.firstIndex(where: { $0.url == pin.url }) {
Expand Down
13 changes: 8 additions & 5 deletions Whisky/Views/Programs/ProgramView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import UniformTypeIdentifiers

struct ProgramView: View {
@ObservedObject var program: Program
@State var image: NSImage?
@State var image: Image?
@State var programLoading: Bool = false
@AppStorage("configSectionExapnded") private var configSectionExpanded: Bool = true
@AppStorage("envArgsSectionExpanded") private var envArgsSectionExpanded: Bool = true
Expand Down Expand Up @@ -97,7 +97,7 @@ struct ProgramView: View {
ToolbarItem(placement: .navigation) {
Group {
if let icon = image {
Image(nsImage: icon)
icon
.resizable()
.frame(width: 25, height: 25)
} else {
Expand All @@ -109,10 +109,13 @@ struct ProgramView: View {
.padding(.trailing, 5)
}
}
.onAppear {
if let peFile = program.peFile {
image = peFile.bestIcon()
.task {
guard let peFile = program.peFile else { return }
let task = Task<Image?, Never>.detached {
guard let image = peFile.bestIcon() else { return nil }
return Image(nsImage: image)
}
self.image = await task.value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct BitmapInfoHeader: Hashable {
public let originDirection: BitmapOriginDirection
public let colorFormat: ColorFormat

init(handle: FileHandle, offset: Int) {
init(handle: FileHandle, offset: UInt64) {
var offset = offset
self.size = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
Expand Down Expand Up @@ -65,7 +65,7 @@ public struct BitmapInfoHeader: Hashable {
}

// swiftlint:disable:next cyclomatic_complexity function_body_length
func renderBitmap(handle: FileHandle, offset: Int) -> NSImage {
func renderBitmap(handle: FileHandle, offset: UInt64) -> NSImage? {
var offset = offset
let colorTable = buildColorTable(offset: &offset, handle: handle)

Expand Down Expand Up @@ -147,7 +147,7 @@ public struct BitmapInfoHeader: Hashable {
return constructImage(pixels: pixels)
}

func buildColorTable(offset: inout Int, handle: FileHandle) -> [ColorQuad] {
func buildColorTable(offset: inout UInt64, handle: FileHandle) -> [ColorQuad] {
var colorTable: [ColorQuad] = []

for _ in 0..<clrUsed {
Expand All @@ -167,10 +167,10 @@ public struct BitmapInfoHeader: Hashable {
return colorTable
}

func constructImage(pixels: [ColorQuad]) -> NSImage {
func constructImage(pixels: [ColorQuad]) -> NSImage? {
var pixels = pixels

if pixels.count > 0 {
if !pixels.isEmpty {
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue)
let quadStride = MemoryLayout<ColorQuad>.stride

Expand All @@ -192,7 +192,7 @@ public struct BitmapInfoHeader: Hashable {
}
}

return NSImage()
return nil
}
}

Expand Down
13 changes: 13 additions & 0 deletions WhiskyKit/Sources/WhiskyKit/Extensions/FileHandle+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ import Foundation
import os.log

extension FileHandle {
func extract<T>(_ type: T.Type, offset: UInt64 = 0) -> T? {
do {
try self.seek(toOffset: offset)
if let data = try self.read(upToCount: MemoryLayout<T>.size) {
return data.withUnsafeBytes { $0.loadUnaligned(as: T.self)}
} else {
return nil
}
} catch {
return nil
}
}

func write(line: String) {
do {
guard let data = line.data(using: .utf8) else { return }
Expand Down
60 changes: 60 additions & 0 deletions WhiskyKit/Sources/WhiskyKit/PE/COFFFileHeader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// PortableExecutable+COFFFileHeader.swift
// WhiskyKit
//
// This file is part of Whisky.
//
// Whisky is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Whisky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Whisky.
// If not, see https://www.gnu.org/licenses/.
//

import Foundation

extension PEFile {
/// COFF File Header (Object and Image)
///
/// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image
public struct COFFFileHeader: Hashable, Equatable {
public let machine: UInt16
public let numberOfSections: UInt16
public let timeDateStamp: Date
public let pointerToSymbolTable: UInt32
public let numberOfSymbols: UInt32
public let sizeOfOptionalHeader: UInt16
public let characteristics: UInt16

init(handle: FileHandle, offset: UInt64) {
var offset = offset + 4 // Skip signature

self.machine = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2

self.numberOfSections = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2

let timeDateStamp = handle.extract(UInt32.self, offset: offset) ?? 0
self.timeDateStamp = Date(timeIntervalSince1970: TimeInterval(timeDateStamp))
offset += 4

self.pointerToSymbolTable = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4

self.numberOfSymbols = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4

self.sizeOfOptionalHeader = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2

self.characteristics = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
}
}
}
40 changes: 40 additions & 0 deletions WhiskyKit/Sources/WhiskyKit/PE/Magic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// PortableExecutable+Magic.swift
// WhiskyKit
//
// This file is part of Whisky.
//
// Whisky is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Whisky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Whisky.
// If not, see https://www.gnu.org/licenses/.
//

import Foundation

extension PEFile {
public enum Magic: UInt16, Hashable, Equatable, CustomStringConvertible {
case unknown = 0x0
case pe32 = 0x10b
case pe32Plus = 0x20b

// MARK: - CustomStringConvertible

public var description: String {
switch self {
case .unknown:
return "unknown"
case .pe32:
return "PE32"
case .pe32Plus:
return "PE32+"
}
}
}
}
144 changes: 144 additions & 0 deletions WhiskyKit/Sources/WhiskyKit/PE/OptionalHeader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// PortableExecutable+OptionalHeader.swift
// WhiskyKit
//
// This file is part of Whisky.
//
// Whisky is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Whisky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Whisky.
// If not, see https://www.gnu.org/licenses/.
//

import Foundation

extension PEFile {
/// Optional Header
///
/// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only
public struct OptionalHeader: Hashable, Equatable {
// Standard Fields

public let magic: Magic
public let majorLinkerVersion: UInt8
public let minorLinkerVersion: UInt8
public let sizeOfCode: UInt32
public let sizeOfInitializedData: UInt32
public let sizeOfUninitializedData: UInt32
public let addressOfEntryPoint: UInt32
public let baseOfCode: UInt32
public let baseOfData: UInt32?

// Windows-Specific Fields

public let imageBase: UInt64
public let sectionAlignment: UInt32
public let fileAlignment: UInt32
public let majorOperatingSystemVersion: UInt16
public let minorOperatingSystemVersion: UInt16
public let majorImageVersion: UInt16
public let minorImageVersion: UInt16
public let majorSubsystemVersion: UInt16
public let minorSubsystemVersion: UInt16
public let win32VersionValue: UInt32
public let sizeOfImage: UInt32
public let sizeOfHeaders: UInt32
public let checkSum: UInt32
public let subsystem: UInt16
public let dllCharacteristics: UInt16
public let sizeOfStackReserve: UInt32
public let sizeOfStackCommit: UInt32
public let sizeOfHeapReserve: UInt32
public let sizeOfHeapCommit: UInt32
public let loaderFlags: UInt32
public let numberOfRvaAndSizes: UInt32

// swiftlint:disable:next function_body_length
init?(handle: FileHandle, offset: UInt64) {
var offset = offset
let rawMagic = handle.extract(UInt16.self, offset: offset) ?? 0
let magic = Magic(rawValue: rawMagic) ?? .unknown
self.magic = magic
offset += 2
self.majorLinkerVersion = handle.extract(UInt8.self, offset: offset) ?? 0
offset += 1
self.minorLinkerVersion = handle.extract(UInt8.self, offset: offset) ?? 0
offset += 1
self.sizeOfCode = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfInitializedData = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfUninitializedData = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.addressOfEntryPoint = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.baseOfCode = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4

switch magic {
case .pe32Plus:
// PE32+ does not contain this field, following BaseOfCode is a larger ImageBase instead.
self.baseOfData = nil

// PE32+ images have a 8 byte ImageBase field
self.imageBase = handle.extract(UInt64.self, offset: offset) ?? 0
offset += 8
default:
// PE32 contains this additional field, following BaseOfCode.
self.baseOfData = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4

// PE32 images have a 4 byte ImageBase field
let imageBase = handle.extract(UInt32.self, offset: offset) ?? 0
self.imageBase = UInt64(imageBase)
offset += 4
}

self.sectionAlignment = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.fileAlignment = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.majorOperatingSystemVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.minorOperatingSystemVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.majorImageVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.minorImageVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.majorSubsystemVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.minorSubsystemVersion = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.win32VersionValue = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfImage = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfHeaders = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.checkSum = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.subsystem = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.dllCharacteristics = handle.extract(UInt16.self, offset: offset) ?? 0
offset += 2
self.sizeOfStackReserve = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfStackCommit = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfHeapReserve = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.sizeOfHeapCommit = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.loaderFlags = handle.extract(UInt32.self, offset: offset) ?? 0
offset += 4
self.numberOfRvaAndSizes = handle.extract(UInt32.self, offset: offset) ?? 0
}
}
}
Loading

0 comments on commit 643ad89

Please sign in to comment.