/*
 * $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 count;
    int n = 0;
    while (n < len) {
      count = in.read(bytes, off + n, len - n);
      if (count < 0) break; 
      n += count;
    }

    return n;
  }

  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()) >= 0) 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) & 0xFF;
      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);
  }
}

@@ -3446,24 +3446,44 @@
 witch (i) %7B%0A
+      // digits 0-9%0A
       case 0
@@ -3665,16 +3665,183 @@
 - 0x30;%0A
+      // digits A-F%0A      case 0x41:%0A      case 0x42:%0A      case 0x43:%0A      case 0x44:%0A      case 0x45:%0A      case 0x46:%0A        return i - 0x37;%0A      // digits a-f%0A
       ca

Accept input which has upper-case hex digits.