/*
 * $Id: Deobfuscator.java 4536 2008-11-25 16:39:05Z uckelman $
 *
 * Copyright (c) 2000-2009 by Rodney Kinney, Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available 
 * at http://www.opensource.org.
 */
package VASSAL.tools.io;

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;

/**
 * A {@link FilterInputStream} which converts a file created with
 * {@link ObfuscatingOutputStream} back into plain text.
 * Additionally, plain text will be passed through unchanged.
 *
 * @author Joel Uckelman
 * @since 3.2.0
 */
public class DeobfuscatingInputStream extends FilterInputStream {

  /**
   * @param in the stream to wrap
   * @throws IOException
   */
  public DeobfuscatingInputStream(InputStream in) throws IOException {
    super(null);

    final byte[] header = new byte[ObfuscatingOutputStream.HEADER.length()];
    readFully(in, header, 0, header.length);
    if (new String(header, "UTF-8").equals(ObfuscatingOutputStream.HEADER)) {
      this.in = new DeobfuscatingInputStreamImpl(in);
    }
    else {
      final PushbackInputStream pin =
        new PushbackInputStream(in, header.length);
      pin.unread(header);
      this.in = pin;
    }   
  }

  /**
   * Reads the given number of bytes.
   *
   * @param in the source
   * @param bytes the destination
   * @param off the offset into the destination array
   * @param len the number of bytes to read
   * @throws IOException if <code>len</code> bytes cannot be read
   */
  private static int readFully(InputStream in, byte[] bytes, int off, int len)
                                                           throws IOException {
    int left = len;
    int count = 0;
    while (left > 0 && (count = in.read(bytes, off, left)) != -1) {
      left -= count;
      off += count;
    }

    return len - left;
  }

  private static class DeobfuscatingInputStreamImpl extends FilterInputStream {
    private final byte key;
    private final byte[] pair = new byte[2];

    public DeobfuscatingInputStreamImpl(InputStream in) throws IOException {
      super(in);

      readFully(in, pair, 0, 2);
      key = (byte) ((unhex(pair[0]) << 4) | unhex(pair[1]));
    }

    @Override
    public int read(byte[] bytes, int off, int len) throws IOException {
      int b = 0;
      int i = 0;
      while (i < len && (b = read()) != -1) bytes[(i++)+off] = (byte) b;
      return b == -1 && i == 0 ? -1 : i;
    }

    @Override
    public int read() throws IOException {
      switch (readFully(in, pair, 0, 2)) {
      case  0:
        return -1;
      case  1:
        throw new IOException();
      case  2:
        return ((unhex(pair[0]) << 4) | unhex(pair[1])) ^ key;
      default:
        throw new IOException();
      }
    }

    private int unhex(int i) throws IOException {
      switch (i) {
      case 0x30:
      case 0x31:
      case 0x32:
      case 0x33:
      case 0x34:
      case 0x35:
      case 0x36:
      case 0x37:
      case 0x38:
      case 0x39:
        return i - 0x30;
      case 0x61:
      case 0x62:
      case 0x63:
      case 0x64:
      case 0x65:
      case 0x66:
        return i - 0x57;
      default:
        throw new IOException(String.valueOf(i));
      }
    }
  }

  public static void main(String[] args) throws IOException {
    InputStream in = null;
    try {
      in = new DeobfuscatingInputStream(
        args.length > 0 ? new FileInputStream(args[0]) : System.in);
      IOUtils.copy(in, System.out);
    }
    finally {
      IOUtils.closeQuietly(in);
    }

    System.exit(0);
  }
}

@@ -2297,18 +2297,13 @@
 int 
-left = len
+count
 ;%0A  
@@ -2308,21 +2308,17 @@
     int 
-count
+n
  = 0;%0A  
@@ -2330,21 +2330,25 @@
 le (
-left %3E 0 && (
+n %3C len) %7B%0A      
 coun
@@ -2373,56 +2373,60 @@
  off
-, left)) != -1) %7B%0A      left -= count;
+ + n, len - n);%0A      if (count %3C 0) break; 
 %0A      
-off
+n
  += 
@@ -2454,18 +2454,9 @@
 urn 
-len - left
+n
 ;%0A  
@@ -2970,13 +2970,12 @@
 ()) 
-!= -1
+%3E= 0
 ) by

EOF could be any negative number. Simplified readFully().