First cut of runtime binary patching. Needs some work to actually test it in a real scenario..

This commit is contained in:
Christian 2013-06-12 08:53:10 -04:00
parent 83ed6fa32c
commit 4802d04bfd
22 changed files with 1752 additions and 18 deletions

View File

@ -9,6 +9,12 @@ included based on guidelines at
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
with notices intact. The only change is a non-functional change of package name.
This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
authored by genman, heikok, pivot.
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
=== MCP Data ===
This software includes data from the Minecraft Coder Pack (MCP), with kind permission
from them. The license to MCP data is not transitive - distribution of this data by
@ -16,6 +22,14 @@ third parties requires independent licensing from the MCP team. This data is not
redistributable without permission from the MCP team.
=== Sharing ===
I grant permission for some parts of FML to be redistributed outside the terms of the LGPL, for the benefit of
the minecraft modding community. All contributions to these parts should be licensed under the same additional grant.
-- Runtime patcher --
License is granted to redistribute the runtime patcher code (common/cpw/mods/fml/patcher and subdirectories) under
any alternative open source license as classified by the OSI (http://opensource.org/licenses)
-- ASM transformers --
License is granted to redistribute the ASM transformer code (common/cpw/mods/fml/common/asm/ and subdirectories)
under any alternative open source license as classified by the OSI (http://opensource.org/licenses)

View File

@ -30,6 +30,7 @@ import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import javax.swing.JOptionPane;
@ -41,7 +42,9 @@ import org.objectweb.asm.Opcodes;
import cpw.mods.fml.common.CertificateHelper;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import cpw.mods.fml.common.patcher.ClassPatchManager;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.FMLRelauncher;
import cpw.mods.fml.relauncher.IFMLCallHook;
import cpw.mods.fml.relauncher.RelaunchClassLoader;
@ -138,7 +141,11 @@ public class FMLSanityChecker implements IFMLCallHook
public void injectData(Map<String, Object> data)
{
cl = (RelaunchClassLoader) data.get("classLoader");
FMLDeobfuscatingRemapper.INSTANCE.setup((File)data.get("mcLocation"), cl, (String) data.get("deobfuscationFileName"));
File mcDir = (File)data.get("mcLocation");
FMLDeobfuscatingRemapper.INSTANCE.setup(mcDir, cl, (String) data.get("deobfuscationFileName"));
File binpatches = new File(mcDir,"binpatch");
File side = new File(binpatches,FMLRelauncher.side().toLowerCase(Locale.ENGLISH));
ClassPatchManager.INSTANCE.setup(side);
}
}

View File

@ -0,0 +1,13 @@
package cpw.mods.fml.common.asm.transformers;
import cpw.mods.fml.common.patcher.ClassPatchManager;
import cpw.mods.fml.relauncher.IClassTransformer;
public class PatchingTransformer implements IClassTransformer {
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
return ClassPatchManager.INSTANCE.applyPatch(name, transformedName, bytes);
}
}

View File

@ -73,6 +73,57 @@ public class FMLDeobfuscatingRemapper extends Remapper {
mcpNameBiMap=ImmutableBiMap.of();
}
public void setupLoadOnly(String deobfFileName, boolean loadAll)
{
try
{
File mapData = new File(deobfFileName);
mapData = mapData.getCanonicalFile();
ZipFile mapZip = new ZipFile(mapData);
ZipEntry classData = mapZip.getEntry("joined.srg");
ZipInputSupplier zis = new ZipInputSupplier(mapZip, classData);
InputSupplier<InputStreamReader> srgSupplier = CharStreams.newReaderSupplier(zis,Charsets.UTF_8);
List<String> srgList = CharStreams.readLines(srgSupplier);
rawMethodMaps = Maps.newHashMap();
rawFieldMaps = Maps.newHashMap();
Builder<String, String> builder = ImmutableBiMap.<String,String>builder();
Builder<String, String> mcpBuilder = ImmutableBiMap.<String,String>builder();
Splitter splitter = Splitter.on(CharMatcher.anyOf(": ")).omitEmptyStrings().trimResults();
for (String line : srgList)
{
String[] parts = Iterables.toArray(splitter.split(line),String.class);
String typ = parts[0];
if ("CL".equals(typ))
{
parseClass(builder, parts);
parseMCPClass(mcpBuilder,parts);
}
else if ("MD".equals(typ) && loadAll)
{
parseMethod(parts);
}
else if ("FD".equals(typ) && loadAll)
{
parseField(parts);
}
}
classNameBiMap = builder.build();
// Special case some mappings for modloader mods
mcpBuilder.put("BaseMod","net/minecraft/src/BaseMod");
mcpBuilder.put("ModLoader","net/minecraft/src/ModLoader");
mcpBuilder.put("EntityRendererProxy","net/minecraft/src/EntityRendererProxy");
mcpBuilder.put("MLProp","net/minecraft/src/MLProp");
mcpBuilder.put("TradeEntry","net/minecraft/src/TradeEntry");
mcpNameBiMap = mcpBuilder.build();
}
catch (IOException ioe)
{
FMLRelaunchLog.log(Level.SEVERE, ioe, "An error occurred loading the deobfuscation map data");
}
methodNameMaps = Maps.newHashMapWithExpectedSize(rawMethodMaps.size());
fieldNameMaps = Maps.newHashMapWithExpectedSize(rawFieldMaps.size());
}
public void setup(File mcDir, RelaunchClassLoader classLoader, String deobfFileName)
{
this.classLoader = classLoader;

View File

@ -0,0 +1,17 @@
package cpw.mods.fml.common.patcher;
public class ClassPatch {
public final String name;
public final String sourceClassName;
public final String targetClassName;
public final boolean existsAtTarget;
public final byte[] patch;
public ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, byte[] patch)
{
this.name = name;
this.sourceClassName = sourceClassName;
this.targetClassName = targetClassName;
this.existsAtTarget = existsAtTarget;
this.patch = patch;
}
}

