├── LICENSE.txt ├── README.txt ├── build.xml └── src └── ie └── wombat └── jbdiff ├── JBDiff.java ├── JBPatch.java └── Util.java /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005, Joe Desbonnet (jdesbonnet@gmail.com). 2 | * Based on a direct translation of bsdiff utility by Colin Percival 3 | * and available at http://www.daemonology.net/bsdiff/ under the 4 | * same license. 5 | 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of the nor the 16 | * names of its contributors may be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY 20 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 23 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | README file for JBDiff (Java Binary Diff) 2 | Version: 0.1.1 3 | Release date: 1 Oct 2007 4 | 5 | JBDiff (Java Binary Diff) utility is a Java translation of the bsdiff (v4.2) utility 6 | by Colin Percival. See http://www.daemonology.net/bsdiff/ 7 | 8 | The file format is similar to, but currently not compatible with the bsdiff utility. 9 | This is because bsdiff uses bzip2 for compression which is not available in the 10 | standard Java libraries. Instead I use gzip (java.util.zip.*) 11 | 12 | The diff utility is very memory hungry. Attempting to diff very large files with 13 | insufficient RAM may cause your computer to 'trash' (ie become unusably slow and may 14 | require a reset to recover). Comparing two 20MB files will take approx 80 seconds 15 | on a 2GHz Pentium 4 and will require a maximum heap size of at least 220 MBytes. The 16 | maximum heap size can be specified using the -Xmx switch to the Java VM (see examples 17 | below). The patch utility has more modest resource requirements. 18 | 19 | EXAMPLES: 20 | 21 | To compare old.bin with new.bin and produce diff file new-old.diff: 22 | 23 | java -Xmx200m -classpath jbdiff.jar ie.wombat.jbdiff.JBDiff old.bin new.bin new-old.diff 24 | 25 | To patch old.bin with new-old.diff to produce new.bin: 26 | 27 | java -Xmx200m -classpath jbdiff.jar ie.wombat.jbdiff.JBPatch old.bin new.bin new-old.diff 28 | 29 | TODO: 30 | 31 | This first release is a rather blind port of the bsdiff utility. A vast bulk of 32 | the code ported to Java without any modification. There is scope 33 | for optimization (the C bsdiff runs in approx 50% faster than JBDiff). 34 | 35 | Also it would be nice to be able to produce output that is compatible with bsdiff. 36 | I need a bzip2 library for that. 37 | 38 | Any suggestions, feedback and bugs will be much appreciated. Please email 39 | to joe@galway.net 40 | 41 | LICENSE: 42 | 43 | Now available under BSD license (previously GPL). 44 | 45 | CHANGES SINCE 0.1.0 RELEASE: 46 | 47 | Change of license from GPL v2 to BSD license. 48 | 49 | Joe Desbonnet 50 | jdesbonnet@gmail.com 51 | 1 Oct 2007 52 | 53 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JBDiff build 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/ie/wombat/jbdiff/JBDiff.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package ie.wombat.jbdiff; 29 | 30 | import java.io.DataOutputStream; 31 | import java.io.File; 32 | import java.io.FileInputStream; 33 | import java.io.FileOutputStream; 34 | import java.io.IOException; 35 | import java.io.RandomAccessFile; 36 | import java.util.zip.GZIPOutputStream; 37 | 38 | /** 39 | * Java Binary Diff utility. Based on 40 | * bsdiff (v4.2) by Colin Percival 41 | * (see http://www.daemonology.net/bsdiff/ ) and distributed under BSD license. 42 | * 43 | *

44 | * Running this on large files may require an increase of the default 45 | * maximum heap size (use java -Xmx200m) 46 | *

