mirror of
https://github.com/ItsElaineTime/SwresTools.git
synced 2024-06-10 18:29:27 +00:00
Initial commit from private projects repository.
This commit is contained in:
parent
a5d55c35df
commit
d96267259c
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -63,3 +63,5 @@ fastlane/report.xml
|
||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
fastlane/screenshots
|
fastlane/screenshots
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
4
Configurations/Debug.xcconfig
Normal file
4
Configurations/Debug.xcconfig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#include "Project.xcconfig"
|
||||||
|
|
||||||
|
ONLY_ACTIVE_ARCH = YES
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = -Onone
|
5
Configurations/Project.xcconfig
Normal file
5
Configurations/Project.xcconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
SDKROOT = macosx
|
||||||
|
WARNING_CFLAGS = -Wall -Wextra
|
||||||
|
|
||||||
|
SWIFT_VERSION = 3.0
|
||||||
|
SWIFT_INCLUDE_PATHS = "${PROJECT_DIR}/Modules"
|
5
Configurations/Release.xcconfig
Normal file
5
Configurations/Release.xcconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#include "Project.xcconfig"
|
||||||
|
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) NDEBUG=1
|
||||||
|
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = -O
|
18
Extras/DictionaryExtras.swift
Normal file
18
Extras/DictionaryExtras.swift
Normal 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
28
Extras/ErrorExtras.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
Extras/PointerExtras.swift
Normal file
36
Extras/PointerExtras.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
22
Extras/SequenceExtras.swift
Normal file
22
Extras/SequenceExtras.swift
Normal 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
10
Extras/StrideExtras.swift
Normal 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
100
Extras/StringExtras.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
5
Modules/FUSE/module.modulemap
Normal file
5
Modules/FUSE/module.modulemap
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module Fuse [system] {
|
||||||
|
header "/usr/local/include/osxfuse/fuse.h"
|
||||||
|
link "osxfuse"
|
||||||
|
export *
|
||||||
|
}
|
49
README
Normal file
49
README
Normal 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.
|
367
SwresExplode/SwresExplode.swift
Normal file
367
SwresExplode/SwresExplode.swift
Normal 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)
|
||||||
|
}
|
1
SwresExplode/SwresExplode.xcconfig
Normal file
1
SwresExplode/SwresExplode.xcconfig
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PRODUCT_NAME = SwresExplode
|
8
SwresExplode/main.swift
Normal file
8
SwresExplode/main.swift
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//
|
||||||
|
// main.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Darwin
|
||||||
|
|
||||||
|
exit(swresExplodeMain())
|
BIN
SwresFUSE.png
Normal file
BIN
SwresFUSE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
113
SwresFUSE/FilesystemNode.swift
Normal file
113
SwresFUSE/FilesystemNode.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
SwresFUSE/SwresFUSE.xcconfig
Normal file
6
SwresFUSE/SwresFUSE.xcconfig
Normal 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
324
SwresFUSE/SwresFuse.swift
Normal 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
8
SwresFUSE/main.swift
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//
|
||||||
|
// main.swift
|
||||||
|
// SwresFUSE
|
||||||
|
//
|
||||||
|
|
||||||
|
import Darwin
|
||||||
|
|
||||||
|
exit(swresFuseMain())
|
535
SwresTools.xcodeproj/project.pbxproj
Normal file
535
SwresTools.xcodeproj/project.pbxproj
Normal 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 = {
|
||||||
|
CA5AA8E81DB44814001866EB = {
|
||||||
|
CreatedOnToolsVersion = 8.0;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
|
CA9007C11D8DAFB500B7D2BD = {
|
||||||
|
CreatedOnToolsVersion = 8.0;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
|
CA9567C41DC132620031E5F5 = {
|
||||||
|
CreatedOnToolsVersion = 8.0;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = CA9007BD1D8DAFB500B7D2BD /* Build configuration list for PBXProject "SwresTools" */;
|
||||||
|
compatibilityVersion = "Xcode 3.2";
|
||||||
|
developmentRegion = English;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
);
|
||||||
|
mainGroup = CA9007B91D8DAFB500B7D2BD;
|
||||||
|
productRefGroup = CA9007C31D8DAFB500B7D2BD /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
CA9567C41DC132620031E5F5 /* Everything */,
|
||||||
|
CA9007C11D8DAFB500B7D2BD /* SwresExplode */,
|
||||||
|
CA5AA8E81DB44814001866EB /* SwresFUSE */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
CA5AA8E51DB44814001866EB /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
CA4EB2D11DB8057100A775DF /* PointerExtras.swift in Sources */,
|
||||||
|
CA4EB2D21DB8057100A775DF /* StrideExtras.swift in Sources */,
|
||||||
|
CA4EB2D71DB8057700A775DF /* SeekableReader.swift in Sources */,
|
||||||
|
CA4EB2DC1DB8057700A775DF /* Translator.swift in Sources */,
|
||||||
|
CA4EB2D01DB8057100A775DF /* ErrorExtras.swift in Sources */,
|
||||||
|
CA4EB2D51DB8057700A775DF /* Resource.swift in Sources */,
|
||||||
|
CA4EB2D31DB8057100A775DF /* StringExtras.swift in Sources */,
|
||||||
|
CA5AA8EC1DB44814001866EB /* main.swift in Sources */,
|
||||||
|
CA4EB2E11DB9ECCB00A775DF /* FilesystemNode.swift in Sources */,
|
||||||
|
CA4EB2CE1DB8057100A775DF /* SequenceExtras.swift in Sources */,
|
||||||
|
CA4EB2CF1DB8057100A775DF /* DictionaryExtras.swift in Sources */,
|
||||||
|
CA584C971DB491DF00E36C26 /* SwresFuse.swift in Sources */,
|
||||||
|
CA4EB2D91DB8057700A775DF /* TranslatorManager.swift in Sources */,
|
||||||
|
CA4EB2D81DB8057700A775DF /* Writer.swift in Sources */,
|
||||||
|
CA4EB2D61DB8057700A775DF /* ResourceForkReader.swift in Sources */,
|
||||||
|
CA4EB2DB1DB8057700A775DF /* SndTranslator.swift in Sources */,
|
||||||
|
CA4EB2D41DB8057700A775DF /* FourCharCode.swift in Sources */,
|
||||||
|
CA4EB2DA1DB8057700A775DF /* PascalStringTranslator.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
CA9007BE1D8DAFB500B7D2BD /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
CA6136651DA4ADD0001F81DA /* Translator.swift in Sources */,
|
||||||
|
CA4150F21DAF4407005F689D /* main.swift in Sources */,
|
||||||
|
CA9007CD1D8DB31400B7D2BD /* SeekableReader.swift in Sources */,
|
||||||
|
CA4150EA1DAF4401005F689D /* SequenceExtras.swift in Sources */,
|
||||||
|
CA4150F31DAF4407005F689D /* SwresExplode.swift in Sources */,
|
||||||
|
CA9007CF1D8DBCAF00B7D2BD /* ResourceForkReader.swift in Sources */,
|
||||||
|
CA9007D11D8DBD1900B7D2BD /* FourCharCode.swift in Sources */,
|
||||||
|
CA2B20FF1DA57DAB00A14B92 /* TranslatorManager.swift in Sources */,
|
||||||
|
CA4150EB1DAF4401005F689D /* DictionaryExtras.swift in Sources */,
|
||||||
|
CA4150EF1DAF4401005F689D /* StringExtras.swift in Sources */,
|
||||||
|
CA4150EC1DAF4401005F689D /* ErrorExtras.swift in Sources */,
|
||||||
|
CA4150EE1DAF4401005F689D /* StrideExtras.swift in Sources */,
|
||||||
|
CA572C511DB2058E00A2AF8F /* Writer.swift in Sources */,
|
||||||
|
CA4150ED1DAF4401005F689D /* PointerExtras.swift in Sources */,
|
||||||
|
CA4150F71DAF440F005F689D /* SndTranslator.swift in Sources */,
|
||||||
|
CA9007D51D8DF9C400B7D2BD /* Resource.swift in Sources */,
|
||||||
|
CA4150F61DAF440F005F689D /* PascalStringTranslator.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
CA9567C91DC1326A0031E5F5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = CA9007C11D8DAFB500B7D2BD /* SwresExplode */;
|
||||||
|
targetProxy = CA9567C81DC1326A0031E5F5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
CA9567CB1DC1326C0031E5F5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = CA5AA8E81DB44814001866EB /* SwresFUSE */;
|
||||||
|
targetProxy = CA9567CA1DC1326C0031E5F5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
CA5AA8ED1DB44814001866EB /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C951DB4897F00E36C26 /* SwresFUSE.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
CA5AA8EE1DB44814001866EB /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C951DB4897F00E36C26 /* SwresFUSE.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
CA9007C71D8DAFB500B7D2BD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C8F1DB4894500E36C26 /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
CA9007C81D8DAFB500B7D2BD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C911DB4894500E36C26 /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
CA9007CA1D8DAFB500B7D2BD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C941DB4897800E36C26 /* SwresExplode.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
CA9007CB1D8DAFB500B7D2BD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = CA584C941DB4897800E36C26 /* SwresExplode.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
CA9567C61DC132620031E5F5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
CA9567C71DC132620031E5F5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
CA5AA8EF1DB44814001866EB /* Build configuration list for PBXNativeTarget "SwresFUSE" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
CA5AA8ED1DB44814001866EB /* Debug */,
|
||||||
|
CA5AA8EE1DB44814001866EB /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
CA9007BD1D8DAFB500B7D2BD /* Build configuration list for PBXProject "SwresTools" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
CA9007C71D8DAFB500B7D2BD /* Debug */,
|
||||||
|
CA9007C81D8DAFB500B7D2BD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
CA9007C91D8DAFB500B7D2BD /* Build configuration list for PBXNativeTarget "SwresExplode" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
CA9007CA1D8DAFB500B7D2BD /* Debug */,
|
||||||
|
CA9007CB1D8DAFB500B7D2BD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
CA9567C51DC132620031E5F5 /* Build configuration list for PBXAggregateTarget "Everything" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
CA9567C61DC132620031E5F5 /* Debug */,
|
||||||
|
CA9567C71DC132620031E5F5 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = CA9007BA1D8DAFB500B7D2BD /* Project object */;
|
||||||
|
}
|
7
SwresTools.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
SwresTools.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:SwresTools.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
50
SwresTools/FourCharCode.swift
Normal file
50
SwresTools/FourCharCode.swift
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// FourCharCode.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum FourCharCodeError: SwresError {
|
||||||
|
case invalidSequence
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
return "Cannot construct FourCharCode from invalid byte sequence."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FourCharCode: Equatable, Hashable, CustomStringConvertible {
|
||||||
|
let bytes: Data
|
||||||
|
|
||||||
|
init(_ bytes: Data) throws {
|
||||||
|
guard bytes.count == 4 else {
|
||||||
|
throw FourCharCodeError.invalidSequence
|
||||||
|
}
|
||||||
|
self.bytes = bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ cString: UnsafeMutablePointer<CChar>) throws {
|
||||||
|
let string = String(cString: cString)
|
||||||
|
try self.init(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ string: String) throws {
|
||||||
|
guard let data = string.data(using: String.Encoding.utf8) else {
|
||||||
|
throw FourCharCodeError.invalidSequence
|
||||||
|
}
|
||||||
|
try self.init(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: FourCharCode, rhs: FourCharCode) -> Bool {
|
||||||
|
return lhs.bytes == rhs.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
return (Int(bytes[0]) << 24) + (Int(bytes[1]) << 16) + (Int(bytes[2]) << 8) + (Int(bytes[3]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
let options = MacOSRomanConversionOptions(filterControlCharacters: true, filterFilesystemUnsafeCharacters: false, filterNonASCIICharacters: false, replacementMacOSRomanByte: MacOSRomanByteQuestionMark)
|
||||||
|
return stringFromMacOSRomanBytes(bytes, options: options)
|
||||||
|
}
|
||||||
|
}
|
27
SwresTools/Resource.swift
Normal file
27
SwresTools/Resource.swift
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Resource.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Resource: CustomStringConvertible {
|
||||||
|
let type: FourCharCode
|
||||||
|
let identifier: Int16
|
||||||
|
let name: Data?
|
||||||
|
let data: Data
|
||||||
|
|
||||||
|
var stringName: String? {
|
||||||
|
guard let name = name else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = MacOSRomanConversionOptions(filterControlCharacters: true, filterFilesystemUnsafeCharacters: false, filterNonASCIICharacters: false, replacementMacOSRomanByte: MacOSRomanByteQuestionMark)
|
||||||
|
return stringFromMacOSRomanBytes(name, options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
let formattedName = stringName ?? ""
|
||||||
|
return "<Resource type: \"\(type)\", identifier: \(identifier), name: \"\(formattedName)\", data length: \(data.count)>"
|
||||||
|
}
|
||||||
|
}
|
189
SwresTools/ResourceForkReader.swift
Normal file
189
SwresTools/ResourceForkReader.swift
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
//
|
||||||
|
// ResourceManager.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
// Resource Map documentation comes from Inside Macintosh: More Macintosh Toolbox (1993).
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
typealias ResourcesByType = Dictionary<FourCharCode, Array<Resource>>
|
||||||
|
|
||||||
|
enum ResourceForkReaderError: NestingSwresError {
|
||||||
|
case emptyResourceFork
|
||||||
|
case couldntReadResourceFork(underlyingError: Error?)
|
||||||
|
case invalidFormat(underlyingError: SeekableReaderError)
|
||||||
|
case other
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .emptyResourceFork:
|
||||||
|
return "The resource fork is empty."
|
||||||
|
case .couldntReadResourceFork(_):
|
||||||
|
return "Couldn't read resource fork."
|
||||||
|
case .invalidFormat(_):
|
||||||
|
return "Input file is corrupted or not a resource fork."
|
||||||
|
case .other :
|
||||||
|
return "An unexpected error happend while reading the resource fork."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingError: Error? {
|
||||||
|
switch self {
|
||||||
|
case .couldntReadResourceFork(let subError):
|
||||||
|
return subError
|
||||||
|
case .invalidFormat(let subError):
|
||||||
|
return subError
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resource header is:
|
||||||
|
// * Offset from beginning of resource fork to resource data (4)
|
||||||
|
// * Offset from beginning of resource fork to resource map (4)
|
||||||
|
// * Length of resource data (4)
|
||||||
|
// * Length of resource map (4)
|
||||||
|
func readResourceFork(_ path: URL) throws -> ResourcesByType {
|
||||||
|
let data = try _readResourceFork(path)
|
||||||
|
|
||||||
|
do {
|
||||||
|
var reader = SeekableReader(data)
|
||||||
|
let dataOffset = try reader.readInt32()
|
||||||
|
let mapOffset = try reader.readInt32()
|
||||||
|
return try _parseResourceMap(reader: reader, dataOffset: dataOffset, mapOffset: mapOffset)
|
||||||
|
} catch let error as SeekableReaderError {
|
||||||
|
throw ResourceForkReaderError.invalidFormat(underlyingError: error)
|
||||||
|
} catch {
|
||||||
|
assertionFailure()
|
||||||
|
throw ResourceForkReaderError.other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _readResourceFork(_ path: URL) throws -> Data {
|
||||||
|
var error: Error?
|
||||||
|
|
||||||
|
guard let data = ["..namedfork/rsrc", ""].firstSome({ (suffix: String) -> Data? in
|
||||||
|
let url = path.appendingPathComponent(suffix)
|
||||||
|
|
||||||
|
var data: Data?
|
||||||
|
do {
|
||||||
|
data = try Data(contentsOf: url)
|
||||||
|
} catch let lastError {
|
||||||
|
error = lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != nil && data!.count == 0 {
|
||||||
|
error = ResourceForkReaderError.emptyResourceFork
|
||||||
|
data = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}) else {
|
||||||
|
throw ResourceForkReaderError.couldntReadResourceFork(underlyingError: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resource map is:
|
||||||
|
// * Reserved for copy of resource header (16)
|
||||||
|
// * Reserved for handle to next resource map (4)
|
||||||
|
// * Reserved for file reference number (2)
|
||||||
|
// * Resource fork attributes (2)
|
||||||
|
// * Offset from beginning of map to resource type list (2) [1]
|
||||||
|
// * Offset from beginning of map to resource name list (2)
|
||||||
|
// * Number of types in the map minus 1 (2)
|
||||||
|
// * Resource type list (Variable)
|
||||||
|
// * Reference lists (Variable)
|
||||||
|
// * Resource name list (Variable)
|
||||||
|
//
|
||||||
|
// Type list starts with:
|
||||||
|
// * Number of types in the map minus 1
|
||||||
|
// Each type is:
|
||||||
|
// * Resource type (4)
|
||||||
|
// * Number of resources of this type in map minus 1 (2)
|
||||||
|
// * Offset from beginning of resource type list to reference list for this type (2)
|
||||||
|
//
|
||||||
|
// [1] Actually points to the type count, not the start of the variable length type list
|
||||||
|
func _parseResourceMap(reader: SeekableReader, dataOffset: Int32, mapOffset: Int32) throws -> ResourcesByType {
|
||||||
|
var reader = reader
|
||||||
|
var resourcesByType = ResourcesByType()
|
||||||
|
|
||||||
|
try reader.seek(mapOffset)
|
||||||
|
try reader.skip(16 + 4 + 2 + 2) // Header copy, handle, file no., attributes
|
||||||
|
|
||||||
|
let typeListOffset = mapOffset + Int32(try reader.readInt16())
|
||||||
|
let nameListOffset = mapOffset + Int32(try reader.readInt16())
|
||||||
|
|
||||||
|
try reader.seek(typeListOffset)
|
||||||
|
let typeCount = try reader.readInt16() + 1
|
||||||
|
|
||||||
|
for _ in 1...typeCount {
|
||||||
|
let fourCharCodeBytes = try reader.readBytes(4)
|
||||||
|
let type = try FourCharCode(fourCharCodeBytes)
|
||||||
|
let resourceCount = try reader.readInt16() + 1
|
||||||
|
let referenceListOffset = typeListOffset + Int32(try reader.readInt16())
|
||||||
|
|
||||||
|
resourcesByType[type] = try _parseReferenceList(reader: reader, dataOffset: dataOffset, referenceListOffset: referenceListOffset, nameListOffset: nameListOffset, type: type, resourceCount: resourceCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourcesByType
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reference list entry is:
|
||||||
|
// * Resource ID (2)
|
||||||
|
// * Offset from beginning of resource name list to resource name (2)
|
||||||
|
// * Resource attributes (1)
|
||||||
|
// * Offset from beginning of resource data to data for this resource (3)
|
||||||
|
// * Reserved for handle to resource (4)
|
||||||
|
private func _parseReferenceList(reader: SeekableReader, dataOffset: Int32, referenceListOffset: Int32, nameListOffset: Int32, type: FourCharCode, resourceCount: Int16) throws -> Array<Resource> {
|
||||||
|
var reader = reader
|
||||||
|
var resources = Array<Resource>()
|
||||||
|
resources.reserveCapacity(Int(resourceCount))
|
||||||
|
|
||||||
|
try reader.seek(referenceListOffset)
|
||||||
|
for _ in 1...resourceCount {
|
||||||
|
let identifier = try reader.readInt16()
|
||||||
|
|
||||||
|
var name: Data?
|
||||||
|
let nameOffset = try reader.readInt16()
|
||||||
|
if nameOffset != -1 {
|
||||||
|
let absoluteNameOffset = nameListOffset + Int32(nameOffset)
|
||||||
|
try name = _parseName(reader: reader, offset: absoluteNameOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
try reader.skip(1) // Resource attributes
|
||||||
|
|
||||||
|
let relativeDataOffset = try reader.readInt24()
|
||||||
|
let absoluteDataOffset = dataOffset + Int32(relativeDataOffset)
|
||||||
|
let data = try _parseResourceData(reader: reader, offset: absoluteDataOffset)
|
||||||
|
|
||||||
|
let resource = Resource(type: type, identifier: identifier, name: name, data: data)
|
||||||
|
resources.append(resource)
|
||||||
|
|
||||||
|
try reader.skip(4) // Handle to resource
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
// A name is:
|
||||||
|
// * Length of following resource name (1)
|
||||||
|
// * Characters of resource name (Variable)
|
||||||
|
private func _parseName(reader: SeekableReader, offset: Int32) throws -> Data {
|
||||||
|
var reader = reader
|
||||||
|
try reader.seek(offset)
|
||||||
|
let length = Int(try reader.readInt8())
|
||||||
|
let bytes = try reader.readBytes(length)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _parseResourceData(reader: SeekableReader, offset: Int32) throws -> Data {
|
||||||
|
var reader = reader
|
||||||
|
try reader.seek(offset)
|
||||||
|
|
||||||
|
let length = try reader.readInt32()
|
||||||
|
return try reader.readBytes(length)
|
||||||
|
}
|
103
SwresTools/SeekableReader.swift
Normal file
103
SwresTools/SeekableReader.swift
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// SeekableReader.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SeekableReaderError: SwresError {
|
||||||
|
case invalidLocation(Int)
|
||||||
|
case invalidRange(location: Int, length: Int)
|
||||||
|
case invalidParameter(name: String, value: Any)
|
||||||
|
case internalError
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch(self) {
|
||||||
|
case .invalidLocation(let location):
|
||||||
|
return String.init(format: "Invalid seek to location 0x%x.", location)
|
||||||
|
case .invalidRange(let location, let length):
|
||||||
|
return String.init(format: "Invalid read at location 0x%x with length %d.", location, length)
|
||||||
|
case .invalidParameter:
|
||||||
|
return "Program error."
|
||||||
|
case .internalError:
|
||||||
|
return "Internal error."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes big-endian byte order.
|
||||||
|
struct SeekableReader {
|
||||||
|
private let _data: Data
|
||||||
|
private var _offset: Int
|
||||||
|
|
||||||
|
init(_ data: Data) {
|
||||||
|
_data = data
|
||||||
|
_offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private mutating func _readUInt32(length: Int) throws -> UInt32 {
|
||||||
|
guard length > 0 else {
|
||||||
|
assertionFailure()
|
||||||
|
throw SeekableReaderError.internalError
|
||||||
|
}
|
||||||
|
guard _offset + length <= _data.count else {
|
||||||
|
throw SeekableReaderError.invalidRange(location: _offset, length: length)
|
||||||
|
}
|
||||||
|
|
||||||
|
var value: UInt32 = 0
|
||||||
|
for _ in 1...length {
|
||||||
|
let byte = _data[_offset]
|
||||||
|
value = (value << 8) + UInt32(byte)
|
||||||
|
_offset += 1
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readInt8() throws -> Int8 {
|
||||||
|
return try Int8(truncatingBitPattern: _readUInt32(length: 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readInt16() throws -> Int16 {
|
||||||
|
return try Int16(truncatingBitPattern: _readUInt32(length: 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readInt24() throws -> Int32 {
|
||||||
|
return try Int32(bitPattern: _readUInt32(length: 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readInt32() throws -> Int32 {
|
||||||
|
return try Int32(bitPattern: _readUInt32(length: 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readBytes(_ length: Int) throws -> Data {
|
||||||
|
guard length > 0 else {
|
||||||
|
throw SeekableReaderError.invalidParameter(name: "length", value: length)
|
||||||
|
}
|
||||||
|
guard _offset + length <= _data.count else {
|
||||||
|
throw SeekableReaderError.invalidRange(location: _offset, length: length)
|
||||||
|
}
|
||||||
|
|
||||||
|
let subdata = _data.subdata(in: _offset..<(_offset + length))
|
||||||
|
_offset += length
|
||||||
|
return subdata
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func readBytes(_ length: Int32) throws -> Data {
|
||||||
|
return try readBytes(Int(length))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func seek(_ offset: Int) throws {
|
||||||
|
guard offset >= 0 && offset < _data.count else {
|
||||||
|
throw SeekableReaderError.invalidLocation(offset)
|
||||||
|
}
|
||||||
|
_offset = offset
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func seek(_ offset: Int32) throws {
|
||||||
|
try seek(Int(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func skip(_ offset: Int) throws {
|
||||||
|
try(seek(_offset + offset))
|
||||||
|
}
|
||||||
|
}
|
125
SwresTools/Writer.swift
Normal file
125
SwresTools/Writer.swift
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
//
|
||||||
|
// Writer.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum WriterEndianness {
|
||||||
|
case littleEndian
|
||||||
|
case bigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WriterError: SwresError {
|
||||||
|
case invalidParameter(message: String)
|
||||||
|
case alreadyFinalized
|
||||||
|
case internalError
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidParameter(let message):
|
||||||
|
return "Invalid parameter. \(message)"
|
||||||
|
case .alreadyFinalized:
|
||||||
|
return "The writer has already been finalized."
|
||||||
|
case .internalError:
|
||||||
|
return "Internal error."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Writer {
|
||||||
|
private let _endianness: WriterEndianness
|
||||||
|
private var _outputStream: OutputStream
|
||||||
|
private var _finalized: Bool = false
|
||||||
|
|
||||||
|
init(endianness: WriterEndianness) {
|
||||||
|
_endianness = endianness
|
||||||
|
_outputStream = OutputStream.toMemory()
|
||||||
|
_outputStream.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _unwrapped(_ endianness: WriterEndianness?) -> WriterEndianness {
|
||||||
|
if let unwrappedEndianness = endianness {
|
||||||
|
return unwrappedEndianness
|
||||||
|
}
|
||||||
|
return _endianness
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _validateNotFinalized() throws {
|
||||||
|
guard !_finalized else {
|
||||||
|
throw WriterError.alreadyFinalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(_ value: Int16, endianness: WriterEndianness? = nil) throws {
|
||||||
|
try _validateNotFinalized()
|
||||||
|
|
||||||
|
var value = value
|
||||||
|
|
||||||
|
switch _unwrapped(endianness) {
|
||||||
|
case .littleEndian:
|
||||||
|
value = value.littleEndian
|
||||||
|
case .bigEndian:
|
||||||
|
value = value.bigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
try withUnsafePointer(to: &value) { (valuePointer: UnsafePointer<Int16>) in
|
||||||
|
let bytePointer = UnsafeRawPointer(valuePointer).assumingMemoryBound(to: UInt8.self)
|
||||||
|
try _write(bytePointer, length: MemoryLayout<UInt16>.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(_ value: Int32, endianness: WriterEndianness? = nil) throws {
|
||||||
|
try _validateNotFinalized()
|
||||||
|
|
||||||
|
var value = value
|
||||||
|
|
||||||
|
switch _unwrapped(endianness) {
|
||||||
|
case .littleEndian:
|
||||||
|
value = value.littleEndian
|
||||||
|
case .bigEndian:
|
||||||
|
value = value.bigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
try withUnsafePointer(to: &value) { (valuePointer: UnsafePointer<Int32>) in
|
||||||
|
let bytePointer = UnsafeRawPointer(valuePointer).assumingMemoryBound(to: UInt8.self)
|
||||||
|
try _write(bytePointer, length: MemoryLayout<UInt32>.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(_ data: Data) throws {
|
||||||
|
try _validateNotFinalized()
|
||||||
|
|
||||||
|
_ = try data.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
|
||||||
|
try _write(pointer, length: data.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(ascii: String) throws {
|
||||||
|
try _validateNotFinalized()
|
||||||
|
|
||||||
|
guard let data = ascii.data(using: String.Encoding.ascii, allowLossyConversion: true) else {
|
||||||
|
throw WriterError.invalidParameter(message: "Invalid ASCII string.")
|
||||||
|
}
|
||||||
|
|
||||||
|
try write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _write(_ pointer: UnsafePointer<UInt8>, length: Int) throws {
|
||||||
|
let writtenBytes = _outputStream.write(pointer, maxLength: length)
|
||||||
|
guard writtenBytes == length else {
|
||||||
|
throw WriterError.internalError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func finalize() throws -> Data {
|
||||||
|
try _validateNotFinalized()
|
||||||
|
|
||||||
|
let result = _outputStream.property(forKey: Stream.PropertyKey.dataWrittenToMemoryStreamKey)
|
||||||
|
guard let unwrappedResult = result, let data = unwrappedResult as? Data else {
|
||||||
|
throw WriterError.internalError
|
||||||
|
}
|
||||||
|
_finalized = true
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
49
Translators/PascalStringTranslator.swift
Normal file
49
Translators/PascalStringTranslator.swift
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// PascalStringTranslator.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PascalStringTranslator: Translator {
|
||||||
|
@discardableResult func _looksLikePascalString(_ data: Data) throws -> Bool {
|
||||||
|
guard data.count > 0 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Can't translate empty data.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let length = data[0]
|
||||||
|
guard data.count == Int(length) + 1 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Resource length doesn't match first byte prefix.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
static private var _strType: FourCharCode = try! FourCharCode("STR ")
|
||||||
|
|
||||||
|
func compatibilityWith(resource: Resource) -> TranslatorCompatibility {
|
||||||
|
do {
|
||||||
|
try _looksLikePascalString(resource.data)
|
||||||
|
} catch {
|
||||||
|
return TranslatorCompatibility.notCompatible
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resource.type {
|
||||||
|
case PascalStringTranslator._strType:
|
||||||
|
return TranslatorCompatibility.likelyCompatible
|
||||||
|
default:
|
||||||
|
return TranslatorCompatibility.possiblyCompatible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func translate(resource: Resource) throws -> Translation {
|
||||||
|
let data = resource.data
|
||||||
|
try _looksLikePascalString(data)
|
||||||
|
|
||||||
|
let options = MacOSRomanConversionOptions(filterControlCharacters: false, filterFilesystemUnsafeCharacters: false, filterNonASCIICharacters: false, replacementMacOSRomanByte: nil)
|
||||||
|
let string = stringFromMacOSRomanBytes(data.subdata(in: 1..<data.count), options: options)
|
||||||
|
let stringData = string.data(using: String.Encoding.utf8)
|
||||||
|
|
||||||
|
return Translation(data: stringData!, suggestedFileExtension: "txt")
|
||||||
|
}
|
||||||
|
}
|
149
Translators/SndTranslator.swift
Normal file
149
Translators/SndTranslator.swift
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
//
|
||||||
|
// SndTranslator.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
// This is an incredibly basic converter for `Snd ' resources. It does not attempt to actually
|
||||||
|
// parse headers or commands property. It does not recognize extended headers. It does not
|
||||||
|
// do anything useful for anything other than the most basic, 8-bit mono PCM samples with
|
||||||
|
// super boring and obvious headers.
|
||||||
|
|
||||||
|
// Resournce information comes from Inside Macintosh (1993).
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
private let SndResourceType: FourCharCode = try! FourCharCode("snd ")
|
||||||
|
private let SuggestedFileExtension: String = "wav"
|
||||||
|
|
||||||
|
private let WAVHeaderCount = 44
|
||||||
|
|
||||||
|
private struct SndResource {
|
||||||
|
let sampleRate: Int
|
||||||
|
let data: Data
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SndTranslator: Translator {
|
||||||
|
func compatibilityWith(resource: Resource) -> TranslatorCompatibility {
|
||||||
|
if resource.type == SndResourceType {
|
||||||
|
return TranslatorCompatibility.likelyCompatible
|
||||||
|
}
|
||||||
|
return TranslatorCompatibility.notCompatible
|
||||||
|
}
|
||||||
|
|
||||||
|
func translate(resource: Resource) throws -> Translation {
|
||||||
|
let reader = SeekableReader(resource.data)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let sndResource = try _readSndResource(reader)
|
||||||
|
let data = try _wavData(sndResource)
|
||||||
|
return Translation(data: data, suggestedFileExtension: SuggestedFileExtension)
|
||||||
|
} catch let error {
|
||||||
|
throw TranslatorError.invalidResource(reason: "Failed to parse snd resource.", underlyingError: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _readSndResource(_ reader: SeekableReader) throws -> SndResource {
|
||||||
|
var reader = reader
|
||||||
|
let format = try reader.readInt16()
|
||||||
|
|
||||||
|
guard format == 1 || format == 2 else {
|
||||||
|
throw TranslatorError.invalidResource(reason: "Unknown snd format \(format)", underlyingError: nil)
|
||||||
|
}
|
||||||
|
guard format == 1 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Format 2 snd resources are not supported.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataFormatCount = try reader.readInt16()
|
||||||
|
guard dataFormatCount > 0 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "The resource doesn't contain any data formats.")
|
||||||
|
}
|
||||||
|
guard dataFormatCount == 1 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "The author hasn't read the spec closely enough to understand what to do when the resource contains more than one data format.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataType = try reader.readInt16()
|
||||||
|
guard dataType == 5 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Only sampled sound data (type 0x0005) is supported.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the sound channel init options.
|
||||||
|
try reader.skip(4)
|
||||||
|
|
||||||
|
let commandCount = try reader.readInt16()
|
||||||
|
guard commandCount == 1 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "The author hasn't read the spec closely enough to understand what to do when the resource contains more than one sound command.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = try reader.readInt16()
|
||||||
|
guard command == Int16(bitPattern: 0x8051) else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Only bufferCmd commands are supported.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// param1 seems to be unused for this command.
|
||||||
|
try reader.skip(2)
|
||||||
|
let soundHeaderOffset = try reader.readInt32()
|
||||||
|
|
||||||
|
try reader.seek(soundHeaderOffset)
|
||||||
|
let sampleDataPointer = try reader.readInt32()
|
||||||
|
guard sampleDataPointer == 0 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "The author hasn't read the spec closely enough to understand what a non-zero sample data pointer means.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let sampleByteLength = try reader.readInt32()
|
||||||
|
guard sampleByteLength > 0 else {
|
||||||
|
throw TranslatorError.invalidResource(reason: "Sample has a negative length.", underlyingError: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sample rate is an unsigned 16.16 fixed point integer. Flooring the frequency loses
|
||||||
|
// information but the precision loss is less than one hundredth of one percent and WAV
|
||||||
|
// maybe doesn't support fractional frequencies?
|
||||||
|
let sampleRateFixed = try reader.readInt32()
|
||||||
|
let sampleRate = Int(sampleRateFixed >> 16)
|
||||||
|
|
||||||
|
// Skip the two loop point parameters
|
||||||
|
try reader.skip(8)
|
||||||
|
|
||||||
|
let sampleEncoding = try reader.readInt8()
|
||||||
|
guard sampleEncoding == 0 else {
|
||||||
|
throw TranslatorError.unsupportedResource(reason: "Encoded samples are not supported.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip baseFrequency
|
||||||
|
try reader.skip(1)
|
||||||
|
|
||||||
|
let sampleData = try reader.readBytes(sampleByteLength)
|
||||||
|
|
||||||
|
return SndResource(sampleRate: sampleRate, data: sampleData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func _wavData(_ sndResource: SndResource) throws -> Data {
|
||||||
|
let writer = Writer(endianness: .littleEndian)
|
||||||
|
|
||||||
|
let data = sndResource.data
|
||||||
|
let dataCount = data.count
|
||||||
|
let wavFileSize = dataCount + WAVHeaderCount
|
||||||
|
|
||||||
|
let sampleRate = sndResource.sampleRate
|
||||||
|
|
||||||
|
// The header ChunkSize does not include the first ChunkID and size, i.e.
|
||||||
|
// it's the file size minus the first 8 bytes
|
||||||
|
try writer.write(ascii: "RIFF")
|
||||||
|
try writer.write(Int32(wavFileSize - 8))
|
||||||
|
try writer.write(ascii: "WAVE")
|
||||||
|
|
||||||
|
try writer.write(ascii: "fmt ")
|
||||||
|
try writer.write(Int32(16)) // Subchunk size
|
||||||
|
try writer.write(Int16(1)) // AudioFormat: PCM
|
||||||
|
try writer.write(Int16(1)) // Channel count
|
||||||
|
try writer.write(Int32(sampleRate))
|
||||||
|
try writer.write(Int32(sampleRate)) // Bytes per second
|
||||||
|
try writer.write(Int16(1)) // Block alignment
|
||||||
|
try writer.write(Int16(8)) // Bits per sample
|
||||||
|
|
||||||
|
try writer.write(ascii: "data")
|
||||||
|
try writer.write(Int32(dataCount))
|
||||||
|
try writer.write(data)
|
||||||
|
|
||||||
|
return try writer.finalize()
|
||||||
|
}
|
||||||
|
}
|
49
Translators/Translator.swift
Normal file
49
Translators/Translator.swift
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// Translator.swift
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum TranslatorError: NestingSwresError {
|
||||||
|
case unsupportedResource(reason: String)
|
||||||
|
case invalidResource(reason: String?, underlyingError: Error?)
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .unsupportedResource(let reason):
|
||||||
|
return "The resource may be valid but this translator is not able to convert it. \(reason)"
|
||||||
|
case .invalidResource(let reason, _):
|
||||||
|
var message = "The resource does not appear to be valid."
|
||||||
|
if let unwrappedReason = reason {
|
||||||
|
message += " \(unwrappedReason)"
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var underlyingError: Error? {
|
||||||
|
switch self {
|
||||||
|
case .invalidResource(_, let error):
|
||||||
|
return error
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TranslatorCompatibility {
|
||||||
|
case notCompatible
|
||||||
|
case possiblyCompatible
|
||||||
|
case likelyCompatible
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Translation {
|
||||||
|
let data: Data
|
||||||
|
let suggestedFileExtension: String
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol Translator {
|
||||||
|
func compatibilityWith(resource: Resource) -> TranslatorCompatibility
|
||||||
|
func translate(resource: Resource) throws -> Translation
|
||||||
|
}
|
62
Translators/TranslatorManager.swift
Normal file
62
Translators/TranslatorManager.swift
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
//
|
||||||
|
// TranslatorManager
|
||||||
|
// SwresTools
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum TranslatorFilter: Int, Comparable {
|
||||||
|
case noTranslators
|
||||||
|
case onlyLikelyTranslators
|
||||||
|
case likelyAndPossibleTranslators
|
||||||
|
|
||||||
|
static func <(lhs: TranslatorFilter, rhs: TranslatorFilter) -> Bool {
|
||||||
|
return lhs.rawValue < rhs.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TranslationResult {
|
||||||
|
case translated(_: Translation)
|
||||||
|
case error(_: Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TranslatorManager {
|
||||||
|
static let sharedInstance = TranslatorManager()
|
||||||
|
|
||||||
|
private let _translators: Array<Translator>
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
_translators = [
|
||||||
|
PascalStringTranslator(),
|
||||||
|
SndTranslator(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
func translate(_ resource: Resource, includeTranslators translatorFilter: TranslatorFilter = TranslatorFilter.noTranslators) -> Array<TranslationResult> {
|
||||||
|
var translationResults = Array<TranslationResult>()
|
||||||
|
|
||||||
|
let translatorsByCompatibility = _translators.groupBy({ (translator: Translator) in
|
||||||
|
return translator.compatibilityWith(resource: resource)
|
||||||
|
})
|
||||||
|
|
||||||
|
var applicableTranslators = Array<Translator>()
|
||||||
|
if let likelyTranslators = translatorsByCompatibility[TranslatorCompatibility.likelyCompatible] {
|
||||||
|
applicableTranslators.append(contentsOf: likelyTranslators)
|
||||||
|
}
|
||||||
|
|
||||||
|
if translatorFilter >= TranslatorFilter.likelyAndPossibleTranslators, let possibleTranslators = translatorsByCompatibility[TranslatorCompatibility.possiblyCompatible] {
|
||||||
|
applicableTranslators.append(contentsOf: possibleTranslators)
|
||||||
|
}
|
||||||
|
|
||||||
|
for translator in applicableTranslators {
|
||||||
|
do {
|
||||||
|
let translation = try translator.translate(resource: resource)
|
||||||
|
translationResults.append(TranslationResult.translated(translation))
|
||||||
|
} catch let error {
|
||||||
|
translationResults.append(TranslationResult.error(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return translationResults
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user