View File

@ -0,0 +1,113 @@
package cpw.mods.fml.common.patcher;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.repackage.com.nothome.delta.GDiffPatcher;
public class ClassPatchManager {
public static final ClassPatchManager INSTANCE = new ClassPatchManager();
private GDiffPatcher patcher = new GDiffPatcher();
private ListMultimap<String, ClassPatch> patches;
private ClassPatchManager()
{
}
public byte[] applyPatch(String name, String mappedName, byte[] inputData)
{
if (patches == null)
{
return inputData;
}
List<ClassPatch> list = patches.get(name);
if (list.isEmpty())
{
return inputData;
}
for (ClassPatch patch: list)
{
if (!patch.targetClassName.equals(mappedName))
{
FMLLog.warning("Binary patch found %s for wrong class %s", patch.targetClassName, mappedName);
}
if (!patch.existsAtTarget && (inputData == null || inputData.length == 0))
{
inputData = new byte[0];
}
else if (!patch.existsAtTarget)
{
FMLLog.warning("Patcher expecting empty class data file for %s, but received non-empty", patch.targetClassName);
}
synchronized (patcher)
{
try
{
inputData = patcher.patch(inputData, patch.patch);
}
catch (IOException e)
{
FMLLog.log(Level.SEVERE, e, "Encountered problem runtime patching class %s", name);
continue;
}
}
}
FMLLog.fine("Successfully applied runtime patches for %s", mappedName);
return inputData;
}
public void setup(File dirToScan)
{
File[] patchFiles = dirToScan.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
return Files.getFileExtension(new File(dir,name).getPath()).equals("binpatch");
}
});
if (patchFiles == null)
{
return;
}
patches = ArrayListMultimap.create();
for (File patch : patchFiles)
{
FMLLog.finest("Reading patch data from %s", patch.getAbsolutePath());
ByteArrayDataInput input;
try
{
input = ByteStreams.newDataInput(Files.toByteArray(patch));
}
catch (IOException e)
{
FMLLog.log(Level.WARNING, e, "Unable to read binpatch file %s - ignoring", patch.getAbsolutePath());
continue;
}
String name = input.readUTF();
String sourceClassName = input.readUTF();
String targetClassName = input.readUTF();
boolean exists = input.readBoolean();
int patchLength = input.readInt();
byte[] patchBytes = new byte[patchLength];
input.readFully(patchBytes);
ClassPatch cp = new ClassPatch(name, sourceClassName, targetClassName, exists, patchBytes);
patches.put(name, cp);
}
FMLLog.fine("Read %d binary patches from %s", patches.size(), dirToScan.getAbsolutePath());
}
}

View File

@ -0,0 +1,5 @@
package cpw.mods.fml.common.patcher;
public class ClassPatchingTransformer {
}

View File

@ -0,0 +1,78 @@
package cpw.mods.fml.common.patcher;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import org.omg.CORBA.REBIND;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import cpw.mods.fml.repackage.com.nothome.delta.Delta;
public class GenDiffSet {
public static void main(String[] args) throws IOException
{
String vanillaMinecraftJar = args[0];
String targetJar = args[1];
String reobfuscationOutputPath = args[2];
String deobfFileName = args[3];
String binPatchOutputDir = args[4];
Delta delta = new Delta();
FMLDeobfuscatingRemapper remapper = FMLDeobfuscatingRemapper.INSTANCE;
remapper.setupLoadOnly(deobfFileName, false);
JarFile originalJarFile = new JarFile(vanillaMinecraftJar);
JarFile targetJarFile = new JarFile(targetJar);
File f = new File(binPatchOutputDir);
f.mkdirs();
for (JarEntry e : Collections.list(originalJarFile.entries()))
{
String name = e.getName();
// Logger.getLogger("GENDIFF").info(String.format("Evaluating path for data :%s",name));
File reobfOutput = new File(reobfuscationOutputPath, name);
if (reobfOutput.exists())
{
String sourceClassName = name.substring(0, name.lastIndexOf(".")).replace('/', '.');
String targetClassName = remapper.map(name.substring(0,name.lastIndexOf("."))).replace('/', '.');
JarEntry entry = targetJarFile.getJarEntry(name);
byte[] vanillaBytes = entry != null ? ByteStreams.toByteArray(targetJarFile.getInputStream(entry)) : new byte[0];
byte[] patchedBytes = Files.toByteArray(reobfOutput);
byte[] diff = delta.compute(vanillaBytes, patchedBytes);
ByteArrayDataOutput diffOut = ByteStreams.newDataOutput(diff.length + 50);
// Original name
diffOut.writeUTF(name);
// Source name
diffOut.writeUTF(sourceClassName);
// Target name
diffOut.writeUTF(targetClassName);
// exists at original
diffOut.writeBoolean(entry!=null);
// length of patch
diffOut.writeInt(diff.length);
// patch
diffOut.write(diff);
File target = new File(binPatchOutputDir, targetClassName+".binpatch");
target.getParentFile().mkdirs();
Files.write(diffOut.toByteArray(), target);
Logger.getLogger("GENDIFF").info(String.format("Wrote patch for %s (%s) at %s",name, targetClassName, target.getAbsolutePath()));
}
}
}
}

View File

