First cut of runtime binary patching. Needs some work to actually test it in a real scenario..
This commit is contained in:
parent
83ed6fa32c
commit
4802d04bfd
22 changed files with 1752 additions and 18 deletions
|
@ -9,6 +9,12 @@ included based on guidelines at
|
||||||
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
|
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.
|
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 ===
|
=== MCP Data ===
|
||||||
This software includes data from the Minecraft Coder Pack (MCP), with kind permission
|
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
|
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.
|
redistributable without permission from the MCP team.
|
||||||
|
|
||||||
=== Sharing ===
|
=== 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)
|
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)
|
under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.security.cert.TrustAnchor;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
@ -41,7 +42,9 @@ import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
import cpw.mods.fml.common.CertificateHelper;
|
import cpw.mods.fml.common.CertificateHelper;
|
||||||
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
|
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.FMLRelaunchLog;
|
||||||
|
import cpw.mods.fml.relauncher.FMLRelauncher;
|
||||||
import cpw.mods.fml.relauncher.IFMLCallHook;
|
import cpw.mods.fml.relauncher.IFMLCallHook;
|
||||||
import cpw.mods.fml.relauncher.RelaunchClassLoader;
|
import cpw.mods.fml.relauncher.RelaunchClassLoader;
|
||||||
|
|
||||||
|
@ -138,7 +141,11 @@ public class FMLSanityChecker implements IFMLCallHook
|
||||||
public void injectData(Map<String, Object> data)
|
public void injectData(Map<String, Object> data)
|
||||||
{
|
{
|
||||||
cl = (RelaunchClassLoader) data.get("classLoader");
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -73,6 +73,57 @@ public class FMLDeobfuscatingRemapper extends Remapper {
|
||||||
mcpNameBiMap=ImmutableBiMap.of();
|
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)
|
public void setup(File mcDir, RelaunchClassLoader classLoader, String deobfFileName)
|
||||||
{
|
{
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
|
|
17
fml/common/cpw/mods/fml/common/patcher/ClassPatch.java
Normal file
17
fml/common/cpw/mods/fml/common/patcher/ClassPatch.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
113
fml/common/cpw/mods/fml/common/patcher/ClassPatchManager.java
Normal file
113
fml/common/cpw/mods/fml/common/patcher/ClassPatchManager.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package cpw.mods.fml.common.patcher;
|
||||||
|
|
||||||
|
public class ClassPatchingTransformer {
|
||||||
|
|
||||||
|
}
|
78
fml/common/cpw/mods/fml/common/patcher/GenDiffSet.java
Normal file
78
fml/common/cpw/mods/fml/common/patcher/GenDiffSet.java
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ public class FMLCorePlugin implements IFMLLoadingPlugin
|
||||||
public String[] getASMTransformerClass()
|
public String[] getASMTransformerClass()
|
||||||
{
|
{
|
||||||
return new String[] {
|
return new String[] {
|
||||||
|
"cpw.mods.fml.common.asm.transformers.PatchingTransformer",
|
||||||
"cpw.mods.fml.common.asm.transformers.AccessTransformer",
|
"cpw.mods.fml.common.asm.transformers.AccessTransformer",
|
||||||
"cpw.mods.fml.common.asm.transformers.MarkerTransformer",
|
"cpw.mods.fml.common.asm.transformers.MarkerTransformer",
|
||||||
"cpw.mods.fml.common.asm.transformers.SideTransformer",
|
"cpw.mods.fml.common.asm.transformers.SideTransformer",
|
||||||
|
|
|
@ -92,6 +92,8 @@ public class RelaunchClassLoader extends URLClassLoader
|
||||||
addTransformerExclusion("com.google.common.");
|
addTransformerExclusion("com.google.common.");
|
||||||
addTransformerExclusion("org.bouncycastle.");
|
addTransformerExclusion("org.bouncycastle.");
|
||||||
addTransformerExclusion("cpw.mods.fml.common.asm.transformers.deobf.");
|
addTransformerExclusion("cpw.mods.fml.common.asm.transformers.deobf.");
|
||||||
|
addTransformerExclusion("cpw.mods.fml.common.patcher.");
|
||||||
|
addTransformerExclusion("cpw.mods.fml.repackage.");
|
||||||
|
|
||||||
if (DEBUG_CLASSLOADING_SAVE)
|
if (DEBUG_CLASSLOADING_SAVE)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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() +
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
446
fml/common/cpw/mods/fml/repackage/com/nothome/delta/Delta.java
Normal file
446
fml/common/cpw/mods/fml/repackage/com/nothome/delta/Delta.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -7,10 +7,10 @@ minecraft servers, minecraft bukkit servers, and minecraft clients.
|
||||||
|
|
||||||
The code is authored by cpw.
|
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-
|
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/
|
http://www.minecraftforge.net/
|
||||||
|
|
||||||
Additionally, it contains an implementation of topological sort based on that
|
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
|
It also contains code from the Maven project for performing versioned dependency
|
||||||
resolution. http://maven.apache.org/
|
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
|
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.
|
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,8 @@ contributors through github.
|
||||||
|
|
||||||
Notable integrations
|
Notable integrations
|
||||||
====================
|
====================
|
||||||
Optifine, with the Cx series have started performing the actions necessary for
|
Optifine has FML compatibility. It varies from Optifine release to release, however
|
||||||
FML compatibility. This means optifine will work well alongside an FML or
|
in general it will work well alongside an FML or
|
||||||
Minecraft Forge installation. FML will detect and ensure the good operation of
|
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
|
Optifine (you can see it in your client as an additional data line on the
|
||||||
bottom left).
|
bottom left).
|
||||||
|
@ -49,9 +49,8 @@ bottom left).
|
||||||
Client notes
|
Client notes
|
||||||
============
|
============
|
||||||
FML does provide a standard pattern for mods to provide HD textures. This is why
|
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
|
optifine needed integration code. FML supports HD texture packs
|
||||||
up to 128x resolution (some 256x resolution packs work but not all). For full
|
up to 128x resolution.
|
||||||
featured HD optifine is recommended.
|
|
||||||
|
|
||||||
Mod information
|
Mod information
|
||||||
===============
|
===============
|
||||||
|
@ -68,15 +67,13 @@ or server):
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
To install this on it's own into a minecraft environment, simply copy the
|
To install on a server, simply execute the FML or Forge jar file, with a copy of
|
||||||
contents of the fml zip file into the minecraft jar file, using your preferred
|
minecraft_server.jar placed in the same directory. FML will launch it's patched
|
||||||
zip management tool (I recommend 7 zip on windows).
|
copy.
|
||||||
|
|
||||||
For servers: the minecraft jar file is minecraft_server.jar.
|
To install on a client, FIRST delete META-INF from the minecraft.jar file, then
|
||||||
For clients: the minecraft jar file is minecraft.jar. You will additionally need
|
copy the contents of the FML or Forge jar file into it. FML and Forge provide
|
||||||
to delete the "META-INF" folder in the minecraft.jar file.
|
their own META-INF data which should *not* be deleted.
|
||||||
For bukkit: the mcportcentral custom builds of craftbukkit contain all you need
|
|
||||||
already. Please refer to mcportcentral.co.za for more information.
|
|
||||||
|
|
||||||
Forge Installation
|
Forge Installation
|
||||||
==================
|
==================
|
||||||
|
|
Loading…
Reference in a new issue