Skip to content

Commit 30ec1e2

Browse files
author
BuildTools
committedJun 25, 2023
Fully automated Forge version list and installation
LexManos may feel free to fuck off ngl Also: makes the Arc DNS injector and the Forge installer separate components instead of non-overwritable assets, so that we can actually update them on the user's end
1 parent b263a9f commit 30ec1e2

25 files changed

+634
-9
lines changed
 
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1678189863424
1+
1687691695259

‎app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -590,11 +590,15 @@ private static void showError(final Context ctx, final int titleId, final String
590590
}
591591

592592
public static void dialogOnUiThread(final Activity activity, final CharSequence title, final CharSequence message) {
593-
activity.runOnUiThread(() -> new AlertDialog.Builder(activity)
593+
activity.runOnUiThread(()->dialog(activity, title, message));
594+
}
595+
596+
public static void dialog(final Context context, final CharSequence title, final CharSequence message) {
597+
new AlertDialog.Builder(context)
594598
.setTitle(title)
595599
.setMessage(message)
596600
.setPositiveButton(android.R.string.ok, null)
597-
.show());
601+
.show();
598602
}
599603

600604
public static void openURL(Activity act, String url) {

‎app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import static net.kdt.pojavlaunch.Tools.shareLog;
44

55
import android.content.Intent;
6-
import android.net.Uri;
76
import android.os.Bundle;
8-
import android.provider.DocumentsContract;
97
import android.view.View;
8+
import android.view.ViewGroup;
109
import android.widget.Button;
1110
import android.widget.ImageButton;
1211
import android.widget.Toast;
@@ -15,12 +14,12 @@
1514
import androidx.annotation.Nullable;
1615
import androidx.fragment.app.Fragment;
1716

18-
1917
import net.kdt.pojavlaunch.CustomControlsActivity;
2018
import net.kdt.pojavlaunch.R;
2119
import net.kdt.pojavlaunch.Tools;
2220
import net.kdt.pojavlaunch.extra.ExtraConstants;
2321
import net.kdt.pojavlaunch.extra.ExtraCore;
22+
import net.kdt.pojavlaunch.modloaders.ForgeDownloaderDialog;
2423
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
2524

2625
public class MainMenuFragment extends Fragment {
@@ -52,6 +51,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
5251
mPlayButton.setOnClickListener(v -> ExtraCore.setValue(ExtraConstants.LAUNCH_GAME, true));
5352

5453
mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext()));
54+
55+
mNewsButton.setOnLongClickListener((v)->{
56+
new ForgeDownloaderDialog().show(view.getContext(), (ViewGroup) view);
57+
return true;
58+
});
5559
}
5660
private void runInstallerWithConfirmation(boolean isCustomArgs) {
5761
if (ProgressKeeper.getTaskCount() == 0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
public interface ForgeDownloadListener {
4+
void onDownloadFinished();
5+
void onInstallerNotAvailable();
6+
void onDownloadError(Exception e);
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
import com.kdt.mcgui.ProgressLayout;
4+
5+
import net.kdt.pojavlaunch.R;
6+
import net.kdt.pojavlaunch.Tools;
7+
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
8+
import net.kdt.pojavlaunch.utils.DownloadUtils;
9+
10+
import java.io.File;
11+
import java.io.FileNotFoundException;
12+
import java.io.IOException;
13+
14+
public class ForgeDownloadTask implements Runnable, Tools.DownloaderFeedback {
15+
private final String mForgeUrl;
16+
private final String mForgeVersion;
17+
private final File mDestinationFile;
18+
private final ForgeDownloadListener mListener;
19+
public ForgeDownloadTask(ForgeDownloadListener listener, String forgeVersion, File destinationFile) {
20+
this.mListener = listener;
21+
this.mForgeUrl = ForgeUtils.getInstallerUrl(forgeVersion);
22+
this.mForgeVersion = forgeVersion;
23+
this.mDestinationFile = destinationFile;
24+
}
25+
@Override
26+
public void run() {
27+
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_progress, mForgeVersion);
28+
try {
29+
byte[] buffer = new byte[8192];
30+
DownloadUtils.downloadFileMonitored(mForgeUrl, mDestinationFile, buffer, this);
31+
mListener.onDownloadFinished();
32+
}catch (IOException e) {
33+
if(e instanceof FileNotFoundException) {
34+
mListener.onInstallerNotAvailable();
35+
}else{
36+
mListener.onDownloadError(e);
37+
}
38+
}
39+
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, -1, -1);
40+
}
41+
42+
@Override
43+
public void updateProgress(int curr, int max) {
44+
int progress100 = (int)(((float)curr / (float)max)*100f);
45+
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.forge_dl_progress, mForgeVersion);
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
import android.app.AlertDialog;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.os.Handler;
7+
import android.os.Looper;
8+
import android.view.LayoutInflater;
9+
import android.view.View;
10+
import android.view.ViewGroup;
11+
import android.widget.ExpandableListView;
12+
import android.widget.ProgressBar;
13+
14+
import net.kdt.pojavlaunch.JavaGUILauncherActivity;
15+
import net.kdt.pojavlaunch.R;
16+
import net.kdt.pojavlaunch.Tools;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.util.List;
21+
22+
public class ForgeDownloaderDialog implements Runnable, ExpandableListView.OnChildClickListener, ForgeDownloadListener {
23+
private final Handler mHandler = new Handler(Looper.getMainLooper());
24+
private ExpandableListView mExpandableListView;
25+
private ProgressBar mProgressBar;
26+
private AlertDialog mAlertDialog;
27+
private File mDestinationFile;
28+
private Context mContext;
29+
public void show(Context context, ViewGroup root) {
30+
this.mContext = context;
31+
this.mDestinationFile = new File(context.getCacheDir(), "forge-installer.jar");
32+
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
33+
View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_expandable_forge_list, root, false);
34+
mProgressBar = dialogView.findViewById(R.id.forge_list_progress_bar);
35+
mExpandableListView = dialogView.findViewById(R.id.forge_expandable_version_list);
36+
mExpandableListView.setOnChildClickListener(this);
37+
dialogBuilder.setView(dialogView);
38+
mAlertDialog = dialogBuilder.show();
39+
new Thread(this).start();
40+
}
41+
42+
@Override
43+
public void run() {
44+
try {
45+
List<String> forgeVersions = ForgeUtils.downloadForgeVersions();
46+
mHandler.post(()->{
47+
if(forgeVersions != null) {
48+
mProgressBar.setVisibility(View.GONE);
49+
mExpandableListView.setAdapter(new ForgeVersionListAdapter(forgeVersions, LayoutInflater.from(mContext)));
50+
}else{
51+
mAlertDialog.dismiss();
52+
}
53+
});
54+
}catch (IOException e) {
55+
mHandler.post(()->{
56+
mAlertDialog.dismiss();
57+
Tools.showError(mContext, e);
58+
});
59+
}
60+
}
61+
62+
63+
64+
@Override
65+
public boolean onChildClick(ExpandableListView expandableListView, View view, int i, int i1, long l) {
66+
String forgeVersion = (String)expandableListView.getExpandableListAdapter().getChild(i, i1);
67+
new Thread(new ForgeDownloadTask(this, forgeVersion, mDestinationFile)).start();
68+
mAlertDialog.dismiss();
69+
return true;
70+
}
71+
72+
@Override
73+
public void onDownloadFinished() {
74+
Intent intent = new Intent(mContext, JavaGUILauncherActivity.class);
75+
ForgeUtils.addAutoInstallArgs(intent, mDestinationFile, true); // since it's a user-invoked install, we want to create a new profile
76+
mContext.startActivity(intent);
77+
}
78+
79+
@Override
80+
public void onInstallerNotAvailable() {
81+
mHandler.post(()-> {
82+
mAlertDialog.dismiss();
83+
Tools.dialog(mContext,
84+
mContext.getString(R.string.global_error),
85+
mContext.getString(R.string.forge_dl_no_installer));
86+
});
87+
}
88+
89+
@Override
90+
public void onDownloadError(Exception e) {
91+
mHandler.post(mAlertDialog::dismiss);
92+
Tools.showError(mContext, e);
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
import android.content.Intent;
4+
5+
import net.kdt.pojavlaunch.Tools;
6+
import net.kdt.pojavlaunch.utils.DownloadUtils;
7+
8+
import org.xml.sax.InputSource;
9+
import org.xml.sax.SAXException;
10+
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.io.StringReader;
14+
import java.util.List;
15+
16+
import javax.xml.parsers.ParserConfigurationException;
17+
import javax.xml.parsers.SAXParser;
18+
import javax.xml.parsers.SAXParserFactory;
19+
20+
public class ForgeUtils {
21+
private static final String FORGE_METADATA_URL = "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml";
22+
private static final String FORGE_INSTALLER_URL = "https://maven.minecraftforge.net/net/minecraftforge/forge/%1$s/forge-%1$s-installer.jar";
23+
public static List<String> downloadForgeVersions() throws IOException {
24+
String forgeMetadata = DownloadUtils.downloadString(FORGE_METADATA_URL);
25+
try {
26+
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
27+
SAXParser parser = parserFactory.newSAXParser();
28+
ForgeVersionListHandler handler = new ForgeVersionListHandler();
29+
parser.parse(new InputSource(new StringReader(forgeMetadata)), handler);
30+
return handler.getVersions();
31+
}catch (SAXException | ParserConfigurationException e) {
32+
e.printStackTrace();
33+
return null;
34+
}
35+
}
36+
public static String getInstallerUrl(String version) {
37+
return String.format(FORGE_INSTALLER_URL, version);
38+
}
39+
40+
public static void addAutoInstallArgs(Intent intent, File modInstallerJar, boolean createProfile) {
41+
intent.putExtra("javaArgs", "-javaagent:"+ Tools.DIR_DATA+"/forge_installer/forge_installer.jar"
42+
+ (createProfile ? "=NPS" : "") + // No Profile Suppression
43+
" -jar "+modInstallerJar.getAbsolutePath());
44+
intent.putExtra("skipDetectMod", true);
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
import android.view.LayoutInflater;
4+
import android.view.View;
5+
import android.view.ViewGroup;
6+
import android.widget.BaseExpandableListAdapter;
7+
import android.widget.ExpandableListAdapter;
8+
import android.widget.TextView;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public class ForgeVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter {
14+
private final List<String> mGameVersions;
15+
private final List<List<String>> mForgeVersions;
16+
private final LayoutInflater mLayoutInflater;
17+
18+
public ForgeVersionListAdapter(List<String> forgeVersions, LayoutInflater layoutInflater) {
19+
this.mLayoutInflater = layoutInflater;
20+
mGameVersions = new ArrayList<>();
21+
mForgeVersions = new ArrayList<>();
22+
for(String version : forgeVersions) {
23+
int dashIndex = version.indexOf("-");
24+
String gameVersion = version.substring(0, dashIndex);
25+
List<String> versionList;
26+
int gameVersionIndex = mGameVersions.indexOf(gameVersion);
27+
if(gameVersionIndex != -1) versionList = mForgeVersions.get(gameVersionIndex);
28+
else {
29+
versionList = new ArrayList<>();
30+
mGameVersions.add(gameVersion);
31+
mForgeVersions.add(versionList);
32+
}
33+
versionList.add(version);
34+
}
35+
}
36+
37+
@Override
38+
public int getGroupCount() {
39+
return mGameVersions.size();
40+
}
41+
42+
@Override
43+
public int getChildrenCount(int i) {
44+
return mForgeVersions.get(i).size();
45+
}
46+
47+
@Override
48+
public Object getGroup(int i) {
49+
return getGameVersion(i);
50+
}
51+
52+
@Override
53+
public Object getChild(int i, int i1) {
54+
return getForgeVersion(i, i1);
55+
}
56+
57+
@Override
58+
public long getGroupId(int i) {
59+
return i;
60+
}
61+
62+
@Override
63+
public long getChildId(int i, int i1) {
64+
return i1;
65+
}
66+
67+
@Override
68+
public boolean hasStableIds() {
69+
return true;
70+
}
71+
72+
@Override
73+
public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) {
74+
if(convertView == null)
75+
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
76+
77+
((TextView) convertView).setText(getGameVersion(i));
78+
79+
return convertView;
80+
}
81+
82+
@Override
83+
public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) {
84+
if(convertView == null)
85+
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
86+
((TextView) convertView).setText(getForgeVersion(i, i1));
87+
return convertView;
88+
}
89+
90+
private String getGameVersion(int i) {
91+
return mGameVersions.get(i);
92+
}
93+
94+
private String getForgeVersion(int i, int i1){
95+
return mForgeVersions.get(i).get(i1);
96+
}
97+
98+
@Override
99+
public boolean isChildSelectable(int i, int i1) {
100+
return true;
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package net.kdt.pojavlaunch.modloaders;
2+
3+
import org.xml.sax.Attributes;
4+
import org.xml.sax.SAXException;
5+
import org.xml.sax.helpers.DefaultHandler;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
public class ForgeVersionListHandler extends DefaultHandler {
11+
private List<String> mForgeVersions;
12+
private StringBuilder mCurrentVersion = null;
13+
@Override
14+
public void startDocument() throws SAXException {
15+
mForgeVersions = new ArrayList<>();
16+
}
17+
18+
@Override
19+
public void characters(char[] ch, int start, int length) throws SAXException {
20+
if(mCurrentVersion != null) mCurrentVersion.append(ch, start, length);
21+
}
22+
23+
@Override
24+
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
25+
if(qName.equals("version")) mCurrentVersion = new StringBuilder();
26+
}
27+
28+
@Override
29+
public void endElement(String uri, String localName, String qName) throws SAXException {
30+
if (qName.equals("version")) {
31+
String version = mCurrentVersion.toString();
32+
mForgeVersions.add(version);
33+
mCurrentVersion = null;
34+
}
35+
}
36+
public List<String> getVersions() {
37+
return mForgeVersions;
38+
}
39+
}

‎app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncAssetManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ public static void unpackSingleFiles(Context ctx){
6868

6969
Tools.copyAssetFile(ctx, "launcher_profiles.json", Tools.DIR_GAME_NEW, false);
7070
Tools.copyAssetFile(ctx,"resolv.conf",Tools.DIR_DATA, false);
71-
Tools.copyAssetFile(ctx,"arc_dns_injector.jar",Tools.DIR_DATA, false);
7271
} catch (IOException e) {
7372
Log.e("AsyncAssetManager", "Failed to unpack critical components !");
7473
}
@@ -86,6 +85,8 @@ public static void unpackComponents(Context ctx){
8685
// we repack them to a single file here
8786
unpackComponent(ctx, "lwjgl3", false);
8887
unpackComponent(ctx, "security", true);
88+
unpackComponent(ctx, "arc_dns_injector", true);
89+
unpackComponent(ctx, "forge_installer", true);
8990
} catch (IOException e) {
9091
Log.e("AsyncAssetManager", "Failed o unpack components !",e );
9192
}

‎app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public static List<String> getJavaArgs(Context ctx, String runtimeHome, String u
352352
"-Dfml.earlyprogresswindow=false" //Forge 1.14+ workaround
353353
));
354354
if(LauncherPreferences.PREF_ARC_CAPES) {
355-
overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176");
355+
overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector/arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176");
356356
}
357357
List<String> additionalArguments = new ArrayList<>();
358358
for(String arg : overridableArguments) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent"
5+
android:orientation="vertical">
6+
<ExpandableListView
7+
android:id="@+id/forge_expandable_version_list"
8+
android:layout_width="match_parent"
9+
android:layout_height="0dp"
10+
android:layout_weight="1">
11+
</ExpandableListView>
12+
13+
<ProgressBar
14+
android:id="@+id/forge_list_progress_bar"
15+
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
16+
android:layout_width="match_parent"
17+
android:layout_height="wrap_content"
18+
android:indeterminate="true" />
19+
</LinearLayout>

‎app_pojavlauncher/src/main/res/values/strings.xml

+3
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,7 @@
372372
<string name="preference_deadzone_scale_description">Increase it if the joystick drifts</string>
373373
<string name="preference_force_big_core_title">Force renderer to run on the big core</string>
374374
<string name="preference_force_big_core_desc">Forces the Minecraft render thread to run on the core with the highest max frequency</string>
375+
<string name="forge_dl_progress">Downloading installer for %s</string>
376+
<string name="forge_dl_failed_to_load_list">Failed to load the version list</string>
377+
<string name="forge_dl_no_installer">Sorry, but this version of Forge does not have an installer, which is not yet supported.</string>
375378
</resources>

‎arc_dns_injector/build.gradle

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ jar {
1111
attributes("Manifest-Version": "1.0",
1212
"PreMain-Class": "git.artdeell.arcdns.ArcDNSInjectorAgent")
1313
}
14-
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/"))
14+
File versionFile = file("../app_pojavlauncher/src/main/assets/components/arc_dns_injector/version")
15+
versionFile.write(String.valueOf(new Date().getTime()))
16+
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/components/arc_dns_injector/"))
1517
}

‎forge_installer/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.gradle
2+
build/
3+
!gradle/wrapper/gradle-wrapper.jar
4+
!**/src/main/**/build/
5+
!**/src/test/**/build/
6+
7+
### IntelliJ IDEA ###
8+
.idea/modules.xml
9+
.idea/jarRepositories.xml
10+
.idea/compiler.xml
11+
.idea/libraries/
12+
*.iws
13+
*.iml
14+
*.ipr
15+
out/
16+
!**/src/main/**/out/
17+
!**/src/test/**/out/
18+
19+
### Eclipse ###
20+
.apt_generated
21+
.classpath
22+
.factorypath
23+
.project
24+
.settings
25+
.springBeans
26+
.sts4-cache
27+
bin/
28+
!**/src/main/**/bin/
29+
!**/src/test/**/bin/
30+
31+
### NetBeans ###
32+
/nbproject/private/
33+
/nbbuild/
34+
/dist/
35+
/nbdist/
36+
/.nb-gradle/
37+
38+
### VS Code ###
39+
.vscode/
40+
41+
### Mac OS ###
42+
.DS_Store

‎forge_installer/build.gradle

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
plugins {
2+
id 'java-library'
3+
}
4+
5+
java {
6+
sourceCompatibility = JavaVersion.VERSION_1_8
7+
targetCompatibility = JavaVersion.VERSION_1_8
8+
}
9+
10+
dependencies {
11+
implementation 'org.json:json:20230618'
12+
}
13+
14+
jar {
15+
from {
16+
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
17+
}
18+
File versionFile = file("../app_pojavlauncher/src/main/assets/components/forge_installer/version")
19+
versionFile.write(String.valueOf(new Date().getTime()))
20+
manifest {
21+
attributes("Manifest-Version": "1.0",
22+
"PreMain-Class": "git.artdeell.forgeinstaller.Agent")
23+
}
24+
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/components/forge_installer/"))
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package git.artdeell.forgeinstaller;
2+
3+
import java.awt.AWTEvent;
4+
import java.awt.Component;
5+
import java.awt.Container;
6+
import java.awt.EventQueue;
7+
import java.awt.Toolkit;
8+
import java.awt.Window;
9+
import java.awt.event.AWTEventListener;
10+
import java.awt.event.WindowEvent;
11+
import java.lang.instrument.Instrumentation;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import javax.swing.AbstractButton;
16+
import javax.swing.JDialog;
17+
import javax.swing.JOptionPane;
18+
19+
public class Agent implements AWTEventListener {
20+
private boolean forgeWindowHandled = false;
21+
private final boolean suppressProfileCreation;
22+
23+
public Agent(boolean ps) {
24+
this.suppressProfileCreation = ps;
25+
}
26+
27+
@Override
28+
public void eventDispatched(AWTEvent event) {
29+
WindowEvent windowEvent = (WindowEvent) event;
30+
Window window = windowEvent.getWindow();
31+
if(windowEvent.getID() == WindowEvent.WINDOW_OPENED) {
32+
if(!forgeWindowHandled) { // false at startup, so we will handle the first window as the Forge one
33+
handleForgeWindow(window);
34+
forgeWindowHandled = true;
35+
}else if(window instanceof JDialog) { // expecting a new dialog
36+
handleDialog(window);
37+
}
38+
}
39+
}
40+
41+
public void handleForgeWindow(Window window) {
42+
List<Component> components = new ArrayList<>();
43+
insertAllComponents(components, window, new MainWindowFilter());
44+
AbstractButton okButton = null;
45+
for(Component component : components) {
46+
if(component instanceof AbstractButton) {
47+
AbstractButton abstractButton = (AbstractButton) component;
48+
switch(abstractButton.getText()) {
49+
case "OK":
50+
okButton = abstractButton; // store the button, so we can press it after processing other stuff
51+
break;
52+
case "Install client":
53+
abstractButton.doClick(); // It should be the default, but let's make sure
54+
}
55+
56+
}
57+
}
58+
if(okButton == null) {
59+
System.out.println("Failed to set all the UI components.");
60+
System.exit(17);
61+
}else{
62+
ProfileFixer.storeProfile();
63+
EventQueue.invokeLater(okButton::doClick); // do that after forge actually builds its window, otherwise we set the path too fast
64+
}
65+
}
66+
67+
public void handleDialog(Window window) {
68+
List<Component> components = new ArrayList<>();
69+
insertAllComponents(components, window, new DialogFilter()); // ensure that it's a JOptionPane dialog
70+
if(components.size() == 1) {
71+
// another common trait of them - they only have one option pane in them,
72+
// so we can discard the rest of the dialog structure
73+
JOptionPane optionPane = (JOptionPane) components.get(0);
74+
if(optionPane.getMessageType() == JOptionPane.INFORMATION_MESSAGE) { // forge doesn't emit information messages for other reasons yet
75+
System.out.println("The install was successful!");
76+
ProfileFixer.reinsertProfile(suppressProfileCreation);
77+
System.exit(0); // again, forge doesn't call exit for some reason, so we do that ourselves here
78+
}
79+
}
80+
}
81+
82+
public void insertAllComponents(List<Component> components, Container parent, ComponentFilter filter) {
83+
int componentCount = parent.getComponentCount();
84+
for(int i = 0; i < componentCount; i++) {
85+
Component component = parent.getComponent(i);
86+
if(filter.checkComponent(component)) components.add(component);
87+
if(component instanceof Container) {
88+
insertAllComponents(components, (Container) component, filter);
89+
}
90+
}
91+
}
92+
93+
public static void premain(String args, Instrumentation inst) {
94+
Toolkit.getDefaultToolkit()
95+
.addAWTEventListener(new Agent(!"NPS".equals(args)), // No Profile Suppression
96+
AWTEvent.WINDOW_EVENT_MASK);
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package git.artdeell.forgeinstaller;
2+
3+
import java.awt.*;
4+
5+
public interface ComponentFilter {
6+
boolean checkComponent(Component component);
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package git.artdeell.forgeinstaller;
2+
3+
import javax.swing.*;
4+
import java.awt.*;
5+
6+
public class DialogFilter implements ComponentFilter{
7+
@Override
8+
public boolean checkComponent(Component component) {
9+
return component instanceof JOptionPane;
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package git.artdeell.forgeinstaller;
2+
3+
import javax.swing.*;
4+
import java.awt.*;
5+
6+
public class MainWindowFilter implements ComponentFilter{
7+
@Override
8+
public boolean checkComponent(Component component) {
9+
return component instanceof JRadioButton
10+
|| component instanceof JTextField
11+
|| component instanceof JButton;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package git.artdeell.forgeinstaller;
2+
3+
import org.json.JSONException;
4+
import org.json.JSONObject;
5+
6+
import java.io.IOException;
7+
import java.nio.charset.StandardCharsets;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.nio.file.Paths;
11+
import java.nio.file.StandardOpenOption;
12+
import java.util.Random;
13+
14+
public class ProfileFixer {
15+
private static final Random random = new Random();
16+
private static final Path profilesPath = Paths.get(System.getProperty("user.home"), ".minecraft", "launcher_profiles.json");
17+
private static JSONObject oldProfile = null;
18+
public static void storeProfile() {
19+
try {
20+
JSONObject minecraftProfiles = new JSONObject(
21+
new String(Files.readAllBytes(profilesPath),
22+
StandardCharsets.UTF_8)
23+
);
24+
JSONObject profilesArray = minecraftProfiles.getJSONObject("profiles");
25+
oldProfile = profilesArray.optJSONObject("forge", null);
26+
}catch (IOException | JSONException e) {
27+
System.out.println("Failed to store Forge profile: "+e);
28+
}
29+
}
30+
31+
private static String pickProfileName() {
32+
return "forge"+random.nextInt();
33+
}
34+
public static void reinsertProfile(boolean suppressProfileCreation) {
35+
try {
36+
JSONObject minecraftProfiles = new JSONObject(
37+
new String(Files.readAllBytes(profilesPath),
38+
StandardCharsets.UTF_8)
39+
);
40+
JSONObject profilesArray = minecraftProfiles.getJSONObject("profiles");
41+
if(oldProfile != null) {
42+
if(suppressProfileCreation) profilesArray.put("forge", oldProfile); // restore the old profile
43+
else {
44+
String name = pickProfileName();
45+
while(profilesArray.has(name)) name = pickProfileName();
46+
profilesArray.put(name, oldProfile); // restore the old profile under a new name
47+
}
48+
}else{
49+
if(suppressProfileCreation) profilesArray.remove("forge"); // remove the new profile
50+
// otherwise it wont be removed
51+
}
52+
minecraftProfiles.put("profiles", profilesArray);
53+
Files.write(profilesPath, minecraftProfiles.toString().getBytes(StandardCharsets.UTF_8),
54+
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
55+
}catch (IOException | JSONException e) {
56+
System.out.println("Failed to restore old Forge profile: "+e);
57+
}
58+
}
59+
}

‎settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ include ':jre_lwjgl3glfw'
1919
include ':app_pojavlauncher'
2020

2121
include ':arc_dns_injector'
22+
include ':forge_installer'

1 commit comments

Comments
 (1)

kirill609 commented on Nov 30, 2023

@kirill609

Lpapap

Please sign in to comment.