@ -5,7 +5,7 @@
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
*
* Contributors:
* cpw - implementation
*/
@ -26,6 +26,7 @@ public class FMLCorePlugin implements IFMLLoadingPlugin
public String[] getASMTransformerClass()
{
return new String[] {
"cpw.mods.fml.common.asm.transformers.PatchingTransformer",
"cpw.mods.fml.common.asm.transformers.AccessTransformer",
"cpw.mods.fml.common.asm.transformers.MarkerTransformer",
"cpw.mods.fml.common.asm.transformers.SideTransformer",

View File

@ -92,6 +92,8 @@ public class RelaunchClassLoader extends URLClassLoader
addTransformerExclusion("com.google.common.");
addTransformerExclusion("org.bouncycastle.");
addTransformerExclusion("cpw.mods.fml.common.asm.transformers.deobf.");
addTransformerExclusion("cpw.mods.fml.common.patcher.");
addTransformerExclusion("cpw.mods.fml.repackage.");
if (DEBUG_CLASSLOADING_SAVE)
{
@ -101,7 +103,7 @@ public class RelaunchClassLoader extends URLClassLoader
{
temp_folder = new File(FMLRelaunchLog.minecraftHome, "CLASSLOADER_TEMP" + x++);
}
if (temp_folder.exists())
{
FMLRelaunchLog.info("DEBUG_CLASSLOADING_SAVE enabled, but 10 temp directories already exist, clean them and try again.");

View File

@ -0,0 +1,97 @@
/*
* ByteArraySeekableSource.java
*
* Created on May 17, 2006, 12:41 PM
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Wraps a byte buffer as a source
*/
public class ByteBufferSeekableSource implements SeekableSource {
private ByteBuffer bb;
private ByteBuffer cur;
/**
* Constructs a new ByteArraySeekableSource.
*/
public ByteBufferSeekableSource(byte[] source) {
this(ByteBuffer.wrap(source));
}
/**
* Constructs a new ByteArraySeekableSource.
*/
public ByteBufferSeekableSource(ByteBuffer bb) {
if (bb == null)
throw new NullPointerException("bb");
this.bb = bb;
bb.rewind();
try {
seek(0);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void seek(long pos) throws IOException {
cur = bb.slice();
if (pos > cur.limit())
throw new IOException("pos " + pos + " cannot seek " + cur.limit());
cur.position((int) pos);
}
public int read(ByteBuffer dest) throws IOException {
if (!cur.hasRemaining())
return -1;
int c = 0;
while (cur.hasRemaining() && dest.hasRemaining()) {
dest.put(cur.get());
c++;
}
return c;
}
public void close() throws IOException {
bb = null;
cur = null;
}
/**
* Returns a debug <code>String</code>.
*/
@Override
public String toString()
{
return "BBSeekable" +
" bb=" + this.bb.position() + "-" + bb.limit() +
" cur=" + this.cur.position() + "-" + cur.limit() +
"";
}
}

View File

@ -0,0 +1,162 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
* Copyright (c) 2002 Nicolas PERIDONT
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
import com.google.common.collect.Maps;
/**
* Checksum computation class.
*/
public class Checksum {
static final boolean debug = false;
private Map<Long,Integer> checksums = Maps.newHashMap();
private static final char single_hash[] = {
/* Random numbers generated using SLIB's pseudo-random number generator. */
0xbcd1, 0xbb65, 0x42c2, 0xdffe, 0x9666, 0x431b, 0x8504, 0xeb46,
0x6379, 0xd460, 0xcf14, 0x53cf, 0xdb51, 0xdb08, 0x12c8, 0xf602,
0xe766, 0x2394, 0x250d, 0xdcbb, 0xa678, 0x02af, 0xa5c6, 0x7ea6,
0xb645, 0xcb4d, 0xc44b, 0xe5dc, 0x9fe6, 0x5b5c, 0x35f5, 0x701a,
0x220f, 0x6c38, 0x1a56, 0x4ca3, 0xffc6, 0xb152, 0x8d61, 0x7a58,
0x9025, 0x8b3d, 0xbf0f, 0x95a3, 0xe5f4, 0xc127, 0x3bed, 0x320b,
0xb7f3, 0x6054, 0x333c, 0xd383, 0x8154, 0x5242, 0x4e0d, 0x0a94,
0x7028, 0x8689, 0x3a22, 0x0980, 0x1847, 0xb0f1, 0x9b5c, 0x4176,
0xb858, 0xd542, 0x1f6c, 0x2497, 0x6a5a, 0x9fa9, 0x8c5a, 0x7743,
0xa8a9, 0x9a02, 0x4918, 0x438c, 0xc388, 0x9e2b, 0x4cad, 0x01b6,
0xab19, 0xf777, 0x365f, 0x1eb2, 0x091e, 0x7bf8, 0x7a8e, 0x5227,
0xeab1, 0x2074, 0x4523, 0xe781, 0x01a3, 0x163d, 0x3b2e, 0x287d,
0x5e7f, 0xa063, 0xb134, 0x8fae, 0x5e8e, 0xb7b7, 0x4548, 0x1f5a,
0xfa56, 0x7a24, 0x900f, 0x42dc, 0xcc69, 0x02a0, 0x0b22, 0xdb31,
0x71fe, 0x0c7d, 0x1732, 0x1159, 0xcb09, 0xe1d2, 0x1351, 0x52e9,
0xf536, 0x5a4f, 0xc316, 0x6bf9, 0x8994, 0xb774, 0x5f3e, 0xf6d6,
0x3a61, 0xf82c, 0xcc22, 0x9d06, 0x299c, 0x09e5, 0x1eec, 0x514f,
0x8d53, 0xa650, 0x5c6e, 0xc577, 0x7958, 0x71ac, 0x8916, 0x9b4f,
0x2c09, 0x5211, 0xf6d8, 0xcaaa, 0xf7ef, 0x287f, 0x7a94, 0xab49,
0xfa2c, 0x7222, 0xe457, 0xd71a, 0x00c3, 0x1a76, 0xe98c, 0xc037,
0x8208, 0x5c2d, 0xdfda, 0xe5f5, 0x0b45, 0x15ce, 0x8a7e, 0xfcad,
0xaa2d, 0x4b5c, 0xd42e, 0xb251, 0x907e, 0x9a47, 0xc9a6, 0xd93f,
0x085e, 0x35ce, 0xa153, 0x7e7b, 0x9f0b, 0x25aa, 0x5d9f, 0xc04d,
0x8a0e, 0x2875, 0x4a1c, 0x295f, 0x1393, 0xf760, 0x9178, 0x0f5b,
0xfa7d, 0x83b4, 0x2082, 0x721d, 0x6462, 0x0368, 0x67e2, 0x8624,
0x194d, 0x22f6, 0x78fb, 0x6791, 0xb238, 0xb332, 0x7276, 0xf272,
0x47ec, 0x4504, 0xa961, 0x9fc8, 0x3fdc, 0xb413, 0x007a, 0x0806,
0x7458, 0x95c6, 0xccaa, 0x18d6, 0xe2ae, 0x1b06, 0xf3f6, 0x5050,
0xc8e8, 0xf4ac, 0xc04c, 0xf41c, 0x992f, 0xae44, 0x5f1b, 0x1113,
0x1738, 0xd9a8, 0x19ea, 0x2d33, 0x9698, 0x2fe9, 0x323f, 0xcde2,
0x6d71, 0xe37d, 0xb697, 0x2c4f, 0x4373, 0x9102, 0x075d, 0x8e25,
0x1672, 0xec28, 0x6acb, 0x86cc, 0x186e, 0x9414, 0xd674, 0xd1a5
};
/**
* Initialize checksums for source. The checksum for the <code>chunkSize</code> bytes at offset
* <code>chunkSize</code> * i is inserted into a hash map.
*/
public Checksum(SeekableSource source, int chunkSize) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(chunkSize * 2);
int count = 0;
while (true) {
source.read(bb);
bb.flip();
if (bb.remaining() < chunkSize)
break;
while (bb.remaining() >= chunkSize) {
long queryChecksum = queryChecksum0(bb, chunkSize);
checksums.put(queryChecksum, count++);
}
bb.compact();
}
}
/**
* Finds the checksum computed from the buffer.
* Marks, gets, then resets the buffer.
*/
public static long queryChecksum(ByteBuffer bb, int len) {
bb.mark();
long sum = queryChecksum0(bb, len);
bb.reset();
return sum;
}
private static long queryChecksum0(ByteBuffer bb, int len) {
int high = 0; int low = 0;
for (int i = 0; i < len; i++) {
low += single_hash[bb.get()+128];
high += low;
}
return ((high & 0xffff) << 16) | (low & 0xffff);
}
/**
* Increments a checksum.
* @param checksum initial checksum
* @param out byte leaving view
* @param in byte entering view
* @param chunkSize size of chunks
* @return new checksum
*/
public static long incrementChecksum(long checksum, byte out, byte in, int chunkSize) {
char old_c = single_hash[out+128];
char new_c = single_hash[in+128];
int low = ((int)((checksum) & 0xffff) - old_c + new_c) & 0xffff;
int high = ((int)((checksum) >> 16) - (old_c * chunkSize) + low) & 0xffff;
return (high << 16) | (low & 0xffff);
}
/**
* 256 random hash values.
*/
public static char[] getSingleHash() {
return single_hash;
}
/**
* Finds the index of a checksum.
*/
public int findChecksumIndex(long hashf) {
if (!checksums.containsKey(hashf))
return -1;
return checksums.get(hashf);
}
/**
* Returns a debug <code>String</code>.
*/
@Override
public String toString()
{
return super.toString() +
" checksums=" + this.checksums;
}
}

View File

@ -0,0 +1,75 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* For debugging patch generation.
*/
public class DebugDiffWriter implements DiffWriter {
private ByteArrayOutputStream os = new ByteArrayOutputStream();
/**
* Constructs a new DebugDiffWriter.
*/
public DebugDiffWriter() {}
public void addCopy(long offset, int length) throws IOException {
if (os.size() > 0)
writeBuf();
System.err.println("COPY off: " + offset + ", len: " + length);
}
public void addData(byte b) throws IOException {
os.write(b);
writeBuf();
}
private void writeBuf() {
System.err.print("DATA: ");
byte[] ba = os.toByteArray();
for (int ix = 0; ix < ba.length; ix++) {
if (ba[ix] == '\n')
System.err.print("\\n");
else
System.err.print(String.valueOf((char)((char) ba[ix])));
//System.err.print("0x" + Integer.toHexString(buf[ix]) + " "); // hex output
}
System.err.println("");
os.reset();
}
public void flush() throws IOException {
System.err.println("FLUSH");
}
public void close() throws IOException {
System.err.println("CLOSE");
}
}

View File

@ -0,0 +1,446 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
* Copyright (c) 2002 Nicolas PERIDONT
* Bug Fixes: Daniel Morrione dan@morrione.net
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Change Log:
* iiimmddyyn nnnnn Description
* ---------- ----- -------------------------------------------------------
* gls100603a Fixes from Torgeir Veimo and Dan Morrione
* gls110603a Stream not being closed thus preventing a file from
* being subsequently deleted.
* gls031504a Error being written to stderr rather than throwing exception
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
/**
* Class for computing deltas against a source.
* The source file is read by blocks and a hash is computed per block.
* Then the target is scanned for matching blocks.
* <p/>
* This class is not thread safe. Use one instance per thread.
* <p/>
* This class should support files over 4GB in length, although you must
* use a larger checksum size, such as 1K, as all checksums use "int" indexing.
* Newer versions may eventually support paging in/out of checksums.
*/
public class Delta {
/**
* Debug flag.
*/
final static boolean debug = false;
/**
* Default size of 16.
* For "Lorem ipsum" text files (see the tests) the ideal size is about 14.
* Any smaller and the patch size becomes actually be larger.
* <p>
* Use a size like 64 or 128 for large files.
*/
public static final int DEFAULT_CHUNK_SIZE = 1<<4;
/**
* Chunk Size.
*/
private int S;
private SourceState source;
private TargetState target;
private DiffWriter output;
/**
* Constructs a new Delta.
* In the future, additional constructor arguments will set the algorithm details.
*/
public Delta() {
setChunkSize(DEFAULT_CHUNK_SIZE);
}
/**
* Sets the chunk size used.
* Larger chunks are faster and use less memory, but create larger patches
* as well.
*
* @param size
*/
public void setChunkSize(int size) {
if (size <= 0)
throw new IllegalArgumentException("Invalid size");
S = size;
}
/**
* Compares the source bytes with target bytes, writing to output.
*/
public void compute(byte source[], byte target[], OutputStream output)
throws IOException {
compute(new ByteBufferSeekableSource(source),
new ByteArrayInputStream(target),
new GDiffWriter(output));
}
/**
* Compares the source bytes with target bytes, returning output.
*/
public byte[] compute(byte source[], byte target[])
throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
compute(source, target, os);
return os.toByteArray();
}
/**
* Compares the source bytes with target input, writing to output.
*/
public void compute(byte[] sourceBytes, InputStream inputStream,
DiffWriter diffWriter) throws IOException
{
compute(new ByteBufferSeekableSource(sourceBytes),
inputStream, diffWriter);
}
/**
* Compares the source file with a target file, writing to output.
*
* @param output will be closed
*/
public void compute(File sourceFile, File targetFile, DiffWriter output)
throws IOException {
RandomAccessFileSeekableSource source = new RandomAccessFileSeekableSource(new RandomAccessFile(sourceFile, "r"));
InputStream is = new BufferedInputStream(new FileInputStream(targetFile));
try {
compute(source, is, output);
} finally {
source.close();
is.close();
}
}
/**
* Compares the source with a target, writing to output.
*
* @param output will be closed
*/
public void compute(SeekableSource seekSource, InputStream targetIS, DiffWriter output)
throws IOException {
if (debug) {
debug("using match length S = " + S);
}
source = new SourceState(seekSource);
target = new TargetState(targetIS);
this.output = output;
if (debug)
debug("checksums " + source.checksum);
while (!target.eof()) {
debug("!target.eof()");
int index = target.find(source);
if (index != -1) {
if (debug)
debug("found hash " + index);
long offset = (long)index * S;
source.seek(offset);
int match = target.longestMatch(source);
if (match >= S) {
if (debug)
debug("output.addCopy("+offset+","+match+")");
output.addCopy(offset, match);
} else {
// move the position back according to how much we can't copy
target.tbuf.position(target.tbuf.position() - match);
addData();
}
} else {
addData();
}
}
output.close();
}
private void addData() throws IOException {
int i = target.read();
if (debug)
debug("addData " + Integer.toHexString(i));
if (i == -1)
return;
output.addData((byte)i);
}
class SourceState {
private Checksum checksum;
private SeekableSource source;
public SourceState(SeekableSource source) throws IOException {
checksum = new Checksum(source, S);
this.source = source;
source.seek(0);
}
public void seek(long index) throws IOException {
source.seek(index);
}
/**
* Returns a debug <code>String</code>.
*/
@Override
public String toString()
{
return "Source"+
" checksum=" + this.checksum +
" source=" + this.source +
"";
}
}
class TargetState {
private ReadableByteChannel c;
private ByteBuffer tbuf = ByteBuffer.allocate(blocksize());
private ByteBuffer sbuf = ByteBuffer.allocate(blocksize());
private long hash;
private boolean hashReset = true;
private boolean eof;
TargetState(InputStream targetIS) throws IOException {
c = Channels.newChannel(targetIS);
tbuf.limit(0);
}
private int blocksize() {
return Math.min(1024 * 16, S * 4);
}
/**
* Returns the index of the next N bytes of the stream.
*/
public int find(SourceState source) throws IOException {
if (eof)
return -1;
sbuf.clear();
sbuf.limit(0);
if (hashReset) {
debug("hashReset");
while (tbuf.remaining() < S) {
tbuf.compact();
int read = c.read(tbuf);
tbuf.flip();
if (read == -1) {
debug("target ending");
return -1;
}
}
hash = Checksum.queryChecksum(tbuf, S);
hashReset = false;
}
if (debug)
debug("hash " + hash + " " + dump());
return source.checksum.findChecksumIndex(hash);
}
public boolean eof() {
return eof;
}
/**
* Reads a byte.
* @throws IOException
*/
public int read() throws IOException {
if (tbuf.remaining() <= S) {
readMore();
if (!tbuf.hasRemaining()) {
eof = true;
return -1;
}
}
byte b = tbuf.get();
if (tbuf.remaining() >= S) {
byte nchar = tbuf.get( tbuf.position() + S -1 );
hash = Checksum.incrementChecksum(hash, b, nchar, S);
} else {
debug("out of char");
}
return b & 0xFF;
}
/**
* Returns the longest match length at the source location.
*/
public int longestMatch(SourceState source) throws IOException {
debug("longestMatch");
int match = 0;
hashReset = true;
while (true) {
if (!sbuf.hasRemaining()) {
sbuf.clear();
int read = source.source.read(sbuf);
sbuf.flip();
if (read == -1)
return match;
}
if (!tbuf.hasRemaining()) {
readMore();
if (!tbuf.hasRemaining()) {
debug("target ending");
eof = true;
return match;
}
}
if (sbuf.get() != tbuf.get()) {
tbuf.position(tbuf.position() - 1);
return match;
}
match++;
}
}
private void readMore() throws IOException {
if (debug)
debug("readMore " + tbuf);
tbuf.compact();
c.read(tbuf);
tbuf.flip();
}
void hash() {
hash = Checksum.queryChecksum(tbuf, S);
}
/**
* Returns a debug <code>String</code>.
*/
@Override
public String toString()
{
return "Target[" +
" targetBuff=" + dump() + // this.tbuf +
" sourceBuff=" + this.sbuf +
" hashf=" + this.hash +
" eof=" + this.eof +
"]";
}
private String dump() { return dump(tbuf); }
private String dump(ByteBuffer bb) {
return getTextDump(bb);
}
private void append(StringBuffer sb, int value) {
char b1 = (char)((value >> 4) & 0x0F);
char b2 = (char)((value) & 0x0F);
sb.append( Character.forDigit(b1, 16) );
sb.append( Character.forDigit(b2, 16) );
}
public String getTextDump(ByteBuffer bb)
{
StringBuffer sb = new StringBuffer(bb.remaining() * 2);
bb.mark();
while (bb.hasRemaining()) {
int val = bb.get();
if (val > 32 && val < 127)
sb.append(" ").append((char)val);
else
append(sb, val);
}
bb.reset();
return sb.toString();
}
}
/**
* Creates a patch using file names.
*/
public static void main(String argv[]) throws Exception {
if (argv.length != 3) {
System.err.println("usage Delta [-d] source target [output]");
System.err.println("either -d or an output filename must be specified.");
System.err.println("aborting..");
return;
}
DiffWriter output = null;
File sourceFile = null;
File targetFile = null;
if (argv[0].equals("-d")) {
sourceFile = new File(argv[1]);
targetFile = new File(argv[2]);
output = new DebugDiffWriter();
} else {
sourceFile = new File(argv[0]);
targetFile = new File(argv[1]);
output =
new GDiffWriter(
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File(argv[2])))));
}
if (sourceFile.length() > Integer.MAX_VALUE
|| targetFile.length() > Integer.MAX_VALUE) {
System.err.println(
"source or target is too large, max length is "
+ Integer.MAX_VALUE);
System.err.println("aborting..");
return;
}
Delta d = new Delta();
d.compute(sourceFile, targetFile, output);
output.flush();
output.close();
if (debug) //gls031504a
System.out.println("finished generating delta");
}
private void debug(String s) {
if (debug)
System.err.println(s);
}
}

View File

@ -0,0 +1,57 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.Closeable;
import java.io.IOException;
/**
* Interface for DIFF writers.
*/
public interface DiffWriter extends Closeable {
/**
* Add a GDIFF copy instruction.
*/
public void addCopy(long offset, int length) throws IOException;
/**
* Add a GDIFF data instruction.
* Implementors should buffer the data.
*/
public void addData(byte b) throws IOException;
/**
* Flushes to output, e.g. any data added.
*/
public void flush() throws IOException;
/**
* Closes this stream.
* Note that {@link Diff} will invoke this method at the end.
*/
public void close() throws IOException;
}

View File

@ -0,0 +1,245 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_INT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_UBYTE;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_INT_USHORT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_LONG_INT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_INT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_UBYTE;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.COPY_USHORT_USHORT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.DATA_INT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.DATA_MAX;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.DATA_USHORT;
import static cpw.mods.fml.repackage.com.nothome.delta.GDiffWriter.EOF;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
/**
* This class patches an input file with a GDIFF patch fil<EFBFBD>e.
*
* The patch file follows the GDIFF file specification available at
* {@link http://www.w3.org/TR/NOTE-gdiff-19970901.html}.
*/
public class GDiffPatcher {
private ByteBuffer buf = ByteBuffer.allocate(1024);
private byte buf2[] = buf.array();
/**
* Constructs a new GDiffPatcher.
*/
public GDiffPatcher() {
}
/**
* Patches to an output file.
*/
public void patch(File sourceFile, File patchFile, File outputFile)
throws IOException
{
RandomAccessFileSeekableSource source =new RandomAccessFileSeekableSource(new RandomAccessFile(sourceFile, "r"));
InputStream patch = new FileInputStream(patchFile);
OutputStream output = new FileOutputStream(outputFile);
try {
patch(source, patch, output);
} catch (IOException e) {
throw e;
} finally {
source.close();
patch.close();
output.close();
}
}
/**
* Patches to an output stream.
*/
public void patch(byte[] source, InputStream patch, OutputStream output) throws IOException {
patch(new ByteBufferSeekableSource(source), patch, output);
}
/**
* Patches in memory, returning the patch result.
*/
public byte[] patch(byte[] source, byte[] patch) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
patch(source, new ByteArrayInputStream(patch), os);
return os.toByteArray();
}
/**
* Patches to an output stream.
*/
public void patch(SeekableSource source, InputStream patch, OutputStream out) throws IOException {
DataOutputStream outOS = new DataOutputStream(out);
DataInputStream patchIS = new DataInputStream(patch);
// the magic string is 'd1 ff d1 ff' + the version number
if (patchIS.readUnsignedByte() != 0xd1 ||
patchIS.readUnsignedByte() != 0xff ||
patchIS.readUnsignedByte() != 0xd1 ||
patchIS.readUnsignedByte() != 0xff ||
patchIS.readUnsignedByte() != 0x04) {
throw new PatchException("magic string not found, aborting!");
}
while (true) {
int command = patchIS.readUnsignedByte();
if (command == EOF)
break;
int length;
int offset;
if (command <= DATA_MAX) {
append(command, patchIS, outOS);
continue;
}
switch (command) {
case DATA_USHORT: // ushort, n bytes following; append
length = patchIS.readUnsignedShort();
append(length, patchIS, outOS);
break;
case DATA_INT: // int, n bytes following; append
length = patchIS.readInt();
append(length, patchIS, outOS);
break;
case COPY_USHORT_UBYTE:
offset = patchIS.readUnsignedShort();
length = patchIS.readUnsignedByte();
copy(offset, length, source, outOS);
break;
case COPY_USHORT_USHORT:
offset = patchIS.readUnsignedShort();
length = patchIS.readUnsignedShort();
copy(offset, length, source, outOS);
break;
case COPY_USHORT_INT:
offset = patchIS.readUnsignedShort();
length = patchIS.readInt();
copy(offset, length, source, outOS);
break;
case COPY_INT_UBYTE:
offset = patchIS.readInt();
length = patchIS.readUnsignedByte();
copy(offset, length, source, outOS);
break;
case COPY_INT_USHORT:
offset = patchIS.readInt();
length = patchIS.readUnsignedShort();
copy(offset, length, source, outOS);
break;
case COPY_INT_INT:
offset = patchIS.readInt();
length = patchIS.readInt();
copy(offset, length, source, outOS);
break;
case COPY_LONG_INT:
long loffset = patchIS.readLong();
length = patchIS.readInt();
copy(loffset, length, source, outOS);
break;
default:
throw new IllegalStateException("command " + command);
}
}
outOS.flush();
}
private void copy(long offset, int length, SeekableSource source, OutputStream output)
throws IOException
{
source.seek(offset);
while (length > 0) {
int len = Math.min(buf.capacity(), length);
buf.clear().limit(len);
int res = source.read(buf);
if (res == -1)
throw new EOFException("in copy " + offset + " " + length);
output.write(buf.array(), 0, res);
length -= res;
}
}
private void append(int length, InputStream patch, OutputStream output) throws IOException {
while (length > 0) {
int len = Math.min(buf2.length, length);
int res = patch.read(buf2, 0, len);
if (res == -1)
throw new EOFException("cannot read " + length);
output.write(buf2, 0, res);
length -= res;
}
}
/**
* Simple command line tool to patch a file.
*/
public static void main(String argv[]) {
if (argv.length != 3) {
System.err.println("usage GDiffPatch source patch output");
System.err.println("aborting..");
return;
}
try {
File sourceFile = new File(argv[0]);
File patchFile = new File(argv[1]);
File outputFile = new File(argv[2]);
if (sourceFile.length() > Integer.MAX_VALUE ||
patchFile.length() > Integer.MAX_VALUE) {
System.err.println("source or patch is too large, max length is " + Integer.MAX_VALUE);
System.err.println("aborting..");
return;
}
GDiffPatcher patcher = new GDiffPatcher();
patcher.patch(sourceFile, patchFile, outputFile);
System.out.println("finished patching file");
} catch (Exception ioe) { //gls031504a
System.err.println("error while patching: " + ioe);
}
}
}

