Initial commit from private projects repository.

This commit is contained in:
Paul Knight 2017-03-14 16:42:00 -07:00
parent a5d55c35df
commit d96267259c
31 changed files with 2456 additions and 0 deletions

2
.gitignore vendored
View File

@ -63,3 +63,5 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
.DS_Store

View File

@ -0,0 +1,4 @@
#include "Project.xcconfig"
ONLY_ACTIVE_ARCH = YES
SWIFT_OPTIMIZATION_LEVEL = -Onone

View File

@ -0,0 +1,5 @@
SDKROOT = macosx
WARNING_CFLAGS = -Wall -Wextra
SWIFT_VERSION = 3.0
SWIFT_INCLUDE_PATHS = "${PROJECT_DIR}/Modules"

View File

@ -0,0 +1,5 @@
#include "Project.xcconfig"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) NDEBUG=1
SWIFT_OPTIMIZATION_LEVEL = -O

View File

@ -0,0 +1,18 @@
//
// DictionaryExtras.swift
// SwresTools
//
extension Dictionary {
init(_ elements: Array<Element>){
self.init()
for (key, value) in elements {
self[key] = value
}
}
func flatMap(transform: (Key, Value) -> (Key, Value)?) -> Dictionary<Key, Value> {
return Dictionary(self.flatMap(transform))
}
}

28
Extras/ErrorExtras.swift Normal file
View File

@ -0,0 +1,28 @@
//
// ErrorExtras.swift
// SwresTools
//
protocol SwresError: Error, CustomStringConvertible {
}
protocol NestingSwresError: SwresError {
var underlyingError: Error? { get }
}
extension Error {
func shortDescription(withUnderlyingError: Bool = false) -> String {
switch self {
case let error as NestingSwresError:
var string = error.description
if let underlyingError = error.underlyingError, withUnderlyingError == true {
string += "\n" + underlyingError.shortDescription(withUnderlyingError: true)
}
return string
case let error as SwresError:
return error.description
default:
return localizedDescription
}
}
}

View File

@ -0,0 +1,36 @@
//
// PointerExtras.swift
// SwresTools
//
import Darwin
// For performance, there are a few places strings are passed around as a base pointer
// and a count of bytes. The Swift standard library includes `UnsafeBufferPointer` and
// `UnsafeMutableBufferPointer` which could wrap that pointer and length, but it's
// surprisingly slow. Replacing that with this tiny `Buffer` struct sped up string
// formatting hotpaths considerably.
struct Buffer<T> {
let pointer: UnsafePointer<T>
let count: Int
}
class ManagedUnsafeMutablePointer<T>: Hashable {
let pointer: UnsafeMutablePointer<T>
init(adoptPointer: UnsafeMutablePointer<T>) {
pointer = adoptPointer
}
deinit {
free(pointer)
}
var hashValue: Int {
return pointer.hashValue
}
static func ==(lhs: ManagedUnsafeMutablePointer, rhs: ManagedUnsafeMutablePointer) -> Bool {
return lhs.pointer == rhs.pointer
}
}

View File

@ -0,0 +1,22 @@
//
// SequenceExtras.swift
// SwresTools
//
extension Sequence {
func firstSome<ElementOfResult>(_ transform: @escaping (Iterator.Element) -> ElementOfResult?) -> ElementOfResult? {
return self.lazy.flatMap(transform).first
}
func groupBy<ElementOfResult: Hashable>(_ transform: (Iterator.Element) -> ElementOfResult) -> Dictionary<ElementOfResult, Array<Iterator.Element>> {
var groupedBy: Dictionary<ElementOfResult, Array<Iterator.Element>> = Dictionary()
for item in self {
let transformed = transform(item)
if groupedBy[transformed] == nil {
groupedBy[transformed] = Array<Iterator.Element>()
}
groupedBy[transformed]!.append(item)
}
return groupedBy
}
}

10
Extras/StrideExtras.swift Normal file
View File

@ -0,0 +1,10 @@
//
// StrideExtras.swift
// SwresTools
//
func offsetAndLengthStride(from: Int, to: Int, by: Int, _ block: (Int, Int) -> Void) {
for offset in stride(from: from, to: to, by: by) {
block(offset, min(by, to - offset))
}
}

100
Extras/StringExtras.swift Normal file
View File

@ -0,0 +1,100 @@
//
// StringFunctions.swift
// SwresTools
//
import Foundation
private let MinimumDisplayableByteValue: UInt8 = 32
let MacOSRomanByteFullStop: UInt8 = 0x2E
let MacOSRomanByteQuestionMark: UInt8 = 0x3F
struct MacOSRomanConversionOptions {
let filterControlCharacters: Bool
let filterFilesystemUnsafeCharacters: Bool
let filterNonASCIICharacters: Bool
let replacementMacOSRomanByte: UInt8?
}
func stringFromMacOSRomanBytes(_ buffer: Buffer<UInt8>, options: MacOSRomanConversionOptions) -> String {
let filterControlCharacters = options.filterControlCharacters || options.filterFilesystemUnsafeCharacters
let filteredBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: buffer.count + 1)
defer {
filteredBytes.deallocate(capacity: buffer.count + 1)
}
var filteredByteIterator = filteredBytes
@inline(__always) func writeFilteredByte(_ byte: UInt8) {
filteredByteIterator.pointee = byte
filteredByteIterator += 1
}
var bufferPointer = buffer.pointer
let endPointer = buffer.pointer + buffer.count
while bufferPointer < endPointer {
var replaceCharacter = false
let byte = bufferPointer.pointee
// SPACE, DELETE
if filterControlCharacters, byte < 0x20 || byte == 0x7F {
replaceCharacter = true
}
// DELETE
else if options.filterNonASCIICharacters, byte > 0x7F {
replaceCharacter = true
}
// ASTERISK, FULL STOP, SOLIDUS, COLON, REVERSE SOLIDUS, TILDE
// Some of these aren't technically unsafe, but they can cause issues in the
// shell or Finder, such as a period at the start of a file or a tilde.
else if options.filterFilesystemUnsafeCharacters, byte == 0x2A || byte == 0x2E || byte == 0x2F || byte == 0x3A || byte == 0x5C || byte == 0x7E {
replaceCharacter = true
}
if replaceCharacter, let unwrappedReplacementByte = options.replacementMacOSRomanByte {
writeFilteredByte(unwrappedReplacementByte)
} else if !replaceCharacter {
writeFilteredByte(byte)
}
bufferPointer += 1
}
filteredByteIterator.pointee = 0
let cStringPointer = UnsafeRawPointer(filteredBytes).assumingMemoryBound(to: CChar.self)
return String(cString: cStringPointer, encoding: String.Encoding.macOSRoman)!
}
func stringFromMacOSRomanBytes(_ data: Data, options: MacOSRomanConversionOptions) -> String {
return data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
let buffer = Buffer(pointer: bytes, count: data.count)
return stringFromMacOSRomanBytes(buffer, options: options)
}
}
func filesystemSafeString(_ data: Data) -> String {
let options = MacOSRomanConversionOptions(filterControlCharacters: true, filterFilesystemUnsafeCharacters: true, filterNonASCIICharacters: false, replacementMacOSRomanByte: nil)
return stringFromMacOSRomanBytes(data, options: options)
}
extension String {
func copyCString() -> ManagedUnsafeMutablePointer<Int8> {
return self.withCString({ (cString: UnsafePointer<Int8>) -> ManagedUnsafeMutablePointer<Int8> in
return ManagedUnsafeMutablePointer(adoptPointer: strdup(cString))
})
}
}
struct StringAndCString {
let string: String
let cString: ManagedUnsafeMutablePointer<CChar>
init(_ string: String) {
self.string = string
cString = string.copyCString()
}
}

