Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/patch - start work on future update #47

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/main/java/com/shanebeestudios/mcdeob/McDeob.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static void main(String[] args) {
.withRequiredArg()
.ofType(String.class);
parser.accepts("decompile", "Marks that we should decompile the deobfuscated source");
parser.accepts("debug", "When enabled, will print more verbose errors");
parser.accepts("cachedjar", "The name of a jar file to use instead of downloading")
.withOptionalArg()
.ofType(String.class);

OptionSet options = null;
try {
Expand Down Expand Up @@ -120,9 +124,11 @@ public static void main(String[] args) {
version.setType(type);

boolean decompile = options.has("decompile");
boolean debug = options.has("debug");
String cachedjar = (String) options.valueOf("cachedjar");

Thread processorThread = new Thread(() -> {
Processor processor = new Processor(version, decompile, null);
Processor processor = new Processor(version, cachedjar, null, decompile, debug);
processor.init();
}, "Processor");
processorThread.start();
Expand Down
278 changes: 189 additions & 89 deletions src/main/java/com/shanebeestudios/mcdeob/Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.shanebeestudios.mcdeob.app.App;
import com.shanebeestudios.mcdeob.util.AppLogger;
import com.shanebeestudios.mcdeob.util.Logger;
import com.shanebeestudios.mcdeob.util.TimeStamp;
import com.shanebeestudios.mcdeob.util.TimeSpan;
import com.shanebeestudios.mcdeob.util.Util;
import com.shanebeestudios.mcdeob.version.Version;
import net.md_5.specialsource.Jar;
Expand All @@ -12,6 +12,8 @@
import net.md_5.specialsource.SpecialSource;
import net.md_5.specialsource.provider.JarProvider;
import net.md_5.specialsource.provider.JointProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler;

import java.awt.*;
Expand All @@ -23,14 +25,18 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class Processor {

private final Version version;
private final @NotNull Version version;
private final @Nullable String cachedJarName;
private final Optional<App> appOption;
private final boolean decompile;
private final App app;
private final boolean debug;

private final Path dataFolderPath;
private Path jarPath;
Expand All @@ -41,10 +47,20 @@ public class Processor {
private String mappingsName;
private String mappedJarName;

public Processor(Version version, boolean decompile, App app) {
public Processor(@NotNull Version version, @Nullable String cachedJarName, @Nullable App app, boolean decompile, boolean debug) {
this.version = version;
if (cachedJarName != null) {
if (cachedJarName.endsWith(".jar")) {
this.cachedJarName = cachedJarName;
} else {
this.cachedJarName = cachedJarName + ".jar";
}
} else {
this.cachedJarName = null;
}
this.appOption = Optional.ofNullable(app);
this.decompile = decompile;
this.app = app;
this.debug = debug;
if (Util.isRunningMacOS()) {
// If running on macOS, put the output directory in the user home directory.
// This is due to how macOS APPs work — their '.' directory resolves to one inside the APP itself.
Expand All @@ -62,107 +78,172 @@ public Path getDataFolderPath() {
return this.dataFolderPath;
}

public Version getVersion() {
public @NotNull Version getVersion() {
return this.version;
}

public Path getJarPath() {
return this.jarPath;
}

@SuppressWarnings("CallToPrintStackTrace")
public void init() {
try {
long start = System.currentTimeMillis();

// Prepare version and check if valid
String versionName = this.version.getVersion();
if (!this.version.prepareVersion()) {
if (this.app != null) this.app.fail();
System.out.println("Invalid version: " + versionName);
return;
}
if (this.app != null) {
this.app.toggleControls();
}
TimeSpan timeSpan = TimeSpan.start();

String versionTypeName = this.version.getType().getName();
this.minecraftJarName = String.format("minecraft_%s_%s.jar", versionTypeName, versionName);
this.mappingsName = String.format("mappings_%s_%s.txt", versionTypeName, versionName);
this.mappedJarName = String.format("remapped_%s_%s.jar", versionTypeName, versionName);
// Prepare version and check if valid
String versionName = this.version.getVersion();

downloadJar();
downloadMappings();
remapJar();
if (this.decompile) {
decompileJar();
}
cleanup();

TimeStamp timeStamp = TimeStamp.fromNow(start);
Logger.info("Completed in %s!", timeStamp);
if (this.app != null) {
this.app.updateStatusBox(String.format("Completed in %s!", timeStamp));
this.app.updateButton("Start!");
this.app.toggleControls();
}
} catch (IOException e) {
e.printStackTrace();
// If the version is invalid, fail
if (!this.version.prepareVersion()) {
fail("Invalid version: " + versionName);
return;
}
}

public void downloadJar() throws IOException {
long start = System.currentTimeMillis();
Logger.info("Downloading JAR file from Mojang.");
if (this.app != null) {
this.app.updateStatusBox("Downloading JAR...");
this.app.updateButton("Downloading JAR...", Color.BLUE);
// If the app is available, turn off controls
this.appOption.ifPresent(App::toggleControls);

String versionTypeName = this.version.getType().getName();
this.minecraftJarName = String.format("minecraft_%s_%s.jar", versionTypeName, versionName);
this.mappingsName = String.format("mappings_%s_%s.txt", versionTypeName, versionName);
this.mappedJarName = String.format("remapped_%s_%s.jar", versionTypeName, versionName);

// Attempt to download jar
if (!downloadJar()) {
fail("Failed to retrieve jar.");
return;
}

this.jarPath = this.dataFolderPath.resolve(this.minecraftJarName);
final URL jarURL = new URL(this.version.getJarURL());
final HttpURLConnection connection = (HttpURLConnection) jarURL.openConnection();
final long length = connection.getContentLengthLong();
if (Files.exists(jarPath) && Files.size(jarPath) == length) {
Logger.info("Already have JAR, skipping download.");
} else try (final InputStream inputStream = connection.getInputStream()) {
Files.copy(inputStream, jarPath, REPLACE_EXISTING);
// Attempt to download mappings
if (!downloadMappings()) {
fail("Failed to download mappings.");
return;
}

// Attempt to remap jar
if (!remapJar()) {
fail("Failed to remap jar.");
return;
}

TimeStamp timeStamp = TimeStamp.fromNow(start);
Logger.info("Successfully downloaded JAR file in %s!", timeStamp);
// Attempt to decompile
if (this.decompile && !decompileJar()) {
fail("Failed to decompile JAR.");
return;
}
cleanup();

timeSpan.finish();
Logger.info("Completed in %s!", timeSpan);
this.appOption.ifPresent(app -> app.finish(timeSpan));
}

public boolean downloadJar() {
// Attempt to use cached jar first
if (this.cachedJarName != null) {
this.jarPath = this.dataFolderPath.resolve(this.cachedJarName);
if (Files.exists(this.jarPath)) {
Logger.info("Using cached jar!");
return true;
} else {
Logger.error("Cached jar as not found.");
return false;
}
} else { // If no cached jar, continue with donload
TimeSpan timeSpan = TimeSpan.start();
Logger.info("Downloading JAR file from Mojang.");
this.appOption.ifPresent(a -> {
a.updateStatusBox("Downloading JAR...");
a.updateButton("Downloading JAR...", Color.BLUE);
});
this.jarPath = this.dataFolderPath.resolve(this.minecraftJarName);

// Try open a connection with Mojang servers for downloads
final HttpURLConnection connection;
long connectionContentLength;
try {
final URL jarURL = new URL(this.version.getJarURL());
connection = (HttpURLConnection) jarURL.openConnection();
connectionContentLength = connection.getContentLengthLong();
} catch (IOException e) {
logException("Failed to open connection to Mojang servers", e);
return false;
}

// Check if we already have the jar on the computer
boolean jarExists;
try {
jarExists = Files.exists(jarPath) && Files.size(jarPath) == connectionContentLength;
} catch (IOException e) {
logException("Failed to check if jar file exists", e);
return false;
}

// If the jar exists skip download, else attempt to download
if (jarExists) {
Logger.warn("Already have JAR, skipping download.");
return true;
} else try (final InputStream inputStream = connection.getInputStream()) {
Files.copy(inputStream, jarPath, REPLACE_EXISTING);
} catch (IOException e) {
logException("Failed to download jar", e);
return false;
}
timeSpan.finish();
Logger.info("Successfully downloaded JAR file in %s!", timeSpan);
return true;
}
}

public void downloadMappings() throws IOException {
long start = System.currentTimeMillis();
public boolean downloadMappings() {
TimeSpan timeSpan = TimeSpan.start();
Logger.info("Downloading mappings file from Mojang...");
if (this.app != null) {
this.app.updateStatusBox("Downloading mappings...");
this.app.updateButton("Downloading mappings...", Color.BLUE);
this.appOption.ifPresent(app -> {
app.updateStatusBox("Downloading mappings...");
app.updateButton("Downloading mappings...", Color.BLUE);
});

final HttpURLConnection connection;
try {
final URL mappingURL = new URL(this.version.getMapURL());
connection = (HttpURLConnection) mappingURL.openConnection();
} catch (IOException e) {
logException("Failed to open connection to Mojang servers", e);
return false;
}
final URL mappingURL = new URL(this.version.getMapURL());
final HttpURLConnection connection = (HttpURLConnection) mappingURL.openConnection();
final long length = connection.getContentLengthLong();
this.mappingsPath = this.dataFolderPath.resolve(this.mappingsName);
if (Files.exists(this.mappingsPath) && Files.size(this.mappingsPath) == length) {
boolean mappingExists;
try {
mappingExists = Files.exists(this.mappingsPath) && Files.size(this.mappingsPath) == length;
} catch (IOException e) {
logException("Failed to check if mapping file exists", e);
return false;
}
if (mappingExists) {
Logger.info("Already have mappings, skipping download.");
return true;
} else try (final InputStream inputStream = connection.getInputStream()) {
Files.copy(inputStream, this.mappingsPath, REPLACE_EXISTING);
} catch (IOException e) {
logException("Failed to download mappings", e);
return false;
}

TimeStamp timeStamp = TimeStamp.fromNow(start);
Logger.info("Successfully downloaded mappings file in %s!", timeStamp);
timeSpan.finish();
Logger.info("Successfully downloaded mappings file in %s!", timeSpan);
return true;
}

public void remapJar() {
long start = System.currentTimeMillis();
if (this.app != null) {
this.app.updateStatusBox("Remapping...");
this.app.updateButton("Remapping...", Color.BLUE);
}
public boolean remapJar() {
TimeSpan timeSpan = TimeSpan.start();
this.appOption.ifPresent(app -> {
app.updateStatusBox("Remapping...");
app.updateButton("Remapping...", Color.BLUE);
});
this.remappedJar = this.dataFolderPath.resolve(this.mappedJarName);

if (!Files.exists(remappedJar)) {
if (Files.exists(this.remappedJar)) {
Logger.info("%s already remapped... skipping mapping.", this.mappedJarName);
} else {
Logger.info("Remapping %s file...", this.minecraftJarName);

try {
Expand All @@ -187,28 +268,34 @@ public void remapJar() {
Util.stripFileFromJar(this.remappedJar, "it/*");
}
} catch (IOException e) {
throw new RuntimeException(e);
logException("Failed to run remapper", e);
return false;
}

TimeStamp timeStamp = TimeStamp.fromNow(start);
Logger.info("Remapping completed in %s!", timeStamp);
} else {
Logger.info("%s already remapped... skipping mapping.", this.mappedJarName);
timeSpan.finish();
Logger.info("Remapping completed in %s!", timeSpan);
}
return true;
}

@SuppressWarnings("resource")
public void decompileJar() throws IOException {
long start = System.currentTimeMillis();
public boolean decompileJar() {
TimeSpan timeSpan = TimeSpan.start();
Logger.info("Decompiling final JAR file.");
if (this.app != null) {
this.app.updateStatusBox("Decompiler starting...");
this.app.updateButton("Decompiling...", Color.BLUE);
this.appOption.ifPresent(app -> {
app.updateStatusBox("Decompiler starting...");
app.updateButton("Decompiling...", Color.BLUE);
});
final Path decompileDir;
try {
decompileDir = Files.createDirectories(this.dataFolderPath.resolve("final-decompile"));
} catch (IOException e) {
logException("Failed to create final-decompile path", e);
return false;
}
final Path decompileDir = Files.createDirectories(this.dataFolderPath.resolve("final-decompile"));

// Setup and run FernFlower
AppLogger appLogger = new AppLogger(this.app);
AppLogger appLogger = new AppLogger(this.appOption.orElse(null));
ConsoleDecompiler decompiler = new ConsoleDecompiler(new File(decompileDir.toUri()), Util.getDecompilerParams(), appLogger);
decompiler.addSource(new File(this.remappedJar.toUri()));
decompiler.decompileContext();
Expand All @@ -217,8 +304,21 @@ public void decompileJar() throws IOException {
// Rename jar file to zip
Util.renameJarsToZips(decompileDir);

TimeStamp timeStamp = TimeStamp.fromNow(start);
Logger.info("Decompiling completed in %s!", timeStamp);
timeSpan.finish();
Logger.info("Decompiling completed in %s!", timeSpan);
return true;
}

@SuppressWarnings("CallToPrintStackTrace")
private void logException(String message, Exception exception) {
Logger.error(message + ": " + exception.getMessage());
if (this.debug) exception.printStackTrace();
}

private void fail(String failMessage) {
Logger.error(failMessage);
this.appOption.ifPresent(app -> app.fail(failMessage));
cleanup();
}

private void cleanup() {
Expand Down
Loading