forked from MinecraftForge/MinecraftForge
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ClassPatchManager.java
237 lines (220 loc) · 9.07 KB
/
ClassPatchManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package cpw.mods.fml.common.patcher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.regex.Pattern;
import org.apache.logging.log4j.Level;
import net.minecraft.launchwrapper.LaunchClassLoader;
import LZMA.LzmaInputStream;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.repackage.com.nothome.delta.GDiffPatcher;
public class ClassPatchManager {
public static final boolean dumpPatched = Boolean.parseBoolean(System.getProperty("fml.dumpPatchedClasses", "false"));
public static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("fml.debugClassPatchManager", "false"));
public static final ClassPatchManager INSTANCE = new ClassPatchManager();
private GDiffPatcher patcher = new GDiffPatcher();
private ListMultimap<String, ClassPatch> patches;
private Map<String,byte[]> patchedClasses = Maps.newHashMap();
private File tempDir;
private static final Pattern prebinpatchMatcher1 = Pattern.compile(String.format("binpatch/%s/.*.binpatch", "client"));
private static final Pattern prebinpatchMatcher2 = Pattern.compile(String.format("binpatch/%s/.*.binpatch", "server"));
private ClassPatchManager()
{
if (dumpPatched)
{
tempDir = Files.createTempDir();
FMLRelaunchLog.info("Dumping patched classes to %s",tempDir.getAbsolutePath());
}
}
public byte[] getPatchedResource(String name, String mappedName, LaunchClassLoader loader) throws IOException
{
byte[] rawClassBytes = loader.getClassBytes(name);
return applyPatch(name, mappedName, rawClassBytes);
}
public byte[] applyPatch(String name, String mappedName, byte[] inputData)
{
if (patches == null)
{
return inputData;
}
if (patchedClasses.containsKey(name))
{
return patchedClasses.get(name);
}
List<ClassPatch> list = patches.get(name);
if (list.isEmpty())
{
return inputData;
}
boolean ignoredError = false;
if (DEBUG)
FMLRelaunchLog.fine("Runtime patching class %s (input size %d), found %d patch%s", mappedName, (inputData == null ? 0 : inputData.length), list.size(), list.size()!=1 ? "es" : "");
for (ClassPatch patch: list)
{
if (!patch.targetClassName.equals(mappedName) && !patch.sourceClassName.equals(name))
{
FMLRelaunchLog.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)
{
FMLRelaunchLog.warning("Patcher expecting empty class data file for %s, but received non-empty", patch.targetClassName);
}
else
{
int inputChecksum = Hashing.adler32().hashBytes(inputData).asInt();
if (patch.inputChecksum != inputChecksum)
{
FMLRelaunchLog.severe("There is a binary discrepency between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", mappedName, name, inputChecksum, patch.inputChecksum);
if (!Boolean.parseBoolean(System.getProperty("fml.ignorePatchDiscrepancies","false")))
{
FMLRelaunchLog.severe("The game is going to exit, because this is a critical error, and it is very improbable that the modded game will work, please obtain clean jar files.");
System.exit(1);
}
else
{
FMLRelaunchLog.severe("FML is going to ignore this error, note that the patch will not be applied, and there is likely to be a malfunctioning behaviour, including not running at all");
ignoredError = true;
continue;
}
}
}
synchronized (patcher)
{
try
{
inputData = patcher.patch(inputData, patch.patch);
}
catch (IOException e)
{
FMLRelaunchLog.log(Level.ERROR, e, "Encountered problem runtime patching class %s", name);
continue;
}
}
}
if (!ignoredError && DEBUG)
{
FMLRelaunchLog.fine("Successfully applied runtime patches for %s (new size %d)", mappedName, inputData.length);
}
if (dumpPatched)
{
try
{
Files.write(inputData, new File(tempDir,mappedName));
}
catch (IOException e)
{
FMLRelaunchLog.log(Level.ERROR, e, "Failed to write %s to %s", mappedName, tempDir.getAbsolutePath());
}
}
patchedClasses.put(name,inputData);
return inputData;
}
public void setup(Side side)
{
Pattern binpatchMatcher;
if (side.isClient()) binpatchMatcher = prebinpatchMatcher1;
else/* if (side.isServer())*/ binpatchMatcher = prebinpatchMatcher2;
JarInputStream jis;
try
{
InputStream binpatchesCompressed = getClass().getResourceAsStream("/binpatches.pack.lzma");
if (binpatchesCompressed==null)
{
FMLRelaunchLog.log(Level.ERROR, "The binary patch set is missing. Either you are in a development environment, or things are not going to work!");
return;
}
LzmaInputStream binpatchesDecompressed = new LzmaInputStream(binpatchesCompressed);
ByteArrayOutputStream jarBytes = new ByteArrayOutputStream();
JarOutputStream jos = new JarOutputStream(jarBytes);
Pack200.newUnpacker().unpack(binpatchesDecompressed, jos);
jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray()));
}
catch (Exception e)
{
FMLRelaunchLog.log(Level.ERROR, e, "Error occurred reading binary patches. Expect severe problems!");
throw Throwables.propagate(e);
}
patches = ArrayListMultimap.create();
do
{
try
{
JarEntry entry = jis.getNextJarEntry();
if (entry == null)
{
break;
}
if (binpatchMatcher.matcher(entry.getName()).matches())
{
ClassPatch cp = readPatch(entry, jis);
if (cp != null)
{
patches.put(cp.sourceClassName, cp);
}
}
else
{
jis.closeEntry();
}
}
catch (IOException e)
{
}
} while (true);
if (DEBUG) {
FMLRelaunchLog.fine("Read %d binary patches", patches.size());
FMLRelaunchLog.fine("Patch list :\n\t%s", Joiner.on("\t\n").join(patches.asMap().entrySet()));}
patchedClasses.clear();
}
private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis)
{
if (DEBUG)
FMLRelaunchLog.finer("Reading patch data from %s", patchEntry.getName());
ByteArrayDataInput input;
try
{
input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis));
}
catch (IOException e)
{
if (DEBUG) FMLRelaunchLog.log(Level.WARN, e, "Unable to read binpatch file %s - ignoring", patchEntry.getName());
return null;
}
String name = input.readUTF();
String sourceClassName = input.readUTF();
String targetClassName = input.readUTF();
boolean exists = input.readBoolean();
int inputChecksum = 0;
if (exists)
{
inputChecksum = input.readInt();
}
int patchLength = input.readInt();
byte[] patchBytes = new byte[patchLength];
input.readFully(patchBytes);
return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes);
}
}