/*
* Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
* Copyright (C) 2021 cyoung06
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package kr.syeyoung.dungeonsguide.launcher;
import kr.syeyoung.dungeonsguide.launcher.auth.AuthManager;
import kr.syeyoung.dungeonsguide.launcher.branch.UpdateRetrieverUtil;
import kr.syeyoung.dungeonsguide.launcher.exceptions.DungeonsGuideLoadingException;
import kr.syeyoung.dungeonsguide.launcher.exceptions.DungeonsGuideUnloadingException;
import kr.syeyoung.dungeonsguide.launcher.exceptions.NoSuitableLoaderFoundException;
import kr.syeyoung.dungeonsguide.launcher.exceptions.NoVersionFoundException;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiChooseVersion;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiDisplayer;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiLoadingError;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiUnloadingError;
import kr.syeyoung.dungeonsguide.launcher.gui.tooltip.Notification;
import kr.syeyoung.dungeonsguide.launcher.gui.tooltip.NotificationManager;
import kr.syeyoung.dungeonsguide.launcher.loader.*;
import kr.syeyoung.dungeonsguide.launcher.util.ProgressStateHolder;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.client.SplashProgress;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.ProgressManager;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.lwjgl.LWJGLException;
import java.io.File;
import java.io.IOException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@Mod(modid = Main.MOD_ID, version = Main.VERSION)
public class Main
{
public static final String MOD_ID = "dungeons_guide_loader";
public static final String VERSION = "4.0.0";
public static final String DOMAIN = "https://v2.dungeons.guide/api";
private static Main main;
private static File configDir;
private DGInterface dgInterface;
private final List listeners = new ArrayList<>();
public static File getConfigDir() {
return configDir;
}
public void addDGReloadListener(DungeonsGuideReloadListener dungeonsGuideReloadListener) {
listeners.add(Objects.requireNonNull(dungeonsGuideReloadListener));
}
public void removeDGReloadListener(DungeonsGuideReloadListener dungeonsGuideReloadListener) {
listeners.remove(dungeonsGuideReloadListener);
}
@Getter
private IDGLoader currentLoader;
private static final UUID dgUnloaded = UUID.randomUUID();
@EventHandler
public void initEvent(FMLInitializationEvent initializationEvent) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.register(GuiDisplayer.INSTANCE);
MinecraftForge.EVENT_BUS.register(NotificationManager.INSTANCE);
NotificationManager.INSTANCE.updateNotification(dgUnloaded, Notification.builder()
.title("Dungeons Guide Not Loaded")
.titleColor(0xFFFF0000)
.description("Click to try reloading....")
.onClick(() -> {
GuiDisplayer.INSTANCE.displayGui(new GuiChooseVersion(new RuntimeException("just unloaded")));
})
.unremovable(true)
.build());
try {
File f = new File(configDir, "loader.cfg");
Configuration configuration = new Configuration(f);
configuration.save();
IDGLoader idgLoader = obtainLoader(configuration);
tryReloading(idgLoader);
} catch (NoSuitableLoaderFoundException | NoVersionFoundException e) {
e.printStackTrace();
GuiDisplayer.INSTANCE.displayGui(new GuiChooseVersion(e));
}
}
public void tryReloadingWithSplash(IDGLoader loader) {
SplashProgress.start();
try {
SplashProgress.drawVanillaScreen(Minecraft.getMinecraft().renderEngine);
} catch (LWJGLException e) {
e.printStackTrace();
}
try {
tryReloading(loader);
} finally {
try {
SplashProgress.finish();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
public void tryReloading(IDGLoader loader) {
try {
reload(loader);
} catch (DungeonsGuideLoadingException e) {
e.printStackTrace();
try {
unload();
} catch (Exception e2) {
e2.printStackTrace();
GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e2));
}
GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e));
} catch (DungeonsGuideUnloadingException e) {
GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e));
}
}
public void unload() throws DungeonsGuideUnloadingException {
if (currentLoader != null && !currentLoader.isUnloadable()) {
throw new UnsupportedOperationException("Current version is not unloadable");
}
dgInterface = null;
for (DungeonsGuideReloadListener listener : listeners) {
listener.unloadReference();
}
NotificationManager.INSTANCE.updateNotification(dgUnloaded, Notification.builder()
.title("Dungeons Guide Not Loaded")
.titleColor(0xFFFF0000)
.description("Click to try reloading....")
.onClick(() -> {
GuiDisplayer.INSTANCE.displayGui(new GuiChooseVersion(new RuntimeException("just unloaded")));
})
.unremovable(true)
.build());
if (currentLoader != null) {
currentLoader.unloadDungeonsGuide();
}
currentLoader = null;
}
private void load(IDGLoader newLoader) throws DungeonsGuideLoadingException {
if (dgInterface != null) throw new IllegalStateException("DG is loaded");
dgInterface = newLoader.loadDungeonsGuide();
currentLoader = newLoader;
try {
dgInterface.init(configDir);
} catch (Exception e) {
throw new DungeonsGuideLoadingException("Exception occured while calling init", e);
}
for (DungeonsGuideReloadListener listener : listeners) {
listener.onLoad(dgInterface);
}
NotificationManager.INSTANCE.updateNotification(UUID.randomUUID(), Notification.builder()
.title("Dungeons Guide Loaded!")
.description("Successfully Loaded Dungeons Guide!\nLoader: "+currentLoader.loaderName()+"\nVersion: "+currentLoader.version())
.titleColor(0xFF00FF00)
.build());
NotificationManager.INSTANCE.removeNotification(dgUnloaded);
}
private volatile IDGLoader reqLoader = null;
public void reloadWithoutStacktraceReference(IDGLoader newLoader) {
reqLoader = newLoader;
}
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent tickEvent) {
if (reqLoader != null) {
IDGLoader loader = reqLoader;
reqLoader = null;
tryReloadingWithSplash(loader);
}
}
public void reload(IDGLoader newLoader) throws DungeonsGuideLoadingException, DungeonsGuideUnloadingException {
try {
ProgressStateHolder.pushProgress("Loading Dungeons Guide...", 2);
ProgressStateHolder.step("Unloading Currently Loaded Version...");
unload();
ProgressStateHolder.step("Loading New Version...");
load(newLoader);
} catch (DungeonsGuideLoadingException | DungeonsGuideUnloadingException e) {
dgInterface = null;
// currentLoader = null;
e.printStackTrace();
throw e;
} finally {
ProgressStateHolder.pop();
}
}
public String getLoaderName(Configuration configuration) {
String loader = System.getProperty("dg.loader");
if (loader == null) {
loader = configuration.get("loader", "modsource", "auto").getString();
}
if (loader == null) loader = "auto";
return loader;
}
public IDGLoader obtainLoader(Configuration configuration) throws NoVersionFoundException, NoSuitableLoaderFoundException {
String loader = getLoaderName(configuration);
if ("devenv".equals(loader)) {
return new DevEnvLoader();
} else if ("local".equals(loader) ||
(loader.equals("auto") && this.getClass().getResourceAsStream("/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.class") != null)) {
return new LocalLoader();
} else if ("jar".equals(loader) ||
(loader.equals("auto") && this.getClass().getResourceAsStream("/mod.jar") != null)) {
return new JarLoader();
} else if (loader.equals("remote") || loader.equals("auto") ){
// remote load
String branch = System.getProperty("branch") == null ? configuration.get("loader", "remoteBranch", "$default").getString() : System.getProperty("branch");
String version = System.getProperty("version") == null ? configuration.get("loader", "remoteVersion", "latest").getString() : System.getProperty("version");
try {
UpdateRetrieverUtil.VersionInfo versionInfo = UpdateRetrieverUtil.getIds(
branch,
version
);
return new RemoteLoader(versionInfo.getFriendlyBranchName(), versionInfo.getBranchId(), versionInfo.getUpdateId());
} catch (IOException e) {
throw new NoVersionFoundException(branch, version, "IO err", e);
}
} else {
throw new NoSuitableLoaderFoundException(System.getProperty("dg.loader"), configuration.get("loader", "modsource", "auto").getString());
}
}
@EventHandler
public void preInit(FMLPreInitializationEvent preInitializationEvent) {
Security.addProvider(new BouncyCastleProvider());
// setup static variables
main = this;
dgInterface = null;
currentLoader = null;
configDir = preInitializationEvent.getModConfigurationDirectory();
// setup preinit progress bar for well, progress bar!
ProgressManager.ProgressBar bar = ProgressManager.push("DungeonsGuide", 1);
// Try authenticate
bar.step("Authenticating...");
try {
AuthManager.getInstance().init();
} catch (Exception e) {
e.printStackTrace();
}
File f = new File(preInitializationEvent.getModConfigurationDirectory(), "loader.cfg");
Configuration configuration = new Configuration(f);
// Save config because... well to generate it
configuration.save();
while(bar.getStep() < bar.getSteps()) bar.step("");
ProgressManager.pop(bar);
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(a -> {
if (dgInterface != null) dgInterface.onResourceReload(a);
});
}
public static Main getMain() {
return main;
}
}