View File

@ -0,0 +1,174 @@
/*
*
* Copyright (c) 2001 Torgeir Veimo
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Outputs a diff following the GDIFF file specification available at
* http://www.w3.org/TR/NOTE-gdiff-19970901.html.
*/
public class GDiffWriter implements DiffWriter {
/**
* Max length of a chunk.
*/
public static final int CHUNK_SIZE = Short.MAX_VALUE;
public static final byte EOF = 0;
/**
* Max length for single length data encode.
*/
public static final int DATA_MAX = 246;
public static final int DATA_USHORT = 247;
public static final int DATA_INT = 248;
public static final int COPY_USHORT_UBYTE = 249;
public static final int COPY_USHORT_USHORT = 250;
public static final int COPY_USHORT_INT = 251;
public static final int COPY_INT_UBYTE = 252;
public static final int COPY_INT_USHORT = 253;
public static final int COPY_INT_INT = 254;
public static final int COPY_LONG_INT = 255;
private ByteArrayOutputStream buf = new ByteArrayOutputStream();
private boolean debug = false;
private DataOutputStream output = null;
/**
* Constructs a new GDiffWriter.
*/
public GDiffWriter(DataOutputStream os) throws IOException {
this.output = os;
// write magic string "d1 ff d1 ff 04"
output.writeByte(0xd1);
output.writeByte(0xff);
output.writeByte(0xd1);
output.writeByte(0xff);
output.writeByte(0x04);
}
/**
* Constructs a new GDiffWriter.
*/
public GDiffWriter(OutputStream output) throws IOException {
this(new DataOutputStream(output));
}
public void addCopy(long offset, int length) throws IOException {
writeBuf();
//output debug data
if (debug)
System.err.println("COPY off: " + offset + ", len: " + length);
// output real data
if (offset > Integer.MAX_VALUE) {
// Actually, we don't support longer files than int.MAX_VALUE at the moment..
output.writeByte(COPY_LONG_INT);
output.writeLong(offset);
output.writeInt(length);
} else if (offset < 65536) {
if (length < 256) {
output.writeByte(COPY_USHORT_UBYTE);
output.writeShort((int)offset);
output.writeByte(length);
} else if (length > 65535) {
output.writeByte(COPY_USHORT_INT);
output.writeShort((int)offset);
output.writeInt(length);
} else {
output.writeByte(COPY_USHORT_USHORT);
output.writeShort((int)offset);
output.writeShort(length);
}
} else {
if (length < 256) {
output.writeByte(COPY_INT_UBYTE);
output.writeInt((int)offset);
output.writeByte(length);
} else if (length > 65535) {
output.writeByte(COPY_INT_INT);
output.writeInt((int)offset);
output.writeInt(length);
} else {
output.writeByte(COPY_INT_USHORT);
output.writeInt((int)offset);
output.writeShort(length);
}
}
}
/**
* Adds a data byte.
*/
public void addData(byte b) throws IOException {
buf.write(b);
if (buf.size() >= CHUNK_SIZE)
writeBuf();
}
private void writeBuf() throws IOException {
if (buf.size() > 0) {
if (buf.size() <= DATA_MAX) {
output.writeByte(buf.size());
} else if (buf.size() <= 65535) {
output.writeByte(DATA_USHORT);
output.writeShort(buf.size());
} else {
output.writeByte(DATA_INT);
output.writeInt(buf.size());
}
buf.writeTo(output);
buf.reset();
}
}
/**
* Flushes accumulated data bytes, if any.
*/
public void flush() throws IOException
{
writeBuf();
output.flush();
}
/**
* Writes the final EOF byte, closes the underlying stream.
*/
public void close() throws IOException {
this.flush();
output.write((byte)EOF);
output.close();
}
}

