2018-10-12 22:56:09 +00:00
|
|
|
/*
|
|
|
|
* This file is part of Baritone.
|
|
|
|
*
|
|
|
|
* Baritone is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* Baritone is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
|
|
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package baritone.gradle.util;
|
|
|
|
|
2018-10-13 00:47:13 +00:00
|
|
|
import com.google.gson.*;
|
|
|
|
import com.google.gson.stream.JsonReader;
|
|
|
|
import com.google.gson.stream.JsonWriter;
|
|
|
|
|
|
|
|
import java.io.*;
|
2023-06-16 03:59:08 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Comparator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2018-10-12 22:56:09 +00:00
|
|
|
import java.util.jar.JarEntry;
|
|
|
|
import java.util.jar.JarFile;
|
|
|
|
import java.util.jar.JarOutputStream;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
2018-10-22 19:42:05 +00:00
|
|
|
* Make a .jar file deterministic by sorting all entries by name, and setting all the last modified times to 42069.
|
2018-10-12 22:56:09 +00:00
|
|
|
* This makes the build 100% reproducible since the timestamp when you built it no longer affects the final file.
|
|
|
|
*
|
|
|
|
* @author leijurv
|
|
|
|
*/
|
|
|
|
public class Determinizer {
|
|
|
|
|
2023-06-16 03:59:08 +00:00
|
|
|
public static void determinize(String inputPath, String outputPath, List<File> toInclude, boolean doForgeReplacementOfMetaInf) throws IOException {
|
2018-10-12 22:56:09 +00:00
|
|
|
System.out.println("Running Determinizer");
|
2018-10-13 04:19:11 +00:00
|
|
|
System.out.println(" Input path: " + inputPath);
|
|
|
|
System.out.println(" Output path: " + outputPath);
|
2023-06-16 03:59:08 +00:00
|
|
|
System.out.println(" Shade: " + toInclude);
|
2018-10-12 22:56:09 +00:00
|
|
|
|
2018-10-13 04:19:11 +00:00
|
|
|
try (
|
|
|
|
JarFile jarFile = new JarFile(new File(inputPath));
|
|
|
|
JarOutputStream jos = new JarOutputStream(new FileOutputStream(new File(outputPath)))
|
|
|
|
) {
|
2018-10-12 22:56:09 +00:00
|
|
|
|
2018-10-13 04:19:11 +00:00
|
|
|
List<JarEntry> entries = jarFile.stream()
|
|
|
|
.sorted(Comparator.comparing(JarEntry::getName))
|
|
|
|
.collect(Collectors.toList());
|
2018-10-12 22:56:09 +00:00
|
|
|
|
2018-10-13 04:19:11 +00:00
|
|
|
for (JarEntry entry : entries) {
|
|
|
|
if (entry.getName().equals("META-INF/fml_cache_annotation.json")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (entry.getName().equals("META-INF/fml_cache_class_versions.json")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
JarEntry clone = new JarEntry(entry.getName());
|
|
|
|
clone.setTime(42069);
|
|
|
|
jos.putNextEntry(clone);
|
2023-06-17 02:36:58 +00:00
|
|
|
if (entry.getName().endsWith(".json")) {
|
|
|
|
JsonElement json = new JsonParser().parse(new InputStreamReader(jarFile.getInputStream(entry)));
|
|
|
|
jos.write(writeSorted(json).getBytes());
|
2023-06-16 03:59:08 +00:00
|
|
|
} else if (entry.getName().equals("META-INF/MANIFEST.MF") && doForgeReplacementOfMetaInf) { // only replace for forge jar
|
2019-02-05 03:01:01 +00:00
|
|
|
ByteArrayOutputStream cancer = new ByteArrayOutputStream();
|
|
|
|
copy(jarFile.getInputStream(entry), cancer);
|
|
|
|
String manifest = new String(cancer.toByteArray());
|
|
|
|
if (!manifest.contains("baritone.launch.BaritoneTweaker")) {
|
|
|
|
throw new IllegalStateException("unable to replace");
|
|
|
|
}
|
|
|
|
manifest = manifest.replace("baritone.launch.BaritoneTweaker", "org.spongepowered.asm.launch.MixinTweaker");
|
|
|
|
jos.write(manifest.getBytes());
|
2018-10-13 04:19:11 +00:00
|
|
|
} else {
|
|
|
|
copy(jarFile.getInputStream(entry), jos);
|
|
|
|
}
|
2018-10-13 00:47:13 +00:00
|
|
|
}
|
2023-06-16 03:59:08 +00:00
|
|
|
for (File file : toInclude) {
|
|
|
|
try (JarFile mixin = new JarFile(file)) {
|
2019-02-05 03:34:44 +00:00
|
|
|
for (JarEntry entry : mixin.stream().sorted(Comparator.comparing(JarEntry::getName)).collect(Collectors.toList())) {
|
|
|
|
if (entry.getName().startsWith("META-INF") && !entry.getName().startsWith("META-INF/services")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
jos.putNextEntry(entry);
|
|
|
|
copy(mixin.getInputStream(entry), jos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-13 04:19:11 +00:00
|
|
|
jos.finish();
|
2018-10-12 22:56:09 +00:00
|
|
|
}
|
2023-06-16 03:59:08 +00:00
|
|
|
System.out.println("Done with determinizer");
|
2018-10-12 22:56:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static void copy(InputStream is, OutputStream os) throws IOException {
|
|
|
|
byte[] buffer = new byte[1024];
|
|
|
|
int len;
|
|
|
|
while ((len = is.read(buffer)) != -1) {
|
|
|
|
os.write(buffer, 0, len);
|
|
|
|
}
|
|
|
|
}
|
2018-10-13 00:47:13 +00:00
|
|
|
|
2023-06-17 02:36:58 +00:00
|
|
|
private static String writeSorted(JsonElement in) throws IOException {
|
2018-10-13 00:47:13 +00:00
|
|
|
StringWriter writer = new StringWriter();
|
|
|
|
JsonWriter jw = new JsonWriter(writer);
|
2018-10-13 03:13:21 +00:00
|
|
|
ORDERED_JSON_WRITER.write(jw, in);
|
2018-10-13 00:47:13 +00:00
|
|
|
return writer.toString() + "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* All credits go to GSON and its contributors. GSON is licensed under the Apache 2.0 License.
|
|
|
|
* This implementation has been modified to write {@link JsonObject} keys in order.
|
|
|
|
*
|
|
|
|
* @see <a href="https://github.com/google/gson/blob/master/LICENSE">GSON License</a>
|
|
|
|
* @see <a href="https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java#L698">Original Source</a>
|
|
|
|
*/
|
2018-10-13 03:13:21 +00:00
|
|
|
private static final TypeAdapter<JsonElement> ORDERED_JSON_WRITER = new TypeAdapter<JsonElement>() {
|
2018-10-13 00:47:13 +00:00
|
|
|
|
|
|
|
@Override
|
2018-10-13 03:13:21 +00:00
|
|
|
public JsonElement read(JsonReader in) {
|
|
|
|
return null;
|
2018-10-13 00:47:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void write(JsonWriter out, JsonElement value) throws IOException {
|
|
|
|
if (value == null || value.isJsonNull()) {
|
|
|
|
out.nullValue();
|
|
|
|
} else if (value.isJsonPrimitive()) {
|
|
|
|
JsonPrimitive primitive = value.getAsJsonPrimitive();
|
|
|
|
if (primitive.isNumber()) {
|
|
|
|
out.value(primitive.getAsNumber());
|
|
|
|
} else if (primitive.isBoolean()) {
|
|
|
|
out.value(primitive.getAsBoolean());
|
|
|
|
} else {
|
|
|
|
out.value(primitive.getAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (value.isJsonArray()) {
|
|
|
|
out.beginArray();
|
|
|
|
for (JsonElement e : value.getAsJsonArray()) {
|
|
|
|
write(out, e);
|
|
|
|
}
|
|
|
|
out.endArray();
|
|
|
|
|
|
|
|
} else if (value.isJsonObject()) {
|
|
|
|
out.beginObject();
|
|
|
|
|
|
|
|
List<Map.Entry<String, JsonElement>> entries = new ArrayList<>(value.getAsJsonObject().entrySet());
|
|
|
|
entries.sort(Comparator.comparing(Map.Entry::getKey));
|
|
|
|
for (Map.Entry<String, JsonElement> e : entries) {
|
|
|
|
out.name(e.getKey());
|
|
|
|
write(out, e.getValue());
|
|
|
|
}
|
|
|
|
out.endObject();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Couldn't write " + value.getClass());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2018-10-12 22:56:09 +00:00
|
|
|
}
|