47 | * 48 | * @author Joe Desbonnet, jdesbonnet@gmail.com 49 | * 50 | */ 51 | 52 | public class JBDiff { 53 | 54 | private static final String VERSION="jbdiff-0.1.1"; 55 | 56 | private static final int min(int x, int y) { 57 | return x < y ? x : y; 58 | } 59 | 60 | private final static void split(int[] I, int[] V, int start, int len, int h) { 61 | 62 | int i, j, k, x, tmp, jj, kk; 63 | 64 | if (len < 16) { 65 | for (k = start; k < start + len; k += j) { 66 | j = 1; 67 | x = V[I[k] + h]; 68 | for (i = 1; k + i < start + len; i++) { 69 | if (V[I[k + i] + h] < x) { 70 | x = V[I[k + i] + h]; 71 | j = 0; 72 | } 73 | 74 | if (V[I[k + i] + h] == x) { 75 | tmp = I[k + j]; 76 | I[k + j] = I[k + i]; 77 | I[k + i] = tmp; 78 | j++; 79 | } 80 | 81 | } 82 | 83 | for (i = 0; i < j; i++) 84 | V[I[k + i]] = k + j - 1; 85 | if (j == 1) 86 | I[k] = -1; 87 | } 88 | 89 | return; 90 | } 91 | 92 | 93 | x = V[I[start + len / 2] + h]; 94 | jj = 0; 95 | kk = 0; 96 | for (i = start; i < start + len; i++) { 97 | if (V[I[i] + h] < x) 98 | jj++; 99 | if (V[I[i] + h] == x) 100 | kk++; 101 | } 102 | 103 | jj += start; 104 | kk += jj; 105 | 106 | i = start; 107 | j = 0; 108 | k = 0; 109 | while (i < jj) { 110 | if (V[I[i] + h] < x) { 111 | i++; 112 | } else if (V[I[i] + h] == x) { 113 | tmp = I[i]; 114 | I[i] = I[jj + j]; 115 | I[jj + j] = tmp; 116 | j++; 117 | } else { 118 | tmp = I[i]; 119 | I[i] = I[kk + k]; 120 | I[kk + k] = tmp; 121 | k++; 122 | } 123 | 124 | } 125 | 126 | 127 | while (jj + j < kk) { 128 | if (V[I[jj + j] + h] == x) { 129 | j++; 130 | } else { 131 | tmp = I[jj + j]; 132 | I[jj + j] = I[kk + k]; 133 | I[kk + k] = tmp; 134 | k++; 135 | } 136 | 137 | } 138 | 139 | 140 | if (jj > start) { 141 | split(I, V, start, jj - start, h); 142 | } 143 | 144 | for (i = 0; i < kk - jj; i++) { 145 | V[I[jj + i]] = kk - 1; 146 | } 147 | 148 | if (jj == kk - 1) { 149 | I[jj] = -1; 150 | } 151 | 152 | if (start + len > kk) { 153 | split(I, V, kk, start + len - kk, h); 154 | } 155 | 156 | } 157 | 158 | /** 159 | * Fast suffix sorting (qsufsort). See paper "Faster Suffix Sorting" by N. Jesper Larsson, Kunihiko Sadakane. 160 | * http://www.larsson.dogma.net/research.html 161 | * 162 | * @param I 163 | * @param V 164 | * @param oldBuf 165 | */ 166 | private static void qsufsort(int[] I, int[] V, byte[] oldBuf) { 167 | 168 | int oldsize = oldBuf.length; 169 | 170 | int[] buckets = new int[256]; 171 | int i, h, len; 172 | 173 | for (i = 0; i < 256; i++) { 174 | buckets[i] = 0; 175 | } 176 | 177 | for (i = 0; i < oldsize; i++) { 178 | buckets[ (int)oldBuf[i] &0xff ]++; 179 | } 180 | 181 | for (i = 1; i < 256; i++) { 182 | buckets[i] += buckets[i - 1]; 183 | } 184 | 185 | for (i = 255; i > 0; i--) { 186 | buckets[i] = buckets[i - 1]; 187 | } 188 | 189 | buckets[0] = 0; 190 | 191 | for (i = 0; i < oldsize; i++) { 192 | I[++buckets[(int)oldBuf[i] & 0xff]] = i; 193 | } 194 | 195 | I[0] = oldsize; 196 | for (i = 0; i < oldsize; i++) { 197 | V[i] = buckets[(int)oldBuf[i] &0xff]; 198 | } 199 | V[oldsize] = 0; 200 | 201 | for (i = 1; i < 256; i++) { 202 | if (buckets[i] == buckets[i - 1] + 1) { 203 | I[buckets[i]] = -1; 204 | } 205 | } 206 | 207 | I[0] = -1; 208 | 209 | for (h = 1; I[0] != -(oldsize + 1); h += h) { 210 | len = 0; 211 | for (i = 0; i < oldsize + 1;) { 212 | if (I[i] < 0) { 213 | len -= I[i]; 214 | i -= I[i]; 215 | } else { 216 | //if(len) I[i-len]=-len; 217 | if (len != 0) { 218 | I[i - len] = -len; 219 | } 220 | len = V[I[i]] + 1 - i; 221 | split(I, V, i, len, h); 222 | i += len; 223 | len = 0; 224 | } 225 | 226 | } 227 | 228 | if (len != 0) { 229 | I[i - len] = -len; 230 | } 231 | } 232 | 233 | 234 | for (i = 0; i < oldsize + 1; i++) { 235 | I[V[i]] = i; 236 | } 237 | } 238 | 239 | /** 240 | * Count the number of bytes that match in oldBuf (starting at offset oldOffset) 241 | * and newBuf (starting at offset newOffset). 242 | * 243 | * @param oldBuf 244 | * @param oldOffset 245 | * @param newBuf 246 | * @param newOffset 247 | * @return 248 | */ 249 | private final static int matchlen(byte[] oldBuf, int oldOffset, byte[] newBuf, 250 | int newOffset) { 251 | int end = min(oldBuf.length - oldOffset, newBuf.length - newOffset); 252 | int i; 253 | for (i = 0; i < end; i++) { 254 | if (oldBuf[oldOffset+i] != newBuf[newOffset+i]) { 255 | break; 256 | } 257 | } 258 | return i; 259 | } 260 | 261 | private final static int search(int[] I, byte[] oldBuf, byte[] newBuf, 262 | int newBufOffset, int start, int end, IntByRef pos) { 263 | int x, y; 264 | 265 | if (end - start < 2) { 266 | x = matchlen(oldBuf, I[start], newBuf, newBufOffset); 267 | y = matchlen(oldBuf, I[end], newBuf, newBufOffset); 268 | 269 | if (x > y) { 270 | pos.value = I[start]; 271 | return x; 272 | } else { 273 | pos.value = I[end]; 274 | return y; 275 | } 276 | } 277 | 278 | 279 | x = start + (end - start) / 2; 280 | if (Util.memcmp(oldBuf, I[x], newBuf, newBufOffset) < 0) { 281 | return search(I, oldBuf, newBuf, newBufOffset, x, end, pos); 282 | } else { 283 | return search(I, oldBuf, newBuf, newBufOffset, start, x, pos); 284 | } 285 | 286 | } 287 | 288 | public static void bsdiff (File oldFile, File newFile, File diffFile) 289 | throws IOException { 290 | 291 | int oldsize = (int) oldFile.length(); 292 | byte[] oldBuf = new byte[oldsize]; 293 | 294 | FileInputStream in = new FileInputStream(oldFile); 295 | Util.readFromStream(in, oldBuf, 0, oldsize); 296 | in.close(); 297 | 298 | int[] I = new int[oldsize+1]; 299 | int[] V = new int[oldsize+1]; 300 | 301 | qsufsort(I, V, oldBuf); 302 | 303 | //free(V) 304 | V = null; 305 | System.gc(); 306 | 307 | int newsize = (int) newFile.length(); 308 | byte[] newBuf = new byte[newsize]; 309 | in = new FileInputStream(newFile); 310 | Util.readFromStream(in, newBuf, 0, newsize); 311 | in.close(); 312 | 313 | // diff block 314 | int dblen = 0; 315 | byte[] db = new byte[newsize]; 316 | 317 | // extra block 318 | int eblen = 0; 319 | byte[] eb = new byte[newsize]; 320 | 321 | 322 | /* 323 | * Diff file is composed as follows: 324 | * 325 | * Header (32 bytes) 326 | * Data (from offset 32 to end of file) 327 | * 328 | * Header: 329 | * Offset 0, length 8 bytes: file magic "jbdiff40" 330 | * Offset 8, length 8 bytes: length of ctrl block 331 | * Offset 16, length 8 bytes: length of compressed diff block 332 | * Offset 24, length 8 bytes: length of new file 333 | * 334 | * Data: 335 | * 32 (length ctrlBlockLen): ctrlBlock 336 | * 32+ctrlBlockLen (length diffBlockLen): diffBlock (gziped) 337 | * 32+ctrlBlockLen+diffBlockLen (to end of file): extraBlock (gziped) 338 | * 339 | * ctrlBlock comprises a set of records, each record 12 bytes. A record 340 | * comprises 3 x 32 bit integers. The ctrlBlock is not compressed. 341 | */ 342 | 343 | DataOutputStream diffOut = new DataOutputStream(new FileOutputStream( 344 | diffFile)); 345 | 346 | /* 347 | * Write as much of header as we have now. Size of ctrlBlock and diffBlock 348 | * must be filled in later. 349 | */ 350 | diffOut.write("jbdiff40".getBytes("US-ASCII")); 351 | diffOut.writeLong(-1); // place holder for ctrlBlockLen 352 | diffOut.writeLong(-1); // place holder for diffBlockLen 353 | diffOut.writeLong(newsize); 354 | 355 | 356 | int oldscore, scsc; 357 | 358 | int overlap, Ss, lens; 359 | int i; 360 | int scan = 0; 361 | int len = 0; 362 | int lastscan = 0; 363 | int lastpos = 0; 364 | int lastoffset = 0; 365 | 366 | IntByRef pos = new IntByRef(); 367 | int ctrlBlockLen = 0; 368 | 369 | while (scan < newsize) { 370 | 371 | oldscore = 0; 372 | 373 | for (scsc = scan += len; scan < newsize; scan++) { 374 | 375 | len = search(I, oldBuf, newBuf, scan, 0, oldsize, pos); 376 | 377 | for (; scsc < scan + len; scsc++) { 378 | if ((scsc + lastoffset < oldsize) 379 | && (oldBuf[scsc + lastoffset] == newBuf[scsc])) { 380 | oldscore++; 381 | } 382 | } 383 | 384 | if (((len == oldscore) && (len != 0)) || (len > oldscore + 8)) { 385 | break; 386 | } 387 | 388 | if ((scan + lastoffset < oldsize) 389 | && (oldBuf[scan + lastoffset] == newBuf[scan])) { 390 | oldscore--; 391 | } 392 | } 393 | 394 | 395 | if ((len != oldscore) || (scan == newsize)) { 396 | int s = 0; 397 | int Sf = 0; 398 | int lenf = 0; 399 | for (i = 0; (lastscan + i < scan) && (lastpos + i < oldsize);) { 400 | if (oldBuf[lastpos + i] == newBuf[lastscan + i]) 401 | s++; 402 | i++; 403 | if (s * 2 - i > Sf * 2 - lenf) { 404 | Sf = s; 405 | lenf = i; 406 | } 407 | } 408 | 409 | 410 | int lenb = 0; 411 | if (scan < newsize) { 412 | s = 0; 413 | int Sb = 0; 414 | for (i = 1; (scan >= lastscan + i) && (pos.value >= i); i++) { 415 | if (oldBuf[pos.value - i] == newBuf[scan - i]) 416 | s++; 417 | if (s * 2 - i > Sb * 2 - lenb) { 418 | Sb = s; 419 | lenb = i; 420 | } 421 | } 422 | } 423 | 424 | 425 | if (lastscan + lenf > scan - lenb) { 426 | overlap = (lastscan + lenf) - (scan - lenb); 427 | s = 0; 428 | Ss = 0; 429 | lens = 0; 430 | for (i = 0; i < overlap; i++) { 431 | if (newBuf[lastscan + lenf - overlap + i] == oldBuf[lastpos 432 | + lenf - overlap + i]) { 433 | s++; 434 | } 435 | if (newBuf[scan - lenb + i] == oldBuf[pos.value - lenb + i]) { 436 | s--; 437 | } 438 | if (s > Ss) { 439 | Ss = s; 440 | lens = i + 1; 441 | } 442 | } 443 | 444 | lenf += lens - overlap; 445 | lenb -= lens; 446 | } 447 | 448 | 449 | // ? byte casting introduced here -- might affect things 450 | for (i = 0; i < lenf; i++) { 451 | db[dblen + i] = (byte) (newBuf[lastscan + i] - oldBuf[lastpos 452 | + i]); 453 | } 454 | 455 | for (i = 0; i < (scan - lenb) - (lastscan + lenf); i++) { 456 | eb[eblen + i] = newBuf[lastscan + lenf + i]; 457 | } 458 | 459 | dblen += lenf; 460 | eblen += (scan - lenb) - (lastscan + lenf); 461 | 462 | 463 | /* 464 | * Write control block entry (3 x int) 465 | */ 466 | diffOut.writeInt(lenf); 467 | diffOut.writeInt ( (scan - lenb) - (lastscan + lenf) ); 468 | diffOut.writeInt ( (pos.value - lenb) - (lastpos + lenf) ); 469 | ctrlBlockLen += 12; 470 | 471 | lastscan = scan - lenb; 472 | lastpos = pos.value - lenb; 473 | lastoffset = pos.value - scan; 474 | } // end if 475 | } // end while loop 476 | 477 | 478 | 479 | GZIPOutputStream gzOut; 480 | 481 | /* 482 | * Write diff block 483 | */ 484 | gzOut = new GZIPOutputStream(diffOut); 485 | gzOut.write(db,0,dblen); 486 | gzOut.finish(); 487 | int diffBlockLen = diffOut.size() - ctrlBlockLen - 32; 488 | //System.err.println ("diffBlockLen=" + diffBlockLen); 489 | 490 | /* 491 | * Write extra block 492 | */ 493 | gzOut = new GZIPOutputStream(diffOut); 494 | gzOut.write(eb, 0, eblen); 495 | gzOut.finish(); 496 | long extraBlockLen = diffOut.size() - diffBlockLen - ctrlBlockLen - 32; 497 | //System.err.println ("extraBlockLen=" + extraBlockLen); 498 | 499 | diffOut.close(); 500 | 501 | /* 502 | * Write missing header info. Need to reopen the file with RandomAccessFile 503 | * for this. 504 | */ 505 | RandomAccessFile diff = new RandomAccessFile (diffFile, "rw"); 506 | diff.seek(8); 507 | diff.writeLong (ctrlBlockLen); // ctrlBlockLen (compressed) @offset 8 508 | diff.writeLong(diffBlockLen); // diffBlockLen (compressed) @offset 16 509 | diff.close(); 510 | } 511 | 512 | 513 | /** 514 | * Run JBDiff from the command line. Params: oldfile newfile difffile. 515 | * diff file will be created. 516 | * 517 | * @param arg 518 | * @throws IOException 519 | */ 520 | public static void main(String[] arg) throws IOException { 521 | 522 | if (arg.length != 3) { 523 | System.err.println("usage example: java -Xmx200m ie.wombat.jbdiff.JBDiff oldfile newfile patchfile\n"); 524 | return; 525 | } 526 | 527 | File oldFile = new File(arg[0]); 528 | File newFile = new File(arg[1]); 529 | File diffFile = new File(arg[2]); 530 | 531 | bsdiff (oldFile, newFile, diffFile); 532 | 533 | } 534 | 535 | private static class IntByRef { 536 | public int value; 537 | } 538 | } 539 | 540 | -------------------------------------------------------------------------------- /src/ie/wombat/jbdiff/JBPatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package ie.wombat.jbdiff; 29 | 30 | import java.io.DataInputStream; 31 | import java.io.File; 32 | import java.io.FileInputStream; 33 | import java.io.FileOutputStream; 34 | import java.io.IOException; 35 | import java.util.zip.GZIPInputStream; 36 | 37 | /** 38 | * Java Binary patcher (based on bspatch by Colin Percival) 39 | * 40 | * @author Joe Desbonnet, jdesbonnet@gmail.com 41 | */ 42 | public class JBPatch { 43 | 44 | private static final String VERSION="jbdiff-0.1.1"; 45 | 46 | /** 47 | * Run JBPatch from the command line. Params: oldfile newfile patchfile. 48 | * newfile will be created. 49 | * @param arg 50 | * @throws IOException 51 | */ 52 | public static void main(String[] arg) throws IOException { 53 | 54 | if (arg.length != 3) { 55 | System.err.println("usage example: java -Xmx200m ie.wombat.jbdiff.JBPatch oldfile newfile patchfile"); 56 | } 57 | 58 | File oldFile = new File(arg[0]); 59 | File newFile = new File(arg[1]); 60 | File diffFile = new File(arg[2]); 61 | 62 | bspatch (oldFile, newFile, diffFile); 63 | } 64 | 65 | 66 | 67 | public static void bspatch (File oldFile, File newFile, File diffFile) 68 | throws IOException { 69 | 70 | int oldpos, newpos; 71 | 72 | DataInputStream diffIn = new DataInputStream (new FileInputStream(diffFile)); 73 | 74 | 75 | // Diff file header. Comprises 4 x 64 bit fields: 76 | // 0 8 16 24 32 (byte offset) 77 | // +---------------+---------------+---------------+---------------+ 78 | // | headerMagic | ctrlBlockLen | diffBlockLen | newsize | 79 | // +---------------+---------------+---------------+---------------+ 80 | // headerMagic: Always "jbdiff40" (8 bytes) 81 | // ctrlBlockLen: Length of gzip compressed ctrlBlock (64 bit long) 82 | // diffBlockLen: length of gzip compressed diffBlock (64 bit long) 83 | // newsize: size of new file in bytes (64 bit long) 84 | 85 | long headerMagic = diffIn.readLong(); 86 | long ctrlBlockLen = diffIn.readLong(); 87 | long diffBlockLen = diffIn.readLong(); 88 | int newsize = (int)diffIn.readLong(); 89 | 90 | /* 91 | System.err.println ("newsize=" + newsize); 92 | System.err.println ("ctrlBlockLen=" + ctrlBlockLen); 93 | System.err.println ("diffBlockLen=" + diffBlockLen); 94 | System.err.println ("newsize=" + newsize); 95 | */ 96 | 97 | FileInputStream in; 98 | in = new FileInputStream (diffFile); 99 | in.skip(ctrlBlockLen + 32); 100 | GZIPInputStream diffBlockIn = new GZIPInputStream(in); 101 | 102 | in = new FileInputStream (diffFile); 103 | in.skip (diffBlockLen + ctrlBlockLen + 32); 104 | GZIPInputStream extraBlockIn = new GZIPInputStream(in); 105 | 106 | /* 107 | * Read in old file (file to be patched) to oldBuf 108 | */ 109 | int oldsize = (int) oldFile.length(); 110 | byte[] oldBuf = new byte[oldsize + 1]; 111 | FileInputStream oldIn = new FileInputStream(oldFile); 112 | Util.readFromStream(oldIn, oldBuf, 0, oldsize); 113 | oldIn.close(); 114 | 115 | byte[] newBuf = new byte[newsize + 1]; 116 | 117 | oldpos = 0; 118 | newpos = 0; 119 | int[] ctrl = new int[3]; 120 | int nbytes; 121 | while (newpos < newsize) { 122 | 123 | for (int i = 0; i <= 2; i++) { 124 | ctrl[i] = diffIn.readInt(); 125 | //System.err.println (" ctrl[" + i + "]=" + ctrl[i]); 126 | } 127 | 128 | if (newpos + ctrl[0] > newsize) { 129 | System.err.println("Corrupt patch\n"); 130 | return; 131 | } 132 | 133 | /* 134 | * Read ctrl[0] bytes from diffBlock stream 135 | */ 136 | 137 | if (! Util.readFromStream(diffBlockIn, newBuf, newpos, ctrl[0])) { 138 | System.err.println ("error reading from extraIn"); 139 | return; 140 | } 141 | 142 | for (int i = 0; i < ctrl[0]; i++) { 143 | if ((oldpos + i >= 0) && (oldpos + i < oldsize)) { 144 | newBuf[newpos + i] += oldBuf[oldpos + i]; 145 | } 146 | } 147 | 148 | newpos += ctrl[0]; 149 | oldpos += ctrl[0]; 150 | 151 | if (newpos + ctrl[1] > newsize) { 152 | System.err.println("Corrupt patch"); 153 | return; 154 | } 155 | 156 | 157 | if (! Util.readFromStream(extraBlockIn, newBuf, newpos, ctrl[1])) { 158 | System.err.println ("error reading from extraIn"); 159 | return; 160 | } 161 | 162 | newpos += ctrl[1]; 163 | oldpos += ctrl[2]; 164 | } 165 | 166 | 167 | 168 | // TODO: Check if at end of ctrlIn 169 | // TODO: Check if at the end of diffIn 170 | // TODO: Check if at the end of extraIn 171 | 172 | diffBlockIn.close(); 173 | extraBlockIn.close(); 174 | diffIn.close(); 175 | 176 | 177 | FileOutputStream out = new FileOutputStream(newFile); 178 | out.write(newBuf,0,newBuf.length-1); 179 | out.close(); 180 | } 181 | } 182 | 183 | -------------------------------------------------------------------------------- /src/ie/wombat/jbdiff/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the nor the 13 | * names of its contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package ie.wombat.jbdiff; 29 | 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | 33 | /** 34 | * @author Joe Desbonnet, jdesbonnet@gmail.com 35 | * 36 | */ 37 | public class Util { 38 | 39 | /** 40 | * Equiv of C library memcmp(). 41 | * 42 | * @param s1 43 | * @param s1offset 44 | * @param s2 45 | * @param n 46 | * @return 47 | */ 48 | /* 49 | public final static int memcmp(byte[] s1, int s1offset, byte[] s2, int s2offset, int n) { 50 | 51 | if ((s1offset + n) > s1.length) { 52 | n = s1.length - s1offset; 53 | } 54 | if ((s2offset + n) > s2.length) { 55 | n = s2.length - s2offset; 56 | } 57 | for (int i = 0; i < n; i++) { 58 | if (s1[i + s1offset] != s2[i + s2offset]) { 59 | return s1[i + s1offset] < s2[i + s2offset] ? -1 : 1; 60 | } 61 | } 62 | 63 | return 0; 64 | } 65 | */ 66 | 67 | /** 68 | * Equiv of C library memcmp(). 69 | * 70 | * @param s1 71 | * @param s1offset 72 | * @param s2 73 | * @param n 74 | * @return 75 | */ 76 | public final static int memcmp(byte[] s1, int s1offset, byte[] s2, int s2offset) { 77 | 78 | int n = s1.length - s1offset; 79 | 80 | if (n > (s2.length-s2offset)) { 81 | n = s2.length-s2offset; 82 | } 83 | for (int i = 0; i < n; i++) { 84 | if (s1[i + s1offset] != s2[i + s2offset]) { 85 | return s1[i + s1offset] < s2[i + s2offset] ? -1 : 1; 86 | } 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | public static final boolean readFromStream (InputStream in, byte[] buf, int offset, int len) 93 | throws IOException 94 | { 95 | 96 | int totalBytesRead = 0; 97 | int nbytes; 98 | 99 | while ( totalBytesRead < len) { 100 | nbytes = in.read(buf,offset+totalBytesRead,len-totalBytesRead); 101 | if (nbytes < 0) { 102 | System.err.println ("readFromStream(): returning prematurely. Read " 103 | + totalBytesRead + " bytes"); 104 | return false; 105 | } 106 | totalBytesRead+=nbytes; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | } 113 | --------------------------------------------------------------------------------