View File

@ -0,0 +1,51 @@
/*
* PatchException.java
*
* Created on June 6, 2006, 9:34 PM
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.IOException;
/**
* Thrown when a patch is invalid.
*/
public class PatchException extends IOException {
private static final long serialVersionUID = 1;
/**
* Creates a new instance of <code>PatchException</code> without detail message.
*/
public PatchException() {
}
/**
* Constructs an instance of <code>PatchException</code> with the specified detail message.
* @param msg the detail message.
*/
public PatchException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,75 @@
/*
* RandomAccessFileSeekableSource.java
*
* Created on May 17, 2006, 1:45 PM
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
/**
* Wraps a random access file.
*/
public class RandomAccessFileSeekableSource implements SeekableSource {
private RandomAccessFile raf;
/**
* Constructs a new RandomAccessFileSeekableSource.
* @param raf
*/
public RandomAccessFileSeekableSource(RandomAccessFile raf) {
if (raf == null)
throw new NullPointerException("raf");
this.raf = raf;
}
public void seek(long pos) throws IOException {
raf.seek(pos);
}
public int read(byte[] b, int off, int len) throws IOException {
return raf.read(b, off, len);
}
public long length() throws IOException {
return raf.length();
}
public void close() throws IOException {
raf.close();
}
public int read(ByteBuffer bb) throws IOException {
int c = raf.read(bb.array(), bb.position(), bb.remaining());
if (c == -1)
return -1;
bb.position(bb.position() + c);
return c;
}
}

View File

@ -0,0 +1,51 @@
/*
* SeekableSource.java
*
* Created on May 17, 2006, 12:33 PM
* Copyright (c) 2006 Heiko Klein
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
package cpw.mods.fml.repackage.com.nothome.delta;
import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
/**
* For sources of random-access data, such as {@link RandomAccessFile}.
*/
public interface SeekableSource extends Closeable {
/**
* Sets the position for the next {@link #read(ByteBuffer)}.
*/
void seek(long pos) throws IOException ;
/**
* Reads up to {@link ByteBuffer#remaining()} bytes from the source,
* returning the number of bytes read, or -1 if no bytes were read
* and EOF was reached.
*/
int read(ByteBuffer bb) throws IOException;
}

View File

@ -7,10 +7,10 @@ minecraft servers, minecraft bukkit servers, and minecraft clients.
The code is authored by cpw.
It implements API defined by the client side ModLoader, authored by Risugami.
It (partially) implements API defined by the client side ModLoader, authored by Risugami.
http://www.minecraftforum.net/topic/75440-
It also contains suggestions and hints from LexManos, author of MinecraftForge.
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
http://www.minecraftforge.net/
Additionally, it contains an implementation of topological sort based on that
@ -19,6 +19,9 @@ published at http://keithschwarz.com/interesting/code/?dir=topological-sort
It also contains code from the Maven project for performing versioned dependency
resolution. http://maven.apache.org/
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
with credit to it's authors.
Forge Mod Loader downloads components from the Minecraft Coder Pack
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.

View File

@ -40,8 +40,8 @@ contributors through github.
Notable integrations
====================
Optifine, with the Cx series have started performing the actions necessary for
FML compatibility. This means optifine will work well alongside an FML or
Optifine has FML compatibility. It varies from Optifine release to release, however
in general it will work well alongside an FML or
Minecraft Forge installation. FML will detect and ensure the good operation of
Optifine (you can see it in your client as an additional data line on the
bottom left).
@ -49,9 +49,8 @@ bottom left).
Client notes
============
FML does provide a standard pattern for mods to provide HD textures. This is why
optifine needed integration code. FML supports very very basic HD texture packs
up to 128x resolution (some 256x resolution packs work but not all). For full
featured HD optifine is recommended.
optifine needed integration code. FML supports HD texture packs
up to 128x resolution.
Mod information
===============
@ -68,15 +67,13 @@ or server):
Installation
============
To install this on it's own into a minecraft environment, simply copy the
contents of the fml zip file into the minecraft jar file, using your preferred
zip management tool (I recommend 7 zip on windows).
To install on a server, simply execute the FML or Forge jar file, with a copy of
minecraft_server.jar placed in the same directory. FML will launch it's patched
copy.
For servers: the minecraft jar file is minecraft_server.jar.
For clients: the minecraft jar file is minecraft.jar. You will additionally need
to delete the "META-INF" folder in the minecraft.jar file.
For bukkit: the mcportcentral custom builds of craftbukkit contain all you need
already. Please refer to mcportcentral.co.za for more information.
To install on a client, FIRST delete META-INF from the minecraft.jar file, then
copy the contents of the FML or Forge jar file into it. FML and Forge provide
their own META-INF data which should *not* be deleted.
Forge Installation
==================