View File

@ -0,0 +1,5 @@
module Fuse [system] {
header "/usr/local/include/osxfuse/fuse.h"
link "osxfuse"
export *
}

49
README Normal file
View File

@ -0,0 +1,49 @@
SwresTools is a set of two tools for exploring and dumping classic Macintosh resource forks, written in Swift.
SwresTools can convert some resources to modern types. For example, it can translate some <code>snd </code> resources to WAV files.
To be honest, this was just a fun side project and shouldn't be used for anything important.
### SwresExplode
`SwresExplode` dumps the resources of a classic Macintosh resource fork to individual files.
For example, list all of the resource types in a resource fork:
SwresExplode resourcefile
Dump all `snd ` resources to individual files:
SwresExplode -d -t 'snd '
### SwresFUSE
`SwresFUSE` mounts a resource fork as a filesystem. It requires [FUSE for macOS][fuse].
Example usage:
SwresFUSE resourcefile mountpoint
![A screenshot of SwresFUSE exploring a resource fork.](SwresFUSE.png)
[fuse]: https://osxfuse.github.io
### Why?
¯\\_(ツ)_/¯
### No, Really, Why?
I wanted to rip the music out of the old Mac game [Troubled Souls][troubledsouls], and this seemed like such a bad way to go about it I had to give it a go. I was playing around with Swift and thought this would be a fun way to write a project that interfaced with a C library.
[troubledsouls]: https://en.wikipedia.org/wiki/Troubled_Souls
### Buiding
You may need to point the header parameter of Modules/FUSE/module.modulemap to your FUSE header directory. For some reason module maps don't seem to respect header search paths and need absolute paths.
### Limitations
* The translators are very limited. Only Pascal style prefix strings and an extremely limited subset of `SND` resources are supported.
* ResEdit features like `RMAP` and `TMPL` resources are not supported.
* Editing or creating new resources is not supported.

View File

@ -0,0 +1,367 @@
//
// SwresExplode.swift
// SwresTools
//
import Foundation
struct ExplodeTask {
var printResources: Bool = false
var dumpResources: Bool = false
var overwriteExistingFiles: Bool = false
var translatorFilter: TranslatorFilter = TranslatorFilter.noTranslators
var identifierFilter: Int16?
var typeFilter: FourCharCode?
var inputURL: URL?
var outputFolder: URL = URL(fileURLWithPath: "SwresExplode")
}
func printUsageAndExit(status: Int32 = EXIT_SUCCESS) -> Never {
let processName = ProcessInfo.processInfo.processName
print("Extract Macintosh Toolbox resources.")
print("Usage: \(processName) [options] resourcefile")
print("Options:")
print(" -h Show this help message")
print("Filtering Options:")
print(" -i [id] Filter by resource identifier.")
print(" -t [xxxx] Filter by resource type.")
print("Dumping Options:")
print(" -p Print resources to standard output.")
print(" -d Dump resources to files.")
print(" -o Output directory for the dumped resource.")
print(" -f Overwrite existing files when dumping.")
print(" -c Attempt to convert resources into more modern or portable formats.")
print(" -C Also use best guess conversions.")
print("Examples:")
print(" \(processName) resourcefile List all of the types.")
print(" \(processName) -d -t 'snd ' Dump all `snd ' resources.")
print(" \(processName) -d -t 'snd ' -i 1000 Dump the `snd ' resource with id 1000.")
print(" \(processName) -d -o /tmp/foo Dump all resources to the directory /tmp/foo.")
exit(status)
}
func taskForArguments() -> ExplodeTask {
var task = ExplodeTask()
let argc = CommandLine.argc
opterr = 0
while true {
let option = getopt(argc, CommandLine.unsafeArgv, "hi:t:pdfcCo:")
if option == -1 {
break
}
let optionScalar = UnicodeScalar(Int(option))!
switch optionScalar {
case UnicodeScalar("h"):
printUsageAndExit()
case UnicodeScalar("i"):
let identifierNumber = String(cString: optarg)
task.identifierFilter = Int16(identifierNumber)
case UnicodeScalar("t"):
do {
task.typeFilter = try FourCharCode(optarg)
} catch FourCharCodeError.invalidSequence {
print("Invalid type filter.");
printUsageAndExit(status: EXIT_FAILURE)
} catch {
print("Unexpected error parsing type filter.")
printUsageAndExit(status: EXIT_FAILURE)
}
case UnicodeScalar("p"):
task.printResources = true
case UnicodeScalar("d"):
task.dumpResources = true
case UnicodeScalar("o"):
task.outputFolder = URL(fileURLWithPath: String(cString: optarg))
case UnicodeScalar("f"):
task.overwriteExistingFiles = true
case UnicodeScalar("c"):
task.translatorFilter = max(task.translatorFilter, TranslatorFilter.onlyLikelyTranslators)
case UnicodeScalar("C"):
task.translatorFilter = max(task.translatorFilter, TranslatorFilter.likelyAndPossibleTranslators)
case UnicodeScalar("?"):
let unknownOption = UnicodeScalar(Int(optopt))!
if unknownOption == UnicodeScalar("i") || unknownOption == UnicodeScalar("t") {
print("Option -\(unknownOption) requires an argument.")
} else {
print("Unknown option -\(unknownOption.escaped(asASCII: true)).")
}
printUsageAndExit(status: EXIT_FAILURE)
default:
printUsageAndExit(status: EXIT_FAILURE)
}
}
guard optind < argc else {
print("No input file specified.")
printUsageAndExit(status: EXIT_FAILURE)
}
let inputPathBytes = CommandLine.unsafeArgv[Int(optind)]!
let inputPath = String(cString:inputPathBytes)
task.inputURL = URL(fileURLWithPath: inputPath)
return task
}
func run(_ task: ExplodeTask) -> Int32 {
let resourcesByType = read(task)
process(task: task, resourcesByType: resourcesByType)
return EXIT_SUCCESS
}
func read(_ task: ExplodeTask) -> ResourcesByType {
do {
return try readResourceFork(task.inputURL!)
} catch let error {
print(error.shortDescription(withUnderlyingError: true))
exit(EXIT_FAILURE)
}
}
func process(task: ExplodeTask, resourcesByType: ResourcesByType) {
if task.dumpResources {
do {
try createOutputDirectory(task: task)
} catch let error {
print(error.shortDescription(withUnderlyingError: true))
exit(EXIT_FAILURE)
}
}
let filteredResourcesByType = filter(resources: resourcesByType, task: task)
for (_, resources) in filteredResourcesByType {
for resource in resources {
print(format(resource))
if task.printResources {
print(format(resource.data))
}
if task.dumpResources {
dump(task: task, resource: resource)
}
}
}
}
enum OutputDirectoryError: NestingSwresError {
case directoryIsNotAFolder
case directoryExists
case couldntCreateDirectory(underlyingError: Error)
var underlyingError: Error? {
switch self {
case .couldntCreateDirectory(let underlyingError):
return underlyingError
default:
return nil
}
}
var description: String {
switch self {
case .directoryIsNotAFolder:
return "Output directory is not a folder."
case .directoryExists:
return "Output directory already exists. Use -f to overwrite existing files."
case .couldntCreateDirectory:
return "Couldn't create output directory."
}
}
}
func createOutputDirectory(task: ExplodeTask) throws {
let fileManager = FileManager.default
let outputDirectoryPath = task.outputFolder.path
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: outputDirectoryPath, isDirectory: &isDirectory) {
guard isDirectory.boolValue else {
throw OutputDirectoryError.directoryIsNotAFolder
}
guard task.overwriteExistingFiles else {
throw OutputDirectoryError.directoryExists
}
}
do {
try fileManager.createDirectory(at: task.outputFolder, withIntermediateDirectories: true, attributes: nil)
} catch let error {
throw OutputDirectoryError.couldntCreateDirectory(underlyingError: error)
}
}
func dump(task: ExplodeTask, resource: Resource) {
let (folderURL, fileURL) = explodedLocation(task: task, resource: resource)
let fileManager = FileManager.default
do {
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
try resource.data.write(to: fileURL)
} catch let error {
print("Couldn't dump resource \(resource.type) \(resource.identifier).")
print(error.shortDescription)
}
if task.translatorFilter >= TranslatorFilter.onlyLikelyTranslators {
let translatorManager = TranslatorManager.sharedInstance
let translationResults = translatorManager.translate(resource, includeTranslators: task.translatorFilter)
for translationResult in translationResults {
switch translationResult {
case .translated(let translation):
let outputURL = fileURL.appendingPathExtension(translation.suggestedFileExtension)
let data = translation.data
do {
try data.write(to: outputURL)
} catch let error {
let resourceDescription = format(resource, short: true)
print("Couldn't write translation for resource \(resourceDescription).")
print(error.shortDescription(withUnderlyingError: true))
}
case .error(let error):
let resourceDescription = format(resource, short: true)
print("Failed to translate resource \(resourceDescription).")
print(error.shortDescription(withUnderlyingError: true))
}
}
}
}
func filter(resources: ResourcesByType, task: ExplodeTask) -> ResourcesByType {
return resources.flatMap { (type: FourCharCode, resources: Array<Resource>) -> (FourCharCode, Array<Resource>)? in
if let typeFilter = task.typeFilter, type != typeFilter {
return nil
}
let filteredResources = resources.flatMap { (resource: Resource) -> Resource? in
if let identifierFilter = task.identifierFilter, resource.identifier != identifierFilter {
return nil
}
return resource
}
return (type, filteredResources)
}
}
func format(_ resource: Resource, short: Bool = false) -> String {
if short {
return "'\(resource.type.description)' \(resource.identifier)"
}
var string = String(format: "'%@' %7d %8d bytes", resource.type.description, resource.identifier, resource.data.count)
if let name = resource.stringName {
string += " \"\(name)\""
}
return string
}
func format(_ data: Data) -> String {
let options = MacOSRomanConversionOptions(filterControlCharacters: true, filterFilesystemUnsafeCharacters: false, filterNonASCIICharacters: true, replacementMacOSRomanByte: MacOSRomanByteFullStop)
var lines = Array<String>()
data.withUnsafeBytes { (unsafeBytes: UnsafePointer<UInt8>) in
offsetAndLengthStride(from: 0, to: data.count, by: 16, { (offset: Int, length: Int) in
let formattedOffset = format(asHex: offset, length: 8)
let lineBuffer = Buffer(pointer: unsafeBytes + offset, count: length)
let formattedLine = format(line: lineBuffer, lineLength: 16)
let asciiFormattedBytes = stringFromMacOSRomanBytes(lineBuffer, options: options)
let rowString = "\(formattedOffset): \(formattedLine) \(asciiFormattedBytes)"
lines.append(rowString)
})
}
return lines.joined(separator: "\n")
}
// 0-9, A-F
let asciiHexCharacters: Array<CChar> = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66]
let asciiSpace: CChar = 0x20
func format(asHex number: Int, length: Int) -> String {
assert(length >= 0)
var number = number
let stringBufferCapacity = length + 1
let stringBuffer = UnsafeMutablePointer<CChar>.allocate(capacity: stringBufferCapacity)
defer {
stringBuffer.deallocate(capacity: stringBufferCapacity)
}
var stringBufferIterator = stringBuffer + length
stringBufferIterator.pointee = 0
stringBufferIterator -= 1
while stringBufferIterator >= stringBuffer {
stringBufferIterator.pointee = asciiHexCharacters[number % 16]
number = number / 16
stringBufferIterator -= 1
}
return String(cString: stringBuffer, encoding: String.Encoding.ascii)!
}
func format(line lineBuffer: Buffer<UInt8>, lineLength: Int) -> String {
assert(lineBuffer.count <= lineLength)
let lineBytes = lineBuffer.pointer
let lineBytesCount = lineBuffer.count
let spacerCount = max(0, (lineLength - 1) / 2)
let stringBufferSize = lineLength * 2 + spacerCount + 1
let stringBuffer = UnsafeMutablePointer<CChar>.allocate(capacity: stringBufferSize)
defer {
stringBuffer.deallocate(capacity: stringBufferSize)
}
var stringBufferIterator = stringBuffer
@inline(__always) func writeCChar(_ char: CChar) {
stringBufferIterator.pointee = char
stringBufferIterator += 1
}
for byteIndex in 0..<lineLength {
if byteIndex % 2 == 0 && byteIndex > 0 {
writeCChar(asciiSpace)
}
if byteIndex < lineBytesCount {
let byte = lineBytes[byteIndex]
writeCChar(asciiHexCharacters[Int(byte / 16)])
writeCChar(asciiHexCharacters[Int(byte % 16)])
} else {
writeCChar(asciiSpace)
writeCChar(asciiSpace)
}
}
writeCChar(0)
return String(cString: stringBuffer, encoding: String.Encoding.ascii)!
}
func explodedLocation(task: ExplodeTask, resource: Resource) -> (URL, URL) {
let outputFolder = task.outputFolder
let typeFolder = outputFolder.appendingPathComponent(filesystemSafeString(resource.type.bytes))
var filename = "\(resource.identifier)"
if let name = resource.name {
let sanitizedName = filesystemSafeString(name)
filename += " \(sanitizedName)"
}
let url = typeFolder.appendingPathComponent(filename)
return (typeFolder, url)
}
func swresExplodeMain() -> Int32 {
let task = taskForArguments()
return run(task)
}

View File

@ -0,0 +1 @@
PRODUCT_NAME = SwresExplode

8
SwresExplode/main.swift Normal file
View File

@ -0,0 +1,8 @@
//
// main.swift
// SwresTools
//
import Darwin
exit(swresExplodeMain())

BIN
SwresFUSE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@ -0,0 +1,113 @@
//
// FilesystemNode.swift
// SwresTools
//
import Foundation
enum FilesystemNode {
case folder(name: StringAndCString, children: Dictionary<String, FilesystemNode>)
case file(name: StringAndCString, data: Data)
var cStringName: ManagedUnsafeMutablePointer<Int8> {
switch self {
case .folder(let name, _):
return name.cString
case .file(let name, _):
return name.cString
}
}
var name: String {
switch self {
case .folder(let name, _):
return name.string
case .file(let name, _):
return name.string
}
}
init(name: String, data: Data) {
self = .file(name: StringAndCString(name), data: data)
}
init(name: String, children: Array<FilesystemNode>) {
let nameAndChildTuples = children.map { (child: FilesystemNode) -> (String, FilesystemNode) in
return (child.name, child)
}
self = .folder(name: StringAndCString(name), children: Dictionary(nameAndChildTuples))
}
func nodeAtPath(_ path: UnsafePointer<Int8>) -> FilesystemNode? {
guard let pathString = String(cString: path, encoding: String.Encoding.utf8) else {
return nil
}
return nodeAtPath(pathString)
}
func nodeAtPath(_ path: String) -> FilesystemNode? {
let pathComponents = path.components(separatedBy: "/").filter { (pathComponent: String) -> Bool in
pathComponent.characters.count > 0
}
return _nodeAtPath(pathComponents[0 ..< pathComponents.endIndex])
}
private func _nodeAtPath(_ pathComponents: ArraySlice<String>) -> FilesystemNode? {
guard let nextComponent = pathComponents.first else {
return self
}
switch self {
case .file:
guard pathComponents.count == 1 && nextComponent == self.name else {
return nil
}
return self
case .folder(_, let children):
guard let child = children[nextComponent] else {
return nil
}
return child._nodeAtPath(pathComponents[1 ..< pathComponents.endIndex])
}
}
func isFolder() -> Bool {
switch self {
case .folder:
return true
default:
return false
}
}
func stLinkCount() -> nlink_t {
switch self {
case .file:
return 1
case .folder(_, let children):
let childFolders = children.filter { (_, child: FilesystemNode) in
return child.isFolder()
}
return nlink_t(childFolders.count + 2)
}
}
func stMode() -> mode_t {
switch self {
case .file:
return S_IFREG | 0o0444
case .folder:
return S_IFDIR | 0o0555
}
}
func stSize() -> off_t {
switch self {
case .file(_, let data):
return off_t(data.count)
case .folder:
return 0
}
}
}

View File

@ -0,0 +1,6 @@
PRODUCT_NAME = SwresFUSE
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) FUSE_USE_VERSION=26 _FILE_OFFSET_BITS=64
HEADER_SEARCH_PATHS = $(inherited) /usr/local/include/osxfuse
LIBRARY_SEARCH_PATHS = $(inherited) /usr/local/lib

324
SwresFUSE/SwresFuse.swift Normal file
View File

@ -0,0 +1,324 @@
//
// SwresFuse.swift
// SwresTools
//
import Foundation
import Fuse
struct FuseTask {
var inputURL: URL?
var includeTranslations: Bool = false
var allowPossibleTranslations: Bool = false
var fuseArgs: ManagedUnsafeMutablePointer<fuse_args>?
}
struct CommandLineOption {
let template: String
let key: CommandLineKey
}
enum CommandLineKey: Int32 {
case fuse_opt_key_keep = -3
case fuse_opt_key_nonopt = -2
case fuse_opt_key_opt = -1
case help = 1
case includeTranslations
case allowPossibleTranslations
}
var rootNode: FilesystemNode?
let currentDirectoryCString = ".".copyCString()
let parentDirectoryCString = "..".copyCString()
func printUsageAndExit(status: Int32 = EXIT_SUCCESS) -> Never {
let processName = ProcessInfo.processInfo.processName
print("Mount a resource fork with FUSE.")
print("Usage: \(processName) [options] resourcefile mountpoint")
print("Options:")
print(" -h Show this help message")
print(" -c Attempt to convert resources into more modern or portable formats.")
print(" -C Also use best guess conversions.")
exit(status)
}
func dieWithMessage(_ message: String) -> Never {
print(message)
exit(EXIT_FAILURE)
}
func withFuseOptions(_ options: Array<CommandLineOption>, _ block: (UnsafePointer<fuse_opt>) -> Void) {
var cStringPool = Array<ManagedUnsafeMutablePointer<Int8>>()
var fuseOptions = ContiguousArray<fuse_opt>()
fuseOptions.reserveCapacity(options.count)
for option in options {
let cString = option.template.copyCString()
cStringPool.append(cString)
let fuseOption = fuse_opt(templ: UnsafePointer(cString.pointer), offset: UInt(UInt32(bitPattern: -1)), value: option.key.rawValue)
fuseOptions.append(fuseOption)
}
let nullOption = fuse_opt(templ: nil, offset: 0, value: 0)
fuseOptions.append(nullOption)
fuseOptions.withUnsafeBufferPointer { (fuseOptionsBufferPointer: UnsafeBufferPointer<fuse_opt>) in
guard let fuseOptionsPointer = fuseOptionsBufferPointer.baseAddress else {
dieWithMessage("Error setting up option parsing.")
}
block(fuseOptionsPointer)
}
}
func taskForArguments() -> FuseTask {
var task = FuseTask()
let options = [
CommandLineOption(template: "-h", key: CommandLineKey.help),
CommandLineOption(template: "-c", key: CommandLineKey.includeTranslations),
CommandLineOption(template: "-C", key: CommandLineKey.allowPossibleTranslations),
CommandLineOption(template: "-d", key: CommandLineKey.fuse_opt_key_keep),
]
let args = malloc(MemoryLayout<fuse_args>.size).assumingMemoryBound(to: fuse_args.self)
args.pointee.argc = CommandLine.argc
args.pointee.argv = CommandLine.unsafeArgv
args.pointee.allocated = 0
withFuseOptions(options, { (fuseOptions: UnsafePointer<fuse_opt>) in
let parseResult = fuse_opt_parse(args, &task, fuseOptions, { (context: UnsafeMutableRawPointer?, arg: UnsafePointer<Int8>?, key: Int32, args: UnsafeMutablePointer<fuse_args>?) -> Int32 in
guard let taskRawPointer = UnsafeMutableRawPointer(context) else {
dieWithMessage("Error parsing arguments. Received a NULL context pointer from FUSE.")
}
let taskPointer = taskRawPointer.bindMemory(to: FuseTask.self, capacity: 1)
guard let arg = arg else {
dieWithMessage("Error parsing arguments. Received a NULL argument from FUSE.")
}
guard let option = String(cString: arg, encoding: String.Encoding.ascii) else {
dieWithMessage("Error parsing arguments. Argument encoding unrecognized.")
}
guard let key = CommandLineKey(rawValue: key) else {
dieWithMessage("Unexpected key from FUSE.")
}
switch key {
case .fuse_opt_key_nonopt:
if taskPointer.pointee.inputURL == nil {
taskPointer.pointee.inputURL = URL(fileURLWithPath: option)
return 0
}
return 1
case .fuse_opt_key_opt:
print("Unrecognized option \(option).")
printUsageAndExit(status: EXIT_FAILURE)
case .help:
printUsageAndExit()
case .includeTranslations:
taskPointer.pointee.includeTranslations = true
return 0
case .allowPossibleTranslations:
taskPointer.pointee.includeTranslations = true
taskPointer.pointee.allowPossibleTranslations = true
return 0
default:
dieWithMessage("Unexpected key from FUSE.")
}
return 1
})
if parseResult != 0 {
print("Failed to parse optinos.")
exit(EXIT_FAILURE)
}
})
fuse_opt_add_arg(args, "-s")
fuse_opt_add_arg(args, "-f")
task.fuseArgs = ManagedUnsafeMutablePointer(adoptPointer: args)
return task
}
func getAttr(path: UnsafePointer<Int8>?, stbuf: UnsafeMutablePointer<stat>?) -> Int32 {
guard let rootNode = rootNode else {
dieWithMessage("No filesystem root note was created.")
}
guard let path = path, let stbuf = stbuf else {
dieWithMessage("Received null parameter from FUSE.")
}
guard let node = rootNode.nodeAtPath(path) else {
return -ENOENT
}
stbuf.pointee.st_mode = node.stMode()
stbuf.pointee.st_nlink = node.stLinkCount()
stbuf.pointee.st_size = node.stSize()
stbuf.pointee.st_uid = getuid()
stbuf.pointee.st_gid = getgid()
return 0
}
func readDir(path: UnsafePointer<Int8>?, buf: UnsafeMutableRawPointer?, filler: fuse_fill_dir_t?, offset: off_t, fi: UnsafeMutablePointer<fuse_file_info>?) -> Int32 {
guard let rootNode = rootNode else {
dieWithMessage("No filesystem root note was created.")
}
guard let path = path, let buf = buf, let filler = filler else {
dieWithMessage("Received null parameter from FUSE.")
}
guard let node = rootNode.nodeAtPath(path) else {
return -ENOENT
}
@inline(__always) func appendEntry(filename: ManagedUnsafeMutablePointer<Int8>) {
guard filler(buf, filename.pointer, nil, 0) == 0 else {
// TODO: Figure out how to correctly support large directories.
dieWithMessage("readDir buffer is full.")
}
}
switch node {
case .file:
return -ENOENT
case .folder(_, let children):
appendEntry(filename: parentDirectoryCString)
appendEntry(filename: currentDirectoryCString)
for (_, child) in children {
appendEntry(filename: child.cStringName)
}
}
return 0
}
func openFile(path: UnsafePointer<Int8>?, fi: UnsafeMutablePointer<fuse_file_info>?) -> Int32 {
guard let rootNode = rootNode else {
dieWithMessage("No filesystem root note was created.")
}
guard let path = path, let fi = fi else {
dieWithMessage("Received null parameter from FUSE.")
}
guard let _ = rootNode.nodeAtPath(path) else {
return -ENOENT
}
guard fi.pointee.flags & 3 == O_RDONLY else {
return -EACCES
}
return 0
}
func readFile(path: UnsafePointer<Int8>?, buf: UnsafeMutablePointer<Int8>?, size: size_t, offset: off_t, fi: UnsafeMutablePointer<fuse_file_info>?) -> Int32 {
guard let rootNode = rootNode else {
dieWithMessage("No filesystem root note was created.")
}
guard let path = path else {
dieWithMessage("Received null parameter from FUSE.")
}
guard let node = rootNode.nodeAtPath(path) else {
return -ENOENT
}
switch node {
case .folder:
return -ENOENT
case .file(_, let data):
let length = data.count
let offset = Int(offset)
guard length > offset else {
return 0
}
let bytesCopied = min(length - offset, size)
data.withUnsafeBytes { (dataBytes: UnsafePointer<Int8>) -> Void in
memcpy(buf, dataBytes + offset, bytesCopied)
}
return Int32(bytesCopied)
}
}
func run(_ task: FuseTask) -> Int32 {
guard let inputURL = task.inputURL else {
dieWithMessage("Missing inputURL in task.")
}
do {
let resourcesByType = try readResourceFork(inputURL)
rootNode = filesystemNode(resourcesByType, includeTranslations: task.includeTranslations)
var operations = fuse_operations()
operations.getattr = getAttr
operations.readdir = readDir
operations.open = openFile
operations.read = readFile
guard let args = task.fuseArgs?.pointer else {
dieWithMessage("Failed to construct arguments to pass to FUSE.")
}
let result = fuse_main_real(args.pointee.argc, args.pointee.argv, &operations, MemoryLayout.size(ofValue: operations), nil)
print("hi")
return result
} catch {
dieWithMessage(error.shortDescription(withUnderlyingError: true))
}
}
func filesystemNode(_ resourcesByType: ResourcesByType, includeTranslations: Bool) -> FilesystemNode {
let folders = resourcesByType.map { (type: FourCharCode, resources: Array<Resource>) -> FilesystemNode in
let folderName = filesystemSafeString(type.bytes)
let children = resources.flatMap { (resource: Resource) -> Array<FilesystemNode> in
return filesystemNodes(resource, includeTranslations: includeTranslations)
}
return FilesystemNode(name: folderName, children: children)
}
return FilesystemNode(name: "ROOT", children: folders)
}
func filesystemNodes(_ resource: Resource, includeTranslations: Bool) -> Array<FilesystemNode> {
var nodes = Array<FilesystemNode>()
var filename = "\(resource.identifier)"
if let name = resource.name {
let sanitizedName = filesystemSafeString(name)
filename += " \(sanitizedName)"
}
nodes.append(FilesystemNode(name: filename, data: resource.data))
if (includeTranslations) {
let translatorManager = TranslatorManager.sharedInstance
let translationResults = translatorManager.translate(resource, includeTranslators: TranslatorFilter.likelyAndPossibleTranslators)
let translationNodes = translationResults.flatMap { (translationResult: TranslationResult) -> FilesystemNode? in
switch translationResult {
case .translated(let translation):
let translatedFilename = filename + ".\(translation.suggestedFileExtension)"
return FilesystemNode(name: translatedFilename, data: translation.data)
case .error(let error):
print(error.shortDescription(withUnderlyingError: true))
return nil
}
}
nodes.append(contentsOf: translationNodes)
}
return nodes
}
func swresFuseMain() -> Int32 {
let task = taskForArguments()
return run(task)
}

