Skip to content

Instantly share code, notes, and snippets.

@samuraisam
Created December 2, 2015 09:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samuraisam/c3eb7452aa727b07654e to your computer and use it in GitHub Desktop.
Save samuraisam/c3eb7452aa727b07654e to your computer and use it in GitHub Desktop.
enum BRBSPatchError: ErrorType {
case Unknown
case CorruptPatch
case PatchFileDoesntExist
case OldFileDoesntExist
}
class BRBSPatch {
static let patchLogEnabled = true
static func patch(oldFilePath: String, newFilePath: String, patchFilePath: String) throws {
func offtin(b: UnsafePointer<CUnsignedChar>) -> off_t {
var y = off_t(b[0])
y |= off_t(b[1]) << 8
y |= off_t(b[2]) << 16
y |= off_t(b[3]) << 24
y |= off_t(b[4]) << 32
y |= off_t(b[5]) << 40
y |= off_t(b[6]) << 48
y |= off_t(b[7] & 0x7f) << 56
if Int(b[7]) & 0x80 != 0 {
y = -y
}
log("oftinbytes \(b[0]) \(b[1]) \(b[2]) \(b[3]) \(b[4]) \(b[5]) \(b[6]) \(b[7])")
return y
}
guard let patchFilePathData = patchFilePath.dataUsingEncoding(NSUTF8StringEncoding) else {
log("unable to convert patch file path into data")
throw BRBSPatchError.Unknown
}
let patchFilePathBytes = UnsafePointer<Int8>(patchFilePathData.bytes)
let r = UnsafePointer<Int8>("r".dataUsingEncoding(NSASCIIStringEncoding)!.bytes)
// open patch file
guard let f = NSFileHandle(forReadingAtPath: patchFilePath) else {
log("unable to open file for reading at path \(patchFilePath)")
throw BRBSPatchError.PatchFileDoesntExist
}
// read header
let headerData = f.readDataOfLength(32)
let header = UnsafePointer<CUnsignedChar>(headerData.bytes)
log("header \(headerData)")
if headerData.length != 32 {
log("incorrect header read length \(headerData.length)")
throw BRBSPatchError.CorruptPatch
}
// check for appropriate magic
let magicData = headerData.subdataWithRange(NSMakeRange(0, 8))
if let magic = NSString(data: magicData, encoding: NSASCIIStringEncoding)
where magic != "BSDIFF40" {
log("incorrect magic: \(magic)")
throw BRBSPatchError.CorruptPatch
}
for i in 0...31 {
log("headerbyte \(i) \(header[i])")
}
// read lengths from header
let bzCrtlLen = offtin(header + 8)
let bzDataLen = offtin(header + 16)
let newSize = offtin(header + 24)
log("bzCrtlLen \(bzCrtlLen)")
log("bzDataLen \(bzDataLen)")
log("newSize \(newSize)")
if bzCrtlLen < 0 || bzDataLen < 0 || newSize < 0 {
log("incorrect header data: crtlLen: \(bzCrtlLen) dataLen: \(bzDataLen) newSize: \(newSize)")
throw BRBSPatchError.CorruptPatch
}
// close patch file and re-open it with bzip2 at the right positions
f.closeFile()
let cpf = fopen(patchFilePathBytes, r)
if cpf == nil {
log("unable to open patch file c")
throw BRBSPatchError.Unknown
}
let cpfseek = fseeko(cpf, 32, SEEK_SET)
if cpfseek != 0 {
log("unable to seek patch file c: \(cpfseek)")
throw BRBSPatchError.Unknown
}
let cbz2err = UnsafeMutablePointer<Int32>.alloc(1)
let cpfbz2 = BZ2_bzReadOpen(cbz2err, cpf, 0, 0, nil, 0)
if cpfbz2 == nil {
log("unable to bzopen patch file c: \(cbz2err)")
throw BRBSPatchError.Unknown
}
let dpf = fopen(patchFilePathBytes, r)
if dpf == nil {
log("unable to open patch file d")
throw BRBSPatchError.Unknown
}
let dpfseek = fseeko(dpf, 32 + bzCrtlLen, SEEK_SET)
if dpfseek != 0 {
log("unable to seek patch file d: \(dpfseek)")
throw BRBSPatchError.Unknown
}
let dbz2err = UnsafeMutablePointer<Int32>.alloc(1)
let dpfbz2 = BZ2_bzReadOpen(dbz2err, dpf, 0, 0, nil, 0)
if dpfbz2 == nil {
log("unable to bzopen patch file d: \(dbz2err)")
throw BRBSPatchError.Unknown
}
let epf = fopen(patchFilePathBytes, r)
if epf == nil {
log("unable to open patch file e")
throw BRBSPatchError.Unknown
}
let epfseek = fseeko(epf, 32 + bzCrtlLen + bzDataLen, SEEK_SET)
if epfseek != 0 {
log("unable to seek patch file e: \(epfseek)")
throw BRBSPatchError.Unknown
}
let ebz2err = UnsafeMutablePointer<Int32>.alloc(1)
let epfbz2 = BZ2_bzReadOpen(ebz2err, epf, 0, 0, nil, 0)
if epfbz2 == nil {
log("unable to bzopen patch file e: \(ebz2err)")
throw BRBSPatchError.Unknown
}
guard let oldData = NSData(contentsOfFile: oldFilePath) else {
log("unable to read old file path")
throw BRBSPatchError.Unknown
}
let old = UnsafePointer<CUnsignedChar>(oldData.bytes)
let oldSize = off_t(oldData.length)
var oldPos: off_t = 0, newPos: off_t = 0
let new = UnsafeMutablePointer<CUnsignedChar>(Array<CChar>(count: Int(newSize) + 1, repeatedValue: 0))
let buf = UnsafeMutablePointer<CUnsignedChar>(Array<CChar>(count: 8, repeatedValue: 0))
var crtl = Array<off_t>(count: 3, repeatedValue: 0)
while newPos < newSize {
// read control data
for i in 0...2 {
let lenread = BZ2_bzRead(cbz2err, cpfbz2, buf, 8)
if (lenread < 8) || ((cbz2err.memory != BZ_OK) && (cbz2err.memory != BZ_STREAM_END)) {
log("unable to read control data \(lenread) \(cbz2err.memory)")
throw BRBSPatchError.CorruptPatch
}
crtl[i] = offtin(UnsafePointer<CUnsignedChar>(buf))
}
// sanity check
if (newPos + crtl[0]) > newSize {
log("incorrect size of crtl[0]")
throw BRBSPatchError.CorruptPatch
}
// read diff string
let dlenread = BZ2_bzRead(dbz2err, dpfbz2, new + Int(newPos), Int32(crtl[0]))
if (dlenread < Int32(crtl[0])) || ((dbz2err.memory != BZ_OK) && (dbz2err.memory != BZ_STREAM_END)) {
log("unable to read diff string \(dlenread) \(dbz2err.memory)")
throw BRBSPatchError.CorruptPatch
}
// add old data to diff string
for i in 0...(crtl[0]-1) {
if oldPos + i > 0 && oldPos + i < oldSize {
new[Int(newPos + i)] += old[Int(oldPos + i)]
}
}
// adjust pointers
newPos += crtl[0]
oldPos += crtl[0]
// sanity check
if (newPos + crtl[1]) > newSize {
log("incorrect size of crtl[1]")
throw BRBSPatchError.CorruptPatch
}
// read extra string
let elenread = BZ2_bzRead(ebz2err, epfbz2, new + Int(newPos), Int32(crtl[1]))
if (elenread < Int32(crtl[1])) || ((ebz2err.memory != BZ_OK) && (ebz2err.memory != BZ_STREAM_END)) {
log("unable to read extra string \(elenread) \(ebz2err.memory)")
throw BRBSPatchError.CorruptPatch
}
// adjust pointers
newPos += crtl[1]
oldPos += crtl[2]
}
// clean up bz2 reads
BZ2_bzReadClose(cbz2err, cpfbz2)
BZ2_bzReadClose(dbz2err, dpfbz2)
BZ2_bzReadClose(ebz2err, epfbz2)
if (fclose(cpf) != 0) || (fclose(dpf) != 0) || (fclose(epf) != 0) {
log("unable to close bzip file handles")
throw BRBSPatchError.Unknown
}
guard let newFh = NSFileHandle(forWritingAtPath: newFilePath) else {
log("unable to open new file for writing")
throw BRBSPatchError.Unknown
}
let newData = NSData(bytesNoCopy: new, length: Int(newSize))
newFh.writeData(newData)
newFh.closeFile()
}
static private func log(string: String) {
if patchLogEnabled {
print("[BRBSPatch] \(string)")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment