Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve PE File & Program Icon Loading #670

Merged
merged 3 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -78,13 +78,14 @@ struct PinView: View {
.sheet(isPresented: $showRenameSheet) {
PinRenameView(name: $name)
}
.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 {
IsaacMarovitz marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down Expand Up @@ -94,7 +94,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 @@ -106,10 +106,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? {
IsaacMarovitz marked this conversation as resolved.
Show resolved Hide resolved
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