8
SwresFUSE/main.swift Normal file
View File

@ -0,0 +1,8 @@
//
// main.swift
// SwresFUSE
//
import Darwin
exit(swresFuseMain())

View File

@ -0,0 +1,535 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXAggregateTarget section */
CA9567C41DC132620031E5F5 /* Everything */ = {
isa = PBXAggregateTarget;
buildConfigurationList = CA9567C51DC132620031E5F5 /* Build configuration list for PBXAggregateTarget "Everything" */;
buildPhases = (
);
dependencies = (
CA9567C91DC1326A0031E5F5 /* PBXTargetDependency */,
CA9567CB1DC1326C0031E5F5 /* PBXTargetDependency */,
);
name = Everything;
productName = Everything;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
CA2B20FF1DA57DAB00A14B92 /* TranslatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2B20FE1DA57DAB00A14B92 /* TranslatorManager.swift */; };
CA4150EA1DAF4401005F689D /* SequenceExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E41DAF4401005F689D /* SequenceExtras.swift */; };
CA4150EB1DAF4401005F689D /* DictionaryExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E51DAF4401005F689D /* DictionaryExtras.swift */; };
CA4150EC1DAF4401005F689D /* ErrorExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E61DAF4401005F689D /* ErrorExtras.swift */; };
CA4150ED1DAF4401005F689D /* PointerExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E71DAF4401005F689D /* PointerExtras.swift */; };
CA4150EE1DAF4401005F689D /* StrideExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E81DAF4401005F689D /* StrideExtras.swift */; };
CA4150EF1DAF4401005F689D /* StringExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E91DAF4401005F689D /* StringExtras.swift */; };
CA4150F21DAF4407005F689D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F01DAF4407005F689D /* main.swift */; };
CA4150F31DAF4407005F689D /* SwresExplode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F11DAF4407005F689D /* SwresExplode.swift */; };
CA4150F61DAF440F005F689D /* PascalStringTranslator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F41DAF440F005F689D /* PascalStringTranslator.swift */; };
CA4150F71DAF440F005F689D /* SndTranslator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F51DAF440F005F689D /* SndTranslator.swift */; };
CA4EB2CE1DB8057100A775DF /* SequenceExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E41DAF4401005F689D /* SequenceExtras.swift */; };
CA4EB2CF1DB8057100A775DF /* DictionaryExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E51DAF4401005F689D /* DictionaryExtras.swift */; };
CA4EB2D01DB8057100A775DF /* ErrorExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E61DAF4401005F689D /* ErrorExtras.swift */; };
CA4EB2D11DB8057100A775DF /* PointerExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E71DAF4401005F689D /* PointerExtras.swift */; };
CA4EB2D21DB8057100A775DF /* StrideExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E81DAF4401005F689D /* StrideExtras.swift */; };
CA4EB2D31DB8057100A775DF /* StringExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150E91DAF4401005F689D /* StringExtras.swift */; };
CA4EB2D41DB8057700A775DF /* FourCharCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007D01D8DBD1900B7D2BD /* FourCharCode.swift */; };
CA4EB2D51DB8057700A775DF /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007D41D8DF9C400B7D2BD /* Resource.swift */; };
CA4EB2D61DB8057700A775DF /* ResourceForkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007CE1D8DBCAF00B7D2BD /* ResourceForkReader.swift */; };
CA4EB2D71DB8057700A775DF /* SeekableReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007CC1D8DB31400B7D2BD /* SeekableReader.swift */; };
CA4EB2D81DB8057700A775DF /* Writer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA572C501DB2058E00A2AF8F /* Writer.swift */; };
CA4EB2D91DB8057700A775DF /* TranslatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2B20FE1DA57DAB00A14B92 /* TranslatorManager.swift */; };
CA4EB2DA1DB8057700A775DF /* PascalStringTranslator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F41DAF440F005F689D /* PascalStringTranslator.swift */; };
CA4EB2DB1DB8057700A775DF /* SndTranslator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4150F51DAF440F005F689D /* SndTranslator.swift */; };
CA4EB2DC1DB8057700A775DF /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6136641DA4ADD0001F81DA /* Translator.swift */; };
CA4EB2E11DB9ECCB00A775DF /* FilesystemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4EB2DF1DB9E44300A775DF /* FilesystemNode.swift */; };
CA572C511DB2058E00A2AF8F /* Writer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA572C501DB2058E00A2AF8F /* Writer.swift */; };
CA584C971DB491DF00E36C26 /* SwresFuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA584C961DB491DF00E36C26 /* SwresFuse.swift */; };
CA5AA8EC1DB44814001866EB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5AA8EB1DB44814001866EB /* main.swift */; };
CA6136651DA4ADD0001F81DA /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6136641DA4ADD0001F81DA /* Translator.swift */; };
CA9007CD1D8DB31400B7D2BD /* SeekableReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007CC1D8DB31400B7D2BD /* SeekableReader.swift */; };
CA9007CF1D8DBCAF00B7D2BD /* ResourceForkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007CE1D8DBCAF00B7D2BD /* ResourceForkReader.swift */; };
CA9007D11D8DBD1900B7D2BD /* FourCharCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007D01D8DBD1900B7D2BD /* FourCharCode.swift */; };
CA9007D51D8DF9C400B7D2BD /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9007D41D8DF9C400B7D2BD /* Resource.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
CA9567C81DC1326A0031E5F5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = CA9007BA1D8DAFB500B7D2BD /* Project object */;
proxyType = 1;
remoteGlobalIDString = CA9007C11D8DAFB500B7D2BD;
remoteInfo = SwresExplode;
};
CA9567CA1DC1326C0031E5F5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = CA9007BA1D8DAFB500B7D2BD /* Project object */;
proxyType = 1;
remoteGlobalIDString = CA5AA8E81DB44814001866EB;
remoteInfo = SwresFUSE;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
CA5AA8E71DB44814001866EB /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
CA9007C01D8DAFB500B7D2BD /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
CA2B20FE1DA57DAB00A14B92 /* TranslatorManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TranslatorManager.swift; path = Translators/TranslatorManager.swift; sourceTree = "<group>"; };
CA4150E41DAF4401005F689D /* SequenceExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SequenceExtras.swift; path = Extras/SequenceExtras.swift; sourceTree = "<group>"; };
CA4150E51DAF4401005F689D /* DictionaryExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DictionaryExtras.swift; path = Extras/DictionaryExtras.swift; sourceTree = "<group>"; };
CA4150E61DAF4401005F689D /* ErrorExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ErrorExtras.swift; path = Extras/ErrorExtras.swift; sourceTree = "<group>"; };
CA4150E71DAF4401005F689D /* PointerExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PointerExtras.swift; path = Extras/PointerExtras.swift; sourceTree = "<group>"; };
CA4150E81DAF4401005F689D /* StrideExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StrideExtras.swift; path = Extras/StrideExtras.swift; sourceTree = "<group>"; };
CA4150E91DAF4401005F689D /* StringExtras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringExtras.swift; path = Extras/StringExtras.swift; sourceTree = "<group>"; };
CA4150F01DAF4407005F689D /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = main.swift; path = SwresExplode/main.swift; sourceTree = "<group>"; };
CA4150F11DAF4407005F689D /* SwresExplode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwresExplode.swift; path = SwresExplode/SwresExplode.swift; sourceTree = "<group>"; };
CA4150F41DAF440F005F689D /* PascalStringTranslator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PascalStringTranslator.swift; path = Translators/PascalStringTranslator.swift; sourceTree = "<group>"; };
CA4150F51DAF440F005F689D /* SndTranslator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SndTranslator.swift; path = Translators/SndTranslator.swift; sourceTree = "<group>"; };
CA4EB2DF1DB9E44300A775DF /* FilesystemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilesystemNode.swift; sourceTree = "<group>"; };
CA572C501DB2058E00A2AF8F /* Writer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Writer.swift; sourceTree = "<group>"; };
CA584C8C1DB47C9300E36C26 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Modules/FUSE/module.modulemap; sourceTree = "<group>"; };
CA584C8F1DB4894500E36C26 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Configurations/Debug.xcconfig; sourceTree = "<group>"; };
CA584C901DB4894500E36C26 /* Project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Project.xcconfig; path = Configurations/Project.xcconfig; sourceTree = "<group>"; };
CA584C911DB4894500E36C26 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Configurations/Release.xcconfig; sourceTree = "<group>"; };
CA584C941DB4897800E36C26 /* SwresExplode.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = SwresExplode.xcconfig; path = SwresExplode/SwresExplode.xcconfig; sourceTree = "<group>"; };
CA584C951DB4897F00E36C26 /* SwresFUSE.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwresFUSE.xcconfig; sourceTree = "<group>"; };
CA584C961DB491DF00E36C26 /* SwresFuse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwresFuse.swift; sourceTree = "<group>"; };
CA5AA8E91DB44814001866EB /* SwresFUSE */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwresFUSE; sourceTree = BUILT_PRODUCTS_DIR; };
CA5AA8EB1DB44814001866EB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
CA6136641DA4ADD0001F81DA /* Translator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Translator.swift; path = Translators/Translator.swift; sourceTree = "<group>"; };
CA9007C21D8DAFB500B7D2BD /* SwresExplode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwresExplode; sourceTree = BUILT_PRODUCTS_DIR; };
CA9007CC1D8DB31400B7D2BD /* SeekableReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeekableReader.swift; sourceTree = "<group>"; };
CA9007CE1D8DBCAF00B7D2BD /* ResourceForkReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceForkReader.swift; sourceTree = "<group>"; };
CA9007D01D8DBD1900B7D2BD /* FourCharCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourCharCode.swift; sourceTree = "<group>"; };
CA9007D41D8DF9C400B7D2BD /* Resource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
CA5AA8E61DB44814001866EB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
CA9007BF1D8DAFB500B7D2BD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
CA4150E31DAF439C005F689D /* SwresExpolode */ = {
isa = PBXGroup;
children = (
CA4150F01DAF4407005F689D /* main.swift */,
CA4150F11DAF4407005F689D /* SwresExplode.swift */,
CA584C941DB4897800E36C26 /* SwresExplode.xcconfig */,
);
name = SwresExpolode;
sourceTree = "<group>";
};
CA584C8D1DB47C9F00E36C26 /* Modules */ = {
isa = PBXGroup;
children = (
CA5AA8F01DB44E40001866EB /* FUSE */,
);
name = Modules;
sourceTree = "<group>";
};
CA584C8E1DB4893300E36C26 /* Configurations */ = {
isa = PBXGroup;
children = (
CA584C8F1DB4894500E36C26 /* Debug.xcconfig */,
CA584C901DB4894500E36C26 /* Project.xcconfig */,
CA584C911DB4894500E36C26 /* Release.xcconfig */,
);
name = Configurations;
sourceTree = "<group>";
};
CA5AA8EA1DB44814001866EB /* SwresFUSE */ = {
isa = PBXGroup;
children = (
CA5AA8EB1DB44814001866EB /* main.swift */,
CA4EB2DF1DB9E44300A775DF /* FilesystemNode.swift */,
CA584C961DB491DF00E36C26 /* SwresFuse.swift */,
CA584C951DB4897F00E36C26 /* SwresFUSE.xcconfig */,
);
path = SwresFUSE;
sourceTree = "<group>";
};
CA5AA8F01DB44E40001866EB /* FUSE */ = {
isa = PBXGroup;
children = (
CA584C8C1DB47C9300E36C26 /* module.modulemap */,
);
name = FUSE;
sourceTree = "<group>";
};
CA6136601DA49D48001F81DA /* Extras */ = {
isa = PBXGroup;
children = (
CA4150E51DAF4401005F689D /* DictionaryExtras.swift */,
CA4150E61DAF4401005F689D /* ErrorExtras.swift */,
CA4150E71DAF4401005F689D /* PointerExtras.swift */,
CA4150E41DAF4401005F689D /* SequenceExtras.swift */,
CA4150E81DAF4401005F689D /* StrideExtras.swift */,
CA4150E91DAF4401005F689D /* StringExtras.swift */,
);
name = Extras;
sourceTree = "<group>";
};
CA6136611DA4ADA2001F81DA /* Translators */ = {
isa = PBXGroup;
children = (
CA4150F41DAF440F005F689D /* PascalStringTranslator.swift */,
CA4150F51DAF440F005F689D /* SndTranslator.swift */,
CA6136641DA4ADD0001F81DA /* Translator.swift */,
CA2B20FE1DA57DAB00A14B92 /* TranslatorManager.swift */,
);
name = Translators;
sourceTree = "<group>";
};
CA9007B91D8DAFB500B7D2BD = {
isa = PBXGroup;
children = (
CA6136601DA49D48001F81DA /* Extras */,
CA9007C41D8DAFB500B7D2BD /* SwresTools */,
CA6136611DA4ADA2001F81DA /* Translators */,
CA4150E31DAF439C005F689D /* SwresExpolode */,
CA5AA8EA1DB44814001866EB /* SwresFUSE */,
CA584C8D1DB47C9F00E36C26 /* Modules */,
CA584C8E1DB4893300E36C26 /* Configurations */,
CA9007C31D8DAFB500B7D2BD /* Products */,
);
sourceTree = "<group>";
};
CA9007C31D8DAFB500B7D2BD /* Products */ = {
isa = PBXGroup;
children = (
CA9007C21D8DAFB500B7D2BD /* SwresExplode */,
CA5AA8E91DB44814001866EB /* SwresFUSE */,
);
name = Products;
sourceTree = "<group>";
};
CA9007C41D8DAFB500B7D2BD /* SwresTools */ = {
isa = PBXGroup;
children = (
CA9007D01D8DBD1900B7D2BD /* FourCharCode.swift */,
CA9007D41D8DF9C400B7D2BD /* Resource.swift */,
CA9007CE1D8DBCAF00B7D2BD /* ResourceForkReader.swift */,
CA9007CC1D8DB31400B7D2BD /* SeekableReader.swift */,
CA572C501DB2058E00A2AF8F /* Writer.swift */,
);
path = SwresTools;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
CA5AA8E81DB44814001866EB /* SwresFUSE */ = {
isa = PBXNativeTarget;
buildConfigurationList = CA5AA8EF1DB44814001866EB /* Build configuration list for PBXNativeTarget "SwresFUSE" */;
buildPhases = (
CA5AA8E51DB44814001866EB /* Sources */,
CA5AA8E61DB44814001866EB /* Frameworks */,
CA5AA8E71DB44814001866EB /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = SwresFUSE;
productName = SwresFUSE;
productReference = CA5AA8E91DB44814001866EB /* SwresFUSE */;
productType = "com.apple.product-type.tool";
};
CA9007C11D8DAFB500B7D2BD /* SwresExplode */ = {
isa = PBXNativeTarget;
buildConfigurationList = CA9007C91D8DAFB500B7D2BD /* Build configuration list for PBXNativeTarget "SwresExplode" */;
buildPhases = (
CA9007BE1D8DAFB500B7D2BD /* Sources */,
CA9007BF1D8DAFB500B7D2BD /* Frameworks */,
CA9007C01D8DAFB500B7D2BD /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = SwresExplode;
productName = SwresTools;
productReference = CA9007C21D8DAFB500B7D2BD /* SwresExplode */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
CA9007BA1D8DAFB500B7D2BD /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0810;
LastUpgradeCheck = 0810;
ORGANIZATIONNAME = "Paul Knight";
TargetAttributes = {