mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-15 07:39:00 -04:00
Add OptiFine automatic installation
Unlike other modloaders, it does require the game being installed beforehand, so it's also implemented here
This commit is contained in:
parent
3cfaec3b4c
commit
84a2bd24c7
Binary file not shown.
@ -1 +1 @@
|
||||
1687971356220
|
||||
1688118307460
|
@ -1,9 +1,11 @@
|
||||
package net.kdt.pojavlaunch.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ExpandableListAdapter;
|
||||
|
||||
import net.kdt.pojavlaunch.JavaGUILauncherActivity;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy;
|
||||
@ -55,6 +57,8 @@ public class OptiFineInstallFragment extends ModVersionListFragment<OptiFineUtil
|
||||
|
||||
@Override
|
||||
public void onDownloadFinished(Context context, File downloadedFile) {
|
||||
Tools.dialog(context, "Not yet complete", "Installation of OptiFine is not yet implemented. To be done!");
|
||||
Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class);
|
||||
OptiFineUtils.addAutoInstallArgs(modInstallerStartIntent, downloadedFile);
|
||||
context.startActivity(modInstallerStartIntent);
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,25 @@ package net.kdt.pojavlaunch.modloaders;
|
||||
|
||||
import com.kdt.mcgui.ProgressLayout;
|
||||
|
||||
import net.kdt.pojavlaunch.JMinecraftVersionList;
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
|
||||
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback{
|
||||
public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback, AsyncMinecraftDownloader.DoneListener {
|
||||
private static final Pattern sMcVersionPattern = Pattern.compile("([0-9]+)\\.([0-9]+)\\.?([0-9]+)?");
|
||||
private final OptiFineUtils.OptiFineVersion mOptiFineVersion;
|
||||
private final File mDestinationFile;
|
||||
private final ModloaderDownloadListener mListener;
|
||||
private final Object mMinecraftDownloadLock = new Object();
|
||||
private Throwable mDownloaderThrowable;
|
||||
|
||||
public OptiFineDownloadTask(OptiFineUtils.OptiFineVersion mOptiFineVersion, File mDestinationFile, ModloaderDownloadListener mListener) {
|
||||
this.mOptiFineVersion = mOptiFineVersion;
|
||||
@ -23,7 +30,7 @@ public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback{
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.of_dl_progress, mOptiFineVersion.downloadUrl);
|
||||
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.of_dl_progress, mOptiFineVersion.versionName);
|
||||
try {
|
||||
if(runCatching()) mListener.onDownloadFinished(mDestinationFile);
|
||||
}catch (IOException e) {
|
||||
@ -35,6 +42,17 @@ public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback{
|
||||
public boolean runCatching() throws IOException {
|
||||
String downloadUrl = scrapeDownloadsPage();
|
||||
if(downloadUrl == null) return false;
|
||||
String minecraftVersion = determineMinecraftVersion();
|
||||
if(minecraftVersion == null) return false;
|
||||
if(!downloadMinecraft(minecraftVersion)) {
|
||||
if(mDownloaderThrowable instanceof Exception) {
|
||||
mListener.onDownloadError((Exception) mDownloaderThrowable);
|
||||
}else {
|
||||
Exception exception = new Exception(mDownloaderThrowable);
|
||||
mListener.onDownloadError(exception);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
DownloadUtils.downloadFileMonitored(downloadUrl, mDestinationFile, new byte[8192], this);
|
||||
return true;
|
||||
}
|
||||
@ -45,9 +63,59 @@ public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback{
|
||||
return scrapeResult;
|
||||
}
|
||||
|
||||
public String determineMinecraftVersion() {
|
||||
Matcher matcher = sMcVersionPattern.matcher(mOptiFineVersion.minecraftVersion);
|
||||
if(matcher.find()) {
|
||||
StringBuilder mcVersionBuilder = new StringBuilder();
|
||||
mcVersionBuilder.append(matcher.group(1));
|
||||
mcVersionBuilder.append('.');
|
||||
mcVersionBuilder.append(matcher.group(2));
|
||||
String thirdGroup = matcher.group(3);
|
||||
if(thirdGroup != null && !thirdGroup.isEmpty() && !"0".equals(thirdGroup)) {
|
||||
mcVersionBuilder.append('.');
|
||||
mcVersionBuilder.append(thirdGroup);
|
||||
}
|
||||
return mcVersionBuilder.toString();
|
||||
}else{
|
||||
mListener.onDataNotAvailable();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean downloadMinecraft(String minecraftVersion) {
|
||||
// the string is always normalized
|
||||
JMinecraftVersionList.Version minecraftJsonVersion = AsyncMinecraftDownloader.getListedVersion(minecraftVersion);
|
||||
if(minecraftJsonVersion == null) return false;
|
||||
try {
|
||||
synchronized (mMinecraftDownloadLock) {
|
||||
new AsyncMinecraftDownloader(null, minecraftJsonVersion, minecraftVersion, this);
|
||||
mMinecraftDownloadLock.wait();
|
||||
}
|
||||
}catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return mDownloaderThrowable == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
int progress100 = (int)(((float)curr / (float)max)*100f);
|
||||
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.of_dl_progress, mOptiFineVersion.versionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadDone() {
|
||||
synchronized (mMinecraftDownloadLock) {
|
||||
mDownloaderThrowable = null;
|
||||
mMinecraftDownloadLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFailed(Throwable throwable) {
|
||||
synchronized (mMinecraftDownloadLock) {
|
||||
mDownloaderThrowable = throwable;
|
||||
mMinecraftDownloadLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ public class OptiFineScraper implements DownloadUtils.ParseCallback<OptiFineUtil
|
||||
|
||||
private void traverseDownloadLine(TagNode tagNode) {
|
||||
OptiFineUtils.OptiFineVersion optiFineVersion = new OptiFineUtils.OptiFineVersion();
|
||||
optiFineVersion.minecraftVersion = mMinecraftVersion;
|
||||
for(TagNode subNode : tagNode.getChildTags()) {
|
||||
if(!subNode.getName().equals("td")) continue;
|
||||
switch(subNode.getAttributeByName("class")) {
|
||||
|
@ -1,7 +1,11 @@
|
||||
package net.kdt.pojavlaunch.modloaders;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@ -17,11 +21,19 @@ public class OptiFineUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addAutoInstallArgs(Intent intent, File modInstallerJar) {
|
||||
intent.putExtra("javaArgs", "-javaagent:"+ Tools.DIR_DATA+"/forge_installer/forge_installer.jar"
|
||||
+ "=OFNPS" +// No Profile Suppression
|
||||
" -jar "+modInstallerJar.getAbsolutePath());
|
||||
intent.putExtra("skipDetectMod", true);
|
||||
}
|
||||
|
||||
public static class OptiFineVersions {
|
||||
public List<String> minecraftVersions;
|
||||
public List<List<OptiFineVersion>> optifineVersions;
|
||||
}
|
||||
public static class OptiFineVersion {
|
||||
public String minecraftVersion;
|
||||
public String versionName;
|
||||
public String downloadUrl;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class AsyncMinecraftDownloader {
|
||||
/* Allows each downloading thread to have its own RECYCLED buffer */
|
||||
private final ConcurrentHashMap<Thread, byte[]> mThreadBuffers = new ConcurrentHashMap<>(5);
|
||||
|
||||
public AsyncMinecraftDownloader(@NonNull Activity activity, JMinecraftVersionList.Version version, String realVersion,
|
||||
public AsyncMinecraftDownloader(Activity activity, JMinecraftVersionList.Version version, String realVersion,
|
||||
@NonNull DoneListener listener){ // this was there for a reason
|
||||
sExecutorService.execute(() -> {
|
||||
try {
|
||||
@ -58,7 +58,7 @@ public class AsyncMinecraftDownloader {
|
||||
});
|
||||
}
|
||||
/* we do the throws DownloaderException thing to avoid blanket-catching Exception as a form of anti-lazy-developer protection */
|
||||
private void downloadGame(@NonNull Activity activity, JMinecraftVersionList.Version verInfo, String versionName) throws DownloaderException {
|
||||
private void downloadGame(Activity activity, JMinecraftVersionList.Version verInfo, String versionName) throws DownloaderException {
|
||||
final String downVName = "/" + versionName + "/" + versionName;
|
||||
|
||||
//Downloading libraries
|
||||
@ -88,7 +88,7 @@ public class AsyncMinecraftDownloader {
|
||||
verInfo = Tools.getVersionInfo(versionName);
|
||||
|
||||
// THIS one function need the activity in the case of an error
|
||||
if(!JRE17Util.installNewJreIfNeeded(activity, verInfo)){
|
||||
if(activity != null && !JRE17Util.installNewJreIfNeeded(activity, verInfo)){
|
||||
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
|
||||
throw new DownloaderException();
|
||||
}
|
||||
|
@ -392,7 +392,7 @@
|
||||
<string name="create_profile_modded_versions">Modded versions</string>
|
||||
<string name="fabric_dl_loader_title">Select versions</string>
|
||||
<string name="of_dl_select_version">Select OptiFine version</string>
|
||||
<string name="of_dl_failed_to_scrape">Failed to get the OptiFine download link</string>
|
||||
<string name="of_dl_failed_to_scrape">Failed to collect data for OptiFine installation</string>
|
||||
<string name="of_dl_progress">Downloading %s</string>
|
||||
<string name="create_profile_optifine">Create OptiFine profile</string>
|
||||
</resources>
|
||||
|
@ -20,11 +20,12 @@ import javax.swing.JOptionPane;
|
||||
public class Agent implements AWTEventListener {
|
||||
private boolean forgeWindowHandled = false;
|
||||
private final boolean suppressProfileCreation;
|
||||
|
||||
private final boolean optiFineInstallation;
|
||||
private final Timer componentTimer = new Timer();
|
||||
|
||||
public Agent(boolean ps) {
|
||||
this.suppressProfileCreation = ps;
|
||||
public Agent(boolean nps, boolean of) {
|
||||
this.suppressProfileCreation = !nps;
|
||||
this.optiFineInstallation = of;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,7 +34,7 @@ public class Agent implements AWTEventListener {
|
||||
Window window = windowEvent.getWindow();
|
||||
if(windowEvent.getID() == WindowEvent.WINDOW_OPENED) {
|
||||
if(!forgeWindowHandled) { // false at startup, so we will handle the first window as the Forge one
|
||||
forgeWindowHandled = handleForgeWindow(window);
|
||||
forgeWindowHandled = handleMainWindow(window);
|
||||
if(forgeWindowHandled) {
|
||||
componentTimer.cancel();
|
||||
componentTimer.purge();
|
||||
@ -46,34 +47,47 @@ public class Agent implements AWTEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleForgeWindow(Window window) {
|
||||
public boolean handleMainWindow(Window window) {
|
||||
List<Component> components = new ArrayList<>();
|
||||
insertAllComponents(components, window, new MainWindowFilter());
|
||||
AbstractButton okButton = null;
|
||||
for(Component component : components) {
|
||||
if(component instanceof AbstractButton) {
|
||||
AbstractButton abstractButton = (AbstractButton) component;
|
||||
switch(abstractButton.getText()) {
|
||||
case "OK":
|
||||
okButton = abstractButton; // store the button, so we can press it after processing other stuff
|
||||
break;
|
||||
case "Install client":
|
||||
abstractButton.doClick(); // It should be the default, but let's make sure
|
||||
}
|
||||
|
||||
abstractButton = optiFineInstallation ?
|
||||
handleOptiFineButton(abstractButton) :
|
||||
handleForgeButton(abstractButton);
|
||||
if(abstractButton != null) okButton = abstractButton;
|
||||
}
|
||||
}
|
||||
if(okButton == null) {
|
||||
System.out.println("Failed to set all the UI components, wil try again in the next window");
|
||||
System.exit(17);
|
||||
return false;
|
||||
}else{
|
||||
ProfileFixer.storeProfile();
|
||||
ProfileFixer.storeProfile(optiFineInstallation ? "OptiFine" : "forge");
|
||||
EventQueue.invokeLater(okButton::doClick); // do that after forge actually builds its window, otherwise we set the path too fast
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AbstractButton handleForgeButton(AbstractButton abstractButton) {
|
||||
switch(abstractButton.getText()) {
|
||||
case "OK":
|
||||
return abstractButton; // return the button, so we can press it after processing other stuff
|
||||
case "Install client":
|
||||
abstractButton.doClick(); // It should be the default, but let's make sure
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public AbstractButton handleOptiFineButton(AbstractButton abstractButton) {
|
||||
if ("Install".equals(abstractButton.getText())) {
|
||||
return abstractButton;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleDialog(Window window) {
|
||||
List<Component> components = new ArrayList<>();
|
||||
insertAllComponents(components, window, new DialogFilter()); // ensure that it's a JOptionPane dialog
|
||||
@ -84,7 +98,7 @@ public class Agent implements AWTEventListener {
|
||||
JOptionPane optionPane = (JOptionPane) components.get(0);
|
||||
if(optionPane.getMessageType() == JOptionPane.INFORMATION_MESSAGE) { // forge doesn't emit information messages for other reasons yet
|
||||
System.out.println("The install was successful!");
|
||||
ProfileFixer.reinsertProfile(suppressProfileCreation);
|
||||
ProfileFixer.reinsertProfile(optiFineInstallation ? "OptiFine" : "forge", suppressProfileCreation);
|
||||
System.exit(0); // again, forge doesn't call exit for some reason, so we do that ourselves here
|
||||
}
|
||||
}
|
||||
@ -102,8 +116,15 @@ public class Agent implements AWTEventListener {
|
||||
}
|
||||
|
||||
public static void premain(String args, Instrumentation inst) {
|
||||
boolean noProfileSuppression = false;
|
||||
boolean optifine = false;
|
||||
if(args != null ) {
|
||||
noProfileSuppression = args.contains("NPS"); // No Profile Suppression
|
||||
optifine = args.contains("OF"); // OptiFine
|
||||
}
|
||||
Agent agent = new Agent(noProfileSuppression, optifine);
|
||||
Toolkit.getDefaultToolkit()
|
||||
.addAWTEventListener(new Agent(!"NPS".equals(args)), // No Profile Suppression
|
||||
.addAWTEventListener(agent,
|
||||
AWTEvent.WINDOW_EVENT_MASK);
|
||||
}
|
||||
}
|
||||
|
@ -15,23 +15,23 @@ public class ProfileFixer {
|
||||
private static final Random random = new Random();
|
||||
private static final Path profilesPath = Paths.get(System.getProperty("user.home"), ".minecraft", "launcher_profiles.json");
|
||||
private static JSONObject oldProfile = null;
|
||||
public static void storeProfile() {
|
||||
public static void storeProfile(String profileName) {
|
||||
try {
|
||||
JSONObject minecraftProfiles = new JSONObject(
|
||||
new String(Files.readAllBytes(profilesPath),
|
||||
StandardCharsets.UTF_8)
|
||||
);
|
||||
JSONObject profilesArray = minecraftProfiles.getJSONObject("profiles");
|
||||
oldProfile = profilesArray.optJSONObject("forge", null);
|
||||
oldProfile = profilesArray.optJSONObject(profileName, null);
|
||||
}catch (IOException | JSONException e) {
|
||||
System.out.println("Failed to store Forge profile: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String pickProfileName() {
|
||||
return "forge"+random.nextInt();
|
||||
private static String pickProfileName(String profileName) {
|
||||
return profileName+random.nextInt();
|
||||
}
|
||||
public static void reinsertProfile(boolean suppressProfileCreation) {
|
||||
public static void reinsertProfile(String profileName, boolean suppressProfileCreation) {
|
||||
try {
|
||||
JSONObject minecraftProfiles = new JSONObject(
|
||||
new String(Files.readAllBytes(profilesPath),
|
||||
@ -41,8 +41,8 @@ public class ProfileFixer {
|
||||
if(oldProfile != null) {
|
||||
if(suppressProfileCreation) profilesArray.put("forge", oldProfile); // restore the old profile
|
||||
else {
|
||||
String name = pickProfileName();
|
||||
while(profilesArray.has(name)) name = pickProfileName();
|
||||
String name = pickProfileName(profileName);
|
||||
while(profilesArray.has(name)) name = pickProfileName(profileName);
|
||||
profilesArray.put(name, oldProfile); // restore the old profile under a new name
|
||||
}
|
||||
}else{
|
||||
|
Loading…
x
Reference in New Issue
Block a user