Merge pull request #4308 from PojavLauncherTeam/feat/automatic_modloader_install

Feature: Automatic Forge, Fabric and OptiFine installation
This commit is contained in:
ArtDev
2023-07-02 12:38:47 +03:00
committed by GitHub
74 changed files with 2382 additions and 157 deletions

View File

@@ -194,6 +194,8 @@ dependencies {
implementation 'org.tukaani:xz:1.8'
implementation 'com.github.PojavLauncherTeam:exp4j:60eaec6f78'
implementation 'net.sourceforge.htmlcleaner:htmlcleaner:2.6.1'
// implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])

View File

Binary file not shown.

View File

@@ -0,0 +1 @@
1687691695196

View File

@@ -0,0 +1 @@
1688133008591

View File

@@ -1 +1 @@
1678189863424
1687691695259

View File

@@ -1,14 +1,13 @@
package com.kdt.mcgui;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static net.kdt.pojavlaunch.fragments.ProfileEditorFragment.DELETED_PROFILE;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.transition.Slide;
import android.transition.Transition;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
@@ -19,15 +18,17 @@ import android.widget.PopupWindow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.FragmentActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.fragments.ProfileTypeSelectFragment;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.profiles.ProfileAdapter;
import net.kdt.pojavlaunch.fragments.ProfileEditorFragment;
import net.kdt.pojavlaunch.profiles.ProfileAdapterExtra;
import fr.spse.extended_view.ExtendedTextView;
@@ -36,6 +37,8 @@ import fr.spse.extended_view.ExtendedTextView;
* dropdown popup view with a custom direction.
*/
public class mcVersionSpinner extends ExtendedTextView {
private static final int VERSION_SPINNER_PROFILE_CREATE = 0;
private static final int VERSION_SPINNER_PROFILE_CREATE_MODDED = 1;
public mcVersionSpinner(@NonNull Context context) {
super(context);
init();
@@ -52,8 +55,12 @@ public class mcVersionSpinner extends ExtendedTextView {
/* The class is in charge of displaying its own list with adapter content being known in advance */
private ListView mListView = null;
private PopupWindow mPopupWindow = null;
private final ProfileAdapter mProfileAdapter = new ProfileAdapter(getContext(), true);
private int mSelectedProfilePosition;
private Object mPopupAnimation;
private final ProfileAdapter mProfileAdapter = new ProfileAdapter(getContext(), new ProfileAdapterExtra[]{
new ProfileAdapterExtra(VERSION_SPINNER_PROFILE_CREATE,
R.string.create_profile,
ResourcesCompat.getDrawable(getResources(), R.drawable.ic_add, null)),
});
/** Set the selection AND saves it as a shared preference */
@@ -63,11 +70,9 @@ public class mcVersionSpinner extends ExtendedTextView {
.putString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,
mProfileAdapter.getItem(position).toString())
.apply();
if(mPopupWindow != null) mPopupWindow.dismiss();
}
public void setSelection(int position){
mSelectedProfilePosition = position;
if(mListView != null) mListView.setSelection(position);
mProfileAdapter.setViewProfile(this, (String) mProfileAdapter.getItem(position), false);
}
@@ -81,6 +86,7 @@ public class mcVersionSpinner extends ExtendedTextView {
int endPadding = getContext().getResources().getDimensionPixelOffset(R.dimen._5sdp);
setPaddingRelative(startPadding, 0, endPadding, 0);
setCompoundDrawablePadding(startPadding);
int profileIndex;
String extra_value = (String) ExtraCore.consumeValue(ExtraConstants.REFRESH_VERSION_SPINNER);
if(extra_value != null){
@@ -115,13 +121,20 @@ public class mcVersionSpinner extends ExtendedTextView {
mListView = (ListView) inflate(getContext(), R.layout.spinner_mc_version, null);
mListView.setAdapter(mProfileAdapter);
mListView.setOnItemClickListener((parent, view, position, id) -> {
if(position == mProfileAdapter.getCount() - 1){
mPopupWindow.dismiss();
Tools.swapFragment((FragmentActivity) getContext(), ProfileEditorFragment.class,
ProfileEditorFragment.TAG, true, new Bundle(1));
return;
Object item = mProfileAdapter.getItem(position);
if(item instanceof String) {
hidePopup(true);
setProfileSelection(position);
}else if(item instanceof ProfileAdapterExtra) {
hidePopup(false);
ProfileAdapterExtra extra = (ProfileAdapterExtra) item;
switch (extra.id) {
case VERSION_SPINNER_PROFILE_CREATE:
Tools.swapFragment((FragmentActivity) getContext(), ProfileTypeSelectFragment.class,
ProfileTypeSelectFragment.TAG, true, null);
break;
}
}
setProfileSelection(position);
});
mPopupWindow = new PopupWindow(mListView, MATCH_PARENT, getContext().getResources().getDimensionPixelOffset(R.dimen._184sdp));
@@ -142,14 +155,26 @@ public class mcVersionSpinner extends ExtendedTextView {
// Custom animation, nice slide in
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
Slide transition = new Slide(Gravity.BOTTOM);
mPopupWindow.setEnterTransition(transition);
mPopupWindow.setExitTransition(transition);
mPopupAnimation = new Slide(Gravity.BOTTOM);
mPopupWindow.setEnterTransition((Transition) mPopupAnimation);
mPopupWindow.setExitTransition((Transition) mPopupAnimation);
}
}
private void hidePopup(boolean animate) {
if(mPopupWindow == null) return;
if(!animate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPopupWindow.setEnterTransition(null);
mPopupWindow.setExitTransition(null);
mPopupWindow.dismiss();
mPopupWindow.setEnterTransition((Transition) mPopupAnimation);
mPopupWindow.setExitTransition((Transition) mPopupAnimation);
}else {
mPopupWindow.dismiss();
}
}
public ProfileAdapter getProfileAdapter() {
return mProfileAdapter;
}
}

View File

@@ -4,8 +4,6 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -89,7 +87,7 @@ public class ImportControlActivity extends Activity {
}).start();
//Auto show the keyboard
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Tools.MAIN_HANDLER.postDelayed(() -> {
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
mEditText.setSelection(mEditText.getText().length());

View File

@@ -28,6 +28,7 @@ import net.kdt.pojavlaunch.utils.MathUtils;
import org.lwjgl.glfw.CallbackBridge;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -55,7 +56,14 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java_gui_launcher);
Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath());
try {
File latestLogFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt");
if (!latestLogFile.exists() && !latestLogFile.createNewFile())
throw new IOException("Failed to create a new log file");
Logger.begin(latestLogFile.getAbsolutePath());
}catch (IOException e) {
Tools.showError(this, e, true);
}
mTouchCharInput = findViewById(R.id.awt_touch_char);
mTouchCharInput.setCharacterSender(new AwtCharSender());
@@ -153,6 +161,7 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
final Runtime runtime = MultiRTUtils.forceReread(jreName);
mSkipDetectMod = getIntent().getExtras().getBoolean("skipDetectMod", false);
if(getIntent().getExtras().getBoolean("openLogOutput", false)) openLogOutput(null);
if (mSkipDetectMod) {
new Thread(() -> launchJavaRuntime(runtime, modFile, javaArgs), "JREMainThread").start();
return;

View File

@@ -165,7 +165,7 @@ public class LauncherActivity extends BaseActivity {
ExtraCore.addExtraListener(ExtraConstants.LAUNCH_GAME, mLaunchGameListener);
new AsyncVersionList().getVersionList(versions -> ExtraCore.setValue(ExtraConstants.RELEASE_TABLE, versions));
new AsyncVersionList().getVersionList(versions -> ExtraCore.setValue(ExtraConstants.RELEASE_TABLE, versions), false);
mProgressLayout.observe(ProgressLayout.DOWNLOAD_MINECRAFT);
mProgressLayout.observe(ProgressLayout.UNPACK_RUNTIME);

View File

@@ -21,8 +21,6 @@ import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.DocumentsContract;
import android.util.Log;
import android.view.KeyEvent;
@@ -133,7 +131,10 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
try {
Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath());
File latestLogFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt");
if(!latestLogFile.exists() && !latestLogFile.createNewFile())
throw new IOException("Failed to create a new log file");
Logger.begin(latestLogFile.getAbsolutePath());
// FIXME: is it safe for multi thread?
GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
touchCharInput.setCharacterSender(new LwjglCharSender());
@@ -287,7 +288,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
protected void onPostResume() {
super.onPostResume();
if(minecraftGLView != null) // Useful when backing out of the app
new Handler(Looper.getMainLooper()).postDelayed(() -> minecraftGLView.refreshSize(), 500);
Tools.MAIN_HANDLER.postDelayed(() -> minecraftGLView.refreshSize(), 500);
}
@Override

View File

@@ -60,6 +60,7 @@ public class PojavApplication extends Application {
Tools.APP_NAME = getResources().getString(R.string.app_short_name);
Tools.DIR_DATA = getDir("files", MODE_PRIVATE).getParent();
Tools.DIR_CACHE = getCacheDir();
Tools.DIR_ACCOUNT_NEW = Tools.DIR_DATA + "/accounts";
Tools.DEVICE_ARCHITECTURE = Architecture.getDeviceArchitecture();
//Force x86 lib directory for Asus x86 based zenfones

View File

@@ -22,6 +22,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import android.util.ArrayMap;
@@ -77,14 +79,15 @@ import java.util.Map;
@SuppressWarnings("IOStreamConstructor")
public final class Tools {
public static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
public static String APP_NAME = "null";
public static final Gson GLOBAL_GSON = new GsonBuilder().setPrettyPrinting().create();
public static final String URL_HOME = "https://pojavlauncherteam.github.io";
public static String NATIVE_LIB_DIR;
public static String DIR_DATA; //Initialized later to get context
public static File DIR_CACHE;
public static String MULTIRT_HOME;
public static String LOCAL_RENDERER = null;
public static int DEVICE_ARCHITECTURE;
@@ -136,6 +139,7 @@ public final class Tools {
* Any value (in)directly dependant on DIR_DATA should be set only here.
*/
public static void initContextConstants(Context ctx){
DIR_CACHE = ctx.getCacheDir();
DIR_DATA = ctx.getFilesDir().getParent();
MULTIRT_HOME = DIR_DATA+"/runtimes";
DIR_GAME_HOME = getPojavStorageRoot(ctx).getAbsolutePath();
@@ -590,11 +594,15 @@ public final class Tools {
}
public static void dialogOnUiThread(final Activity activity, final CharSequence title, final CharSequence message) {
activity.runOnUiThread(() -> new AlertDialog.Builder(activity)
activity.runOnUiThread(()->dialog(activity, title, message));
}
public static void dialog(final Context context, final CharSequence title, final CharSequence message) {
new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show());
.show();
}
public static void openURL(Activity act, String url) {
@@ -890,7 +898,7 @@ public final class Tools {
sExecutorService.execute(() -> {
try {
final String name = getFileName(activity, uri);
final File modInstallerFile = new File(activity.getCacheDir(), name);
final File modInstallerFile = new File(Tools.DIR_CACHE, name);
FileOutputStream fos = new FileOutputStream(modInstallerFile);
InputStream input = activity.getContentResolver().openInputStream(uri);
IOUtils.copy(input, fos);
@@ -955,6 +963,10 @@ public final class Tools {
return runtime;
}
public static void runOnUiThread(Runnable runnable) {
MAIN_HANDLER.post(runnable);
}
public static @NonNull String pickRuntime(MinecraftProfile minecraftProfile, int targetJavaVersion) {
String runtime = getSelectedRuntime(minecraftProfile);
String profileRuntime = getRuntimeName(minecraftProfile.javaDir);

View File

@@ -2,7 +2,6 @@ package net.kdt.pojavlaunch.authenticator.microsoft;
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
import android.os.Looper;
import android.util.ArrayMap;
import android.util.Log;
@@ -13,8 +12,10 @@ import com.kdt.mcgui.ProgressLayout;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.authenticator.listener.DoneListener;
import net.kdt.pojavlaunch.authenticator.listener.ErrorListener;
import net.kdt.pojavlaunch.authenticator.listener.ProgressListener;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import net.kdt.pojavlaunch.authenticator.listener.*;
import org.json.JSONArray;
import org.json.JSONException;
@@ -42,7 +43,6 @@ public class MicrosoftBackgroundLogin {
private final boolean mIsRefresh;
private final String mAuthCode;
private final android.os.Handler mHandler = new android.os.Handler(Looper.getMainLooper());
private static final Map<Long, Integer> XSTS_ERRORS;
static {
XSTS_ERRORS = new ArrayMap<>();
@@ -100,13 +100,13 @@ public class MicrosoftBackgroundLogin {
if(doneListener != null) {
MinecraftAccount finalAcc = acc;
mHandler.post(() -> doneListener.onLoginDone(finalAcc));
Tools.runOnUiThread(() -> doneListener.onLoginDone(finalAcc));
}
}catch (Exception e){
Log.e("MicroAuth", e.toString());
if(errorListener != null)
mHandler.post(() -> errorListener.onLoginError(e));
Tools.runOnUiThread(() -> errorListener.onLoginError(e));
}
ProgressLayout.clearProgress(ProgressLayout.AUTHENTICATE_MICROSOFT);
});
@@ -289,7 +289,7 @@ public class MicrosoftBackgroundLogin {
/** Wrapper to ease notifying the listener */
private void notifyProgress(@Nullable ProgressListener listener, int step){
if(listener != null){
mHandler.post(() -> listener.onLoginProgress(step));
Tools.runOnUiThread(() -> listener.onLoginProgress(step));
}
ProgressLayout.setProgress(ProgressLayout.AUTHENTICATE_MICROSOFT, step*20);
}

View File

@@ -0,0 +1,195 @@
package net.kdt.pojavlaunch.fragments;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import net.kdt.pojavlaunch.JavaGUILauncherActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.modloaders.FabricDownloadTask;
import net.kdt.pojavlaunch.modloaders.FabricUtils;
import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener;
import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy;
import net.kdt.pojavlaunch.profiles.VersionSelectorDialog;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class FabricInstallFragment extends Fragment implements AdapterView.OnItemSelectedListener, ModloaderDownloadListener, Runnable {
public static final String TAG = "FabricInstallTarget";
private static ModloaderListenerProxy sTaskProxy;
private TextView mSelectedVersionLabel;
private String mSelectedLoaderVersion;
private Spinner mLoaderVersionSpinner;
private String mSelectedGameVersion;
private boolean mSelectedSnapshot;
private ProgressBar mProgressBar;
private File mDestinationDir;
private Button mStartButton;
private View mRetryView;
public FabricInstallFragment() {
super(R.layout.fragment_fabric_install);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.mDestinationDir = new File(Tools.DIR_CACHE, "fabric-installer");
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStartButton = view.findViewById(R.id.fabric_installer_start_button);
mStartButton.setOnClickListener(this::onClickStart);
mSelectedVersionLabel = view.findViewById(R.id.fabric_installer_version_select_label);
view.findViewById(R.id.fabric_installer_game_version_change).setOnClickListener(this::onClickSelect);
mLoaderVersionSpinner = view.findViewById(R.id.fabric_installer_loader_ver_spinner);
mLoaderVersionSpinner.setOnItemSelectedListener(this);
mProgressBar = view.findViewById(R.id.fabric_installer_progress_bar);
mRetryView = view.findViewById(R.id.fabric_installer_retry_layout);
view.findViewById(R.id.fabric_installer_retry_button).setOnClickListener(this::onClickRetry);
if(sTaskProxy != null) {
mStartButton.setEnabled(false);
sTaskProxy.attachListener(this);
}
new Thread(this).start();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if(sTaskProxy != null) {
sTaskProxy.detachListener();
}
}
private void onClickStart(View v) {
if(ProgressKeeper.hasOngoingTasks()) {
Toast.makeText(v.getContext(), R.string.tasks_ongoing, Toast.LENGTH_LONG).show();
return;
}
sTaskProxy = new ModloaderListenerProxy();
FabricDownloadTask fabricDownloadTask = new FabricDownloadTask(sTaskProxy, mDestinationDir);
sTaskProxy.attachListener(this);
mStartButton.setEnabled(false);
new Thread(fabricDownloadTask).start();
}
private void onClickSelect(View v) {
VersionSelectorDialog.open(v.getContext(), true, (id, snapshot)->{
mSelectedGameVersion = id;
mSelectedVersionLabel.setText(mSelectedGameVersion);
mSelectedSnapshot = snapshot;
if(mSelectedLoaderVersion != null && sTaskProxy == null) mStartButton.setEnabled(true);
});
}
private void onClickRetry(View v) {
mLoaderVersionSpinner.setAdapter(null);
mStartButton.setEnabled(false);
mProgressBar.setVisibility(View.VISIBLE);
mRetryView.setVisibility(View.GONE);
new Thread(this).start();
}
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
Adapter adapter = adapterView.getAdapter();
mSelectedLoaderVersion = (String) adapter.getItem(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
mSelectedLoaderVersion = null;
}
@Override
public void onDownloadFinished(File downloadedFile) {
Tools.runOnUiThread(()->{
Context context = requireContext();
sTaskProxy.detachListener();
sTaskProxy = null;
mStartButton.setEnabled(true);
// This works because the due to the fact that we have transitioned here
// without adding a transaction to the back stack, which caused the previous
// transaction to be amended (i guess?? thats how the back stack dump looks like)
// we can get back to the main fragment with just one back stack pop.
// For some reason that amendment causes the transaction to lose its tag
// so we cant use the tag here.
getParentFragmentManager().popBackStackImmediate();
Intent intent = new Intent(context, JavaGUILauncherActivity.class);
FabricUtils.addAutoInstallArgs(intent, downloadedFile, mSelectedGameVersion, mSelectedLoaderVersion, mSelectedSnapshot, true);
context.startActivity(intent);
});
}
@Override
public void onDataNotAvailable() {
Tools.runOnUiThread(()->{
Context context = requireContext();
sTaskProxy.detachListener();
sTaskProxy = null;
mStartButton.setEnabled(true);
Tools.dialog(context,
context.getString(R.string.global_error),
context.getString(R.string.fabric_dl_cant_read_meta));
});
}
@Override
public void onDownloadError(Exception e) {
Tools.runOnUiThread(()-> {
Context context = requireContext();
sTaskProxy.detachListener();
sTaskProxy = null;
mStartButton.setEnabled(true);
Tools.showError(context, e);
});
}
@Override
public void run() {
try {
List<String> mLoaderVersions = FabricUtils.downloadLoaderVersionList(false);
if (mLoaderVersions != null) {
Tools.runOnUiThread(()->{
Context context = getContext();
if(context == null) return;
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(context, R.layout.support_simple_spinner_dropdown_item, mLoaderVersions);
mLoaderVersionSpinner.setAdapter(arrayAdapter);
mProgressBar.setVisibility(View.GONE);
});
}else{
Tools.runOnUiThread(()-> {
mRetryView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
});
}
}catch (IOException e) {
Tools.runOnUiThread(()-> {
if(getContext() != null) Tools.showError(getContext(), e);
mRetryView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
});
}
}
}

View File

@@ -0,0 +1,71 @@
package net.kdt.pojavlaunch.fragments;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.widget.ExpandableListAdapter;
import androidx.annotation.NonNull;
import net.kdt.pojavlaunch.JavaGUILauncherActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.modloaders.ForgeDownloadTask;
import net.kdt.pojavlaunch.modloaders.ForgeUtils;
import net.kdt.pojavlaunch.modloaders.ForgeVersionListAdapter;
import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class ForgeInstallFragment extends ModVersionListFragment<List<String>> {
public static final String TAG = "ForgeInstallFragment";
private static ModloaderListenerProxy sTaskProxy;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}
@Override
public int getTitleText() {
return R.string.forge_dl_select_version;
}
@Override
public int getNoDataMsg() {
return R.string.forge_dl_no_installer;
}
@Override
public ModloaderListenerProxy getTaskProxy() {
return sTaskProxy;
}
@Override
public void setTaskProxy(ModloaderListenerProxy proxy) {
sTaskProxy = proxy;
}
@Override
public List<String> loadVersionList() throws IOException {
return ForgeUtils.downloadForgeVersions();
}
@Override
public ExpandableListAdapter createAdapter(List<String> versionList, LayoutInflater layoutInflater) {
return new ForgeVersionListAdapter(versionList, layoutInflater);
}
@Override
public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) {
return new ForgeDownloadTask(listenerProxy, (String) selectedVersion, new File(Tools.DIR_CACHE, "forge-installer.jar"));
}
@Override
public void onDownloadFinished(Context context, File downloadedFile) {
Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class);
ForgeUtils.addAutoInstallArgs(modInstallerStartIntent, downloadedFile, true);
context.startActivity(modInstallerStartIntent);
}
}

View File

@@ -3,9 +3,7 @@ package net.kdt.pojavlaunch.fragments;
import static net.kdt.pojavlaunch.Tools.shareLog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
@@ -15,7 +13,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import net.kdt.pojavlaunch.CustomControlsActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
@@ -52,6 +49,11 @@ public class MainMenuFragment extends Fragment {
mPlayButton.setOnClickListener(v -> ExtraCore.setValue(ExtraConstants.LAUNCH_GAME, true));
mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext()));
mNewsButton.setOnLongClickListener((v)->{
Tools.swapFragment(requireActivity(), FabricInstallFragment.class, FabricInstallFragment.TAG, true, null);
return true;
});
}
private void runInstallerWithConfirmation(boolean isCustomArgs) {
if (ProgressKeeper.getTaskCount() == 0)

View File

@@ -0,0 +1,158 @@
package net.kdt.pojavlaunch.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener;
import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import java.io.File;
import java.io.IOException;
public abstract class ModVersionListFragment<T> extends Fragment implements Runnable, View.OnClickListener, ExpandableListView.OnChildClickListener, ModloaderDownloadListener {
public static final String TAG = "ForgeInstallFragment";
private ExpandableListView mExpandableListView;
private ProgressBar mProgressBar;
private LayoutInflater mInflater;
private View mRetryView;
public ModVersionListFragment() {
super(R.layout.fragment_mod_version_list);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.mInflater = LayoutInflater.from(context);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
((TextView)view.findViewById(R.id.title_textview)).setText(getTitleText());
mProgressBar = view.findViewById(R.id.mod_dl_list_progress);
mExpandableListView = view.findViewById(R.id.mod_dl_expandable_version_list);
mExpandableListView.setOnChildClickListener(this);
mRetryView = view.findViewById(R.id.mod_dl_retry_layout);
view.findViewById(R.id.forge_installer_retry_button).setOnClickListener(this);
ModloaderListenerProxy taskProxy = getTaskProxy();
if(taskProxy != null) {
mExpandableListView.setEnabled(false);
taskProxy.attachListener(this);
}
new Thread(this).start();
}
@Override
public void onDestroyView() {
ModloaderListenerProxy taskProxy = getTaskProxy();
if(taskProxy != null) taskProxy.detachListener();
super.onDestroyView();
}
@Override
public void run() {
try {
T versions = loadVersionList();
Tools.runOnUiThread(()->{
if(versions != null) {
mExpandableListView.setAdapter(createAdapter(versions, mInflater));
}else{
mRetryView.setVisibility(View.VISIBLE);
}
mProgressBar.setVisibility(View.GONE);
});
}catch (IOException e) {
Tools.runOnUiThread(()-> {
if (getContext() != null) {
Tools.showError(getContext(), e);
mRetryView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
}
});
}
}
@Override
public void onClick(View view) {
mRetryView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
new Thread(this).start();
}
@Override
public boolean onChildClick(ExpandableListView expandableListView, View view, int i, int i1, long l) {
if(ProgressKeeper.hasOngoingTasks()) {
Toast.makeText(expandableListView.getContext(), R.string.tasks_ongoing, Toast.LENGTH_LONG).show();
return true;
}
Object forgeVersion = expandableListView.getExpandableListAdapter().getChild(i, i1);
ModloaderListenerProxy taskProxy = new ModloaderListenerProxy();
Runnable downloadTask = createDownloadTask(forgeVersion, taskProxy);
setTaskProxy(taskProxy);
taskProxy.attachListener(this);
mExpandableListView.setEnabled(false);
new Thread(downloadTask).start();
return true;
}
@Override
public void onDownloadFinished(File downloadedFile) {
Tools.runOnUiThread(()->{
Context context = requireContext();
getTaskProxy().detachListener();
setTaskProxy(null);
mExpandableListView.setEnabled(true);
// Read the comment in FabricInstallFragment.onDownloadFinished() to see how this works
getParentFragmentManager().popBackStack();
onDownloadFinished(context, downloadedFile);
});
}
@Override
public void onDataNotAvailable() {
Tools.runOnUiThread(()->{
Context context = requireContext();
getTaskProxy().detachListener();
setTaskProxy(null);
mExpandableListView.setEnabled(true);
Tools.dialog(context,
context.getString(R.string.global_error),
context.getString(getNoDataMsg()));
});
}
@Override
public void onDownloadError(Exception e) {
Tools.runOnUiThread(()->{
Context context = requireContext();
getTaskProxy().detachListener();
setTaskProxy(null);
mExpandableListView.setEnabled(true);
Tools.showError(context, e);
});
}
public abstract int getTitleText();
public abstract int getNoDataMsg();
public abstract ModloaderListenerProxy getTaskProxy();
public abstract T loadVersionList() throws IOException;
public abstract void setTaskProxy(ModloaderListenerProxy proxy);
public abstract ExpandableListAdapter createAdapter(T versionList, LayoutInflater layoutInflater);
public abstract Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy);
public abstract void onDownloadFinished(Context context, File downloadedFile);
}

View File

@@ -0,0 +1,64 @@
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;
import net.kdt.pojavlaunch.modloaders.OptiFineDownloadTask;
import net.kdt.pojavlaunch.modloaders.OptiFineUtils;
import net.kdt.pojavlaunch.modloaders.OptiFineVersionListAdapter;
import java.io.File;
import java.io.IOException;
public class OptiFineInstallFragment extends ModVersionListFragment<OptiFineUtils.OptiFineVersions> {
public static final String TAG = "OptiFineInstallFragment";
private static ModloaderListenerProxy sTaskProxy;
@Override
public int getTitleText() {
return R.string.of_dl_select_version;
}
@Override
public int getNoDataMsg() {
return R.string.of_dl_failed_to_scrape;
}
@Override
public ModloaderListenerProxy getTaskProxy() {
return sTaskProxy;
}
@Override
public OptiFineUtils.OptiFineVersions loadVersionList() throws IOException {
return OptiFineUtils.downloadOptiFineVersions();
}
@Override
public void setTaskProxy(ModloaderListenerProxy proxy) {
sTaskProxy = proxy;
}
@Override
public ExpandableListAdapter createAdapter(OptiFineUtils.OptiFineVersions versionList, LayoutInflater layoutInflater) {
return new OptiFineVersionListAdapter(versionList, layoutInflater);
}
@Override
public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) {
return new OptiFineDownloadTask((OptiFineUtils.OptiFineVersion) selectedVersion,
new File(Tools.DIR_CACHE, "optifine-installer.jar"), listenerProxy);
}
@Override
public void onDownloadFinished(Context context, File downloadedFile) {
Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class);
OptiFineUtils.addAutoInstallArgs(modInstallerStartIntent, downloadedFile);
context.startActivity(modInstallerStartIntent);
}
}

View File

@@ -1,8 +1,5 @@
package net.kdt.pojavlaunch.fragments;
import static net.kdt.pojavlaunch.extra.ExtraCore.getValue;
import static net.kdt.pojavlaunch.profiles.ProfileAdapter.CREATE_PROFILE_MAGIC;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -11,17 +8,13 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.extra.ExtraConstants;
@@ -30,7 +23,7 @@ import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.profiles.VersionListAdapter;
import net.kdt.pojavlaunch.profiles.VersionSelectorDialog;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
@@ -117,28 +110,10 @@ public class ProfileEditorFragment extends Fragment {
});
// Setup the expendable list behavior
mVersionSelectButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(mDefaultVersion.getContext());
ExpandableListView expandableListView = (ExpandableListView) LayoutInflater.from(mDefaultVersion.getContext())
.inflate(R.layout.dialog_expendable_list_view , null);
JMinecraftVersionList jMinecraftVersionList = (JMinecraftVersionList) getValue(ExtraConstants.RELEASE_TABLE);
JMinecraftVersionList.Version[] versionArray;
if(jMinecraftVersionList == null || jMinecraftVersionList.versions == null) versionArray = new JMinecraftVersionList.Version[0];
else versionArray = jMinecraftVersionList.versions;
ExpandableListAdapter adapter = new VersionListAdapter(versionArray, mDefaultVersion.getContext());
expandableListView.setAdapter(adapter);
builder.setView(expandableListView);
AlertDialog dialog = builder.show();
expandableListView.setOnChildClickListener((parent, v1, groupPosition, childPosition, id) -> {
String version = ((String) adapter.getChild(groupPosition, childPosition));
mTempProfile.lastVersionId = version;
mDefaultVersion.setText(version);
dialog.dismiss();
return true;
});
});
mVersionSelectButton.setOnClickListener(v -> VersionSelectorDialog.open(v.getContext(), false, (id, snapshot)->{
mTempProfile.lastVersionId = id;
mDefaultVersion.setText(id);
}));
@@ -180,7 +155,7 @@ public class ProfileEditorFragment extends Fragment {
private MinecraftProfile getProfile(@NonNull String profile){
MinecraftProfile minecraftProfile;
if(getArguments() == null && !profile.equals(CREATE_PROFILE_MAGIC)) {
if(getArguments() == null) {
minecraftProfile = new MinecraftProfile(LauncherProfiles.mainProfileJson.profiles.get(profile));
mProfileKey = profile;
}else{

View File

@@ -0,0 +1,35 @@
package net.kdt.pojavlaunch.fragments;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
public class ProfileTypeSelectFragment extends Fragment {
public static final String TAG = "ProfileTypeSelectFragment";
public ProfileTypeSelectFragment() {
super(R.layout.fragment_profile_type);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.vanilla_profile).setOnClickListener(v -> Tools.swapFragment(requireActivity(), ProfileEditorFragment.class,
ProfileEditorFragment.TAG, false, new Bundle(1)));
// NOTE: Special care needed! If you wll decide to add these to the back stack, please read
// the comment in FabricInstallFragment.onDownloadFinished() and amend the code
// in FabricInstallFragment.onDownloadFinished() and ModVersionListFragment.onDownloadFinished()
view.findViewById(R.id.optifine_profile).setOnClickListener(v -> Tools.swapFragment(requireActivity(), OptiFineInstallFragment.class,
OptiFineInstallFragment.TAG, false, null));
view.findViewById(R.id.modded_profile_fabric).setOnClickListener((v)->
Tools.swapFragment(requireActivity(), FabricInstallFragment.class, FabricInstallFragment.TAG, false, null));
view.findViewById(R.id.modded_profile_forge).setOnClickListener((v)->
Tools.swapFragment(requireActivity(), ForgeInstallFragment.class, ForgeInstallFragment.TAG, false, null));
}
}

View File

@@ -0,0 +1,77 @@
package net.kdt.pojavlaunch.modloaders;
import com.kdt.mcgui.ProgressLayout;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import java.io.File;
import java.io.IOException;
public class FabricDownloadTask implements Runnable, Tools.DownloaderFeedback{
private final File mDestinationDir;
private final File mDestinationFile;
private final ModloaderDownloadListener mModloaderDownloadListener;
public FabricDownloadTask(ModloaderDownloadListener modloaderDownloadListener, File mDestinationDir) {
this.mModloaderDownloadListener = modloaderDownloadListener;
this.mDestinationDir = mDestinationDir;
this.mDestinationFile = new File(mDestinationDir, "fabric-installer.jar");
}
@Override
public void run() {
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.fabric_dl_progress);
try {
if(runCatching()) mModloaderDownloadListener.onDownloadFinished(mDestinationFile);
}catch (IOException e) {
mModloaderDownloadListener.onDownloadError(e);
}
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, -1, -1);
}
private boolean runCatching() throws IOException {
if(!mDestinationDir.exists() && !mDestinationDir.mkdirs()) throw new IOException("Failed to create cache directory");
String[] urlAndVersion = FabricUtils.getInstallerUrlAndVersion();
if(urlAndVersion == null) {
mModloaderDownloadListener.onDataNotAvailable();
return false;
}
File versionFile = new File(mDestinationDir, "fabric-installer-version");
boolean shouldDownloadInstaller = true;
if(urlAndVersion[1] != null && versionFile.canRead()) { // if we know the latest version that we have and the server has
try {
shouldDownloadInstaller = !urlAndVersion[1].equals(Tools.read(versionFile.getAbsolutePath()));
}catch (IOException e) {
e.printStackTrace();
}
}
if(shouldDownloadInstaller) {
if (urlAndVersion[0] != null) {
byte[] buffer = new byte[8192];
DownloadUtils.downloadFileMonitored(urlAndVersion[0], mDestinationFile, buffer, this);
if(urlAndVersion[1] != null) {
try {
Tools.write(versionFile.getAbsolutePath(), urlAndVersion[1]);
}catch (IOException e) {
e.printStackTrace();
}
}
return true;
} else {
mModloaderDownloadListener.onDataNotAvailable();
return false;
}
}else{
return true;
}
}
@Override
public void updateProgress(int curr, int max) {
int progress100 = (int)(((float)curr / (float)max)*100f);
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.fabric_dl_progress);
}
}

View File

@@ -0,0 +1,8 @@
package net.kdt.pojavlaunch.modloaders;
import org.json.JSONException;
import org.json.JSONObject;
public interface FabricMetaReader {
boolean processMetadata(JSONObject jsonObject) throws JSONException;
}

View File

@@ -0,0 +1,83 @@
package net.kdt.pojavlaunch.modloaders;
import android.content.Intent;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class FabricUtils {
private static final String FABRIC_INSTALLER_METADATA_URL = "https://meta.fabricmc.net/v2/versions/installer";
private static final String FABRIC_LOADER_METADATA_URL = "https://meta.fabricmc.net/v2/versions/loader";
public static List<String> downloadLoaderVersionList(boolean onlyStable) throws IOException {
try {
return DownloadUtils.downloadStringCached(FABRIC_LOADER_METADATA_URL,
"fabric_loader_versions", (input)->{
final List<String> loaderList = new ArrayList<>();
try {
enumerateMetadata(input, (object) -> {
if (onlyStable && !object.getBoolean("stable")) return false;
loaderList.add(object.getString("version"));
return false;
});
}catch (JSONException e) {
throw new DownloadUtils.ParseException(e);
}
return loaderList;
});
}catch (DownloadUtils.ParseException e) {
e.printStackTrace();
return null;
}
}
public static String[] getInstallerUrlAndVersion() throws IOException{
String installerMetadata = DownloadUtils.downloadString(FABRIC_INSTALLER_METADATA_URL);
try {
return DownloadUtils.downloadStringCached(FABRIC_INSTALLER_METADATA_URL,
"fabric_installer_versions", input -> {
try {
JSONObject selectedMetadata = enumerateMetadata(installerMetadata, (object) ->
object.getBoolean("stable"));
if (selectedMetadata == null) return null;
return new String[]{selectedMetadata.getString("url"),
selectedMetadata.getString("version")};
} catch (JSONException e) {
throw new DownloadUtils.ParseException(e);
}
});
}catch (DownloadUtils.ParseException e) {
e.printStackTrace();
return null;
}
}
public static void addAutoInstallArgs(Intent intent, File modInstalllerJar,
String gameVersion, String loaderVersion,
boolean isSnapshot, boolean createProfile) {
intent.putExtra("javaArgs", "-jar " + modInstalllerJar.getAbsolutePath() + " client -dir "+ Tools.DIR_GAME_NEW
+ " -mcversion "+gameVersion +" -loader "+loaderVersion +
(isSnapshot ? " -snapshot" : "") +
(createProfile ? "" : " -noprofile"));
intent.putExtra("openLogOutput", true);
}
private static JSONObject enumerateMetadata(String inputMetadata, FabricMetaReader metaReader) throws JSONException{
JSONArray fullMetadata = new JSONArray(inputMetadata);
JSONObject metadataObject = null;
for(int i = 0; i < fullMetadata.length(); i++) {
metadataObject = fullMetadata.getJSONObject(i);
if(metaReader.processMetadata(metadataObject)) return metadataObject;
}
return metadataObject;
}
}

View File

@@ -0,0 +1,48 @@
package net.kdt.pojavlaunch.modloaders;
import com.kdt.mcgui.ProgressLayout;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ForgeDownloadTask implements Runnable, Tools.DownloaderFeedback {
private final String mForgeUrl;
private final String mForgeVersion;
private final File mDestinationFile;
private final ModloaderDownloadListener mListener;
public ForgeDownloadTask(ModloaderDownloadListener listener, String forgeVersion, File destinationFile) {
this.mListener = listener;
this.mForgeUrl = ForgeUtils.getInstallerUrl(forgeVersion);
this.mForgeVersion = forgeVersion;
this.mDestinationFile = destinationFile;
}
@Override
public void run() {
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_progress, mForgeVersion);
try {
byte[] buffer = new byte[8192];
DownloadUtils.downloadFileMonitored(mForgeUrl, mDestinationFile, buffer, this);
mListener.onDownloadFinished(mDestinationFile);
}catch (IOException e) {
if(e instanceof FileNotFoundException) {
mListener.onDataNotAvailable();
}else{
mListener.onDownloadError(e);
}
}
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, -1, -1);
}
@Override
public void updateProgress(int curr, int max) {
int progress100 = (int)(((float)curr / (float)max)*100f);
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.forge_dl_progress, mForgeVersion);
}
}

View File

@@ -0,0 +1,62 @@
package net.kdt.pojavlaunch.modloaders;
import android.content.Intent;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class ForgeUtils {
private static final String FORGE_METADATA_URL = "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml";
private static final String FORGE_INSTALLER_URL = "https://maven.minecraftforge.net/net/minecraftforge/forge/%1$s/forge-%1$s-installer.jar";
public static List<String> downloadForgeVersions() throws IOException {
SAXParser saxParser;
try {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
saxParser = parserFactory.newSAXParser();
}catch (SAXException | ParserConfigurationException e) {
e.printStackTrace();
// if we cant make a parser we might as well not even try to parse anything
return null;
}
try {
//of_test();
return DownloadUtils.downloadStringCached(FORGE_METADATA_URL, "forge_versions", input -> {
try {
ForgeVersionListHandler handler = new ForgeVersionListHandler();
saxParser.parse(new InputSource(new StringReader(input)), handler);
return handler.getVersions();
// IOException is present here StringReader throws it only if the parser called close()
// sooner than needed, which is a parser issue and not an I/O one
}catch (SAXException | IOException e) {
throw new DownloadUtils.ParseException(e);
}
});
}catch (DownloadUtils.ParseException e) {
e.printStackTrace();
return null;
}
}
public static String getInstallerUrl(String version) {
return String.format(FORGE_INSTALLER_URL, version);
}
public static void addAutoInstallArgs(Intent intent, File modInstallerJar, boolean createProfile) {
intent.putExtra("javaArgs", "-javaagent:"+ Tools.DIR_DATA+"/forge_installer/forge_installer.jar"
+ (createProfile ? "=NPS" : "") + // No Profile Suppression
" -jar "+modInstallerJar.getAbsolutePath());
intent.putExtra("skipDetectMod", true);
}
}

View File

@@ -0,0 +1,102 @@
package net.kdt.pojavlaunch.modloaders;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class ForgeVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter {
private final List<String> mGameVersions;
private final List<List<String>> mForgeVersions;
private final LayoutInflater mLayoutInflater;
public ForgeVersionListAdapter(List<String> forgeVersions, LayoutInflater layoutInflater) {
this.mLayoutInflater = layoutInflater;
mGameVersions = new ArrayList<>();
mForgeVersions = new ArrayList<>();
for(String version : forgeVersions) {
int dashIndex = version.indexOf("-");
String gameVersion = version.substring(0, dashIndex);
List<String> versionList;
int gameVersionIndex = mGameVersions.indexOf(gameVersion);
if(gameVersionIndex != -1) versionList = mForgeVersions.get(gameVersionIndex);
else {
versionList = new ArrayList<>();
mGameVersions.add(gameVersion);
mForgeVersions.add(versionList);
}
versionList.add(version);
}
}
@Override
public int getGroupCount() {
return mGameVersions.size();
}
@Override
public int getChildrenCount(int i) {
return mForgeVersions.get(i).size();
}
@Override
public Object getGroup(int i) {
return getGameVersion(i);
}
@Override
public Object getChild(int i, int i1) {
return getForgeVersion(i, i1);
}
@Override
public long getGroupId(int i) {
return i;
}
@Override
public long getChildId(int i, int i1) {
return i1;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
((TextView) convertView).setText(getGameVersion(i));
return convertView;
}
@Override
public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
((TextView) convertView).setText(getForgeVersion(i, i1));
return convertView;
}
private String getGameVersion(int i) {
return mGameVersions.get(i);
}
private String getForgeVersion(int i, int i1){
return mForgeVersions.get(i).get(i1);
}
@Override
public boolean isChildSelectable(int i, int i1) {
return true;
}
}

View File

@@ -0,0 +1,39 @@
package net.kdt.pojavlaunch.modloaders;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
public class ForgeVersionListHandler extends DefaultHandler {
private List<String> mForgeVersions;
private StringBuilder mCurrentVersion = null;
@Override
public void startDocument() throws SAXException {
mForgeVersions = new ArrayList<>();
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if(mCurrentVersion != null) mCurrentVersion.append(ch, start, length);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equals("version")) mCurrentVersion = new StringBuilder();
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("version")) {
String version = mCurrentVersion.toString();
mForgeVersions.add(version);
mCurrentVersion = null;
}
}
public List<String> getVersions() {
return mForgeVersions;
}
}

View File

@@ -0,0 +1,9 @@
package net.kdt.pojavlaunch.modloaders;
import java.io.File;
public interface ModloaderDownloadListener {
void onDownloadFinished(File downloadedFile);
void onDataNotAvailable();
void onDownloadError(Exception e);
}

View File

@@ -0,0 +1,61 @@
package net.kdt.pojavlaunch.modloaders;
import java.io.File;
public class ModloaderListenerProxy implements ModloaderDownloadListener {
public static final int PROXY_RESULT_NONE = -1;
public static final int PROXY_RESULT_FINISHED = 0;
public static final int PROXY_RESULT_NOT_AVAILABLE = 1;
public static final int PROXY_RESULT_ERROR = 2;
private ModloaderDownloadListener mDestinationListener;
private Object mProxyResultObject;
private int mProxyResult = PROXY_RESULT_NONE;
@Override
public synchronized void onDownloadFinished(File downloadedFile) {
if(mDestinationListener != null) {
mDestinationListener.onDownloadFinished(downloadedFile);
}else{
mProxyResult = PROXY_RESULT_FINISHED;
mProxyResultObject = downloadedFile;
}
}
@Override
public synchronized void onDataNotAvailable() {
if(mDestinationListener != null) {
mDestinationListener.onDataNotAvailable();
}else{
mProxyResult = PROXY_RESULT_NOT_AVAILABLE;
mProxyResultObject = null;
}
}
@Override
public synchronized void onDownloadError(Exception e) {
if(mDestinationListener != null) {
mDestinationListener.onDownloadError(e);
}else {
mProxyResult = PROXY_RESULT_ERROR;
mProxyResultObject = e;
}
}
public synchronized void attachListener(ModloaderDownloadListener listener) {
switch(mProxyResult) {
case PROXY_RESULT_FINISHED:
listener.onDownloadFinished((File) mProxyResultObject);
break;
case PROXY_RESULT_NOT_AVAILABLE:
listener.onDataNotAvailable();
break;
case PROXY_RESULT_ERROR:
listener.onDownloadError((Exception) mProxyResultObject);
break;
}
mDestinationListener = listener;
}
public synchronized void detachListener() {
mDestinationListener = null;
}
}

View File

@@ -0,0 +1,45 @@
package net.kdt.pojavlaunch.modloaders;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.HtmlNode;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.TagNodeVisitor;
import java.io.IOException;
import java.net.URL;
public class OFDownloadPageScraper implements TagNodeVisitor {
public static String run(String urlInput) throws IOException{
return new OFDownloadPageScraper().runInner(urlInput);
}
private String mDownloadFullUrl;
private String runInner(String url) throws IOException {
HtmlCleaner htmlCleaner = new HtmlCleaner();
htmlCleaner.clean(new URL(url)).traverse(this);
return mDownloadFullUrl;
}
@Override
public boolean visit(TagNode parentNode, HtmlNode htmlNode) {
if(isDownloadUrl(parentNode, htmlNode)) {
TagNode tagNode = (TagNode) htmlNode;
String href = tagNode.getAttributeByName("href");
if(!href.startsWith("https://")) href = "https://optifine.net/"+href;
this.mDownloadFullUrl = href;
return false;
}
return true;
}
public boolean isDownloadUrl(TagNode parentNode, HtmlNode htmlNode) {
if(!(htmlNode instanceof TagNode)) return false;
if(parentNode == null) return false;
TagNode tagNode = (TagNode) htmlNode;
if(!(parentNode.getName().equals("span")
&& "Download".equals(parentNode.getAttributeByName("id")))) return false;
return tagNode.getName().equals("a") &&
"onDownload()".equals(tagNode.getAttributeByName("onclick"));
}
}

View File

@@ -0,0 +1,121 @@
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, 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;
this.mDestinationFile = mDestinationFile;
this.mListener = mListener;
}
@Override
public void run() {
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.of_dl_progress, mOptiFineVersion.versionName);
try {
if(runCatching()) mListener.onDownloadFinished(mDestinationFile);
}catch (IOException e) {
mListener.onDownloadError(e);
}
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, -1, -1);
}
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;
}
public String scrapeDownloadsPage() throws IOException{
String scrapeResult = OFDownloadPageScraper.run(mOptiFineVersion.downloadUrl);
if(scrapeResult == null) mListener.onDataNotAvailable();
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();
}
}
}

View File

@@ -0,0 +1,90 @@
package net.kdt.pojavlaunch.modloaders;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import java.util.ArrayList;
import java.util.List;
public class OptiFineScraper implements DownloadUtils.ParseCallback<OptiFineUtils.OptiFineVersions> {
private final OptiFineUtils.OptiFineVersions mOptiFineVersions;
private List<OptiFineUtils.OptiFineVersion> mListInProgress;
private String mMinecraftVersion;
public OptiFineScraper() {
mOptiFineVersions = new OptiFineUtils.OptiFineVersions();
mOptiFineVersions.minecraftVersions = new ArrayList<>();
mOptiFineVersions.optifineVersions = new ArrayList<>();
}
@Override
public OptiFineUtils.OptiFineVersions process(String input) throws DownloadUtils.ParseException {
HtmlCleaner htmlCleaner = new HtmlCleaner();
TagNode tagNode = htmlCleaner.clean(input);
traverseTagNode(tagNode);
insertVersionContent(null);
if(mOptiFineVersions.optifineVersions.size() < 1 ||
mOptiFineVersions.minecraftVersions.size() < 1) throw new DownloadUtils.ParseException(null);
return mOptiFineVersions;
}
public void traverseTagNode(TagNode tagNode) {
if(isDownloadLine(tagNode) && mMinecraftVersion != null) {
traverseDownloadLine(tagNode);
} else if(isMinecraftVersionTag(tagNode)) {
insertVersionContent(tagNode);
} else {
for(TagNode tagNodes : tagNode.getChildTags()) {
traverseTagNode(tagNodes);
}
}
}
private boolean isDownloadLine(TagNode tagNode) {
return tagNode.getName().equals("tr") &&
tagNode.hasAttribute("class") &&
tagNode.getAttributeByName("class").startsWith("downloadLine");
}
private boolean isMinecraftVersionTag(TagNode tagNode) {
return tagNode.getName().equals("h2") &&
tagNode.getText().toString().startsWith("Minecraft ");
}
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")) {
case "colFile":
optiFineVersion.versionName = subNode.getText().toString();
break;
case "colMirror":
optiFineVersion.downloadUrl = getLinkHref(subNode);
}
}
mListInProgress.add(optiFineVersion);
}
private String getLinkHref(TagNode parent) {
for(TagNode subNode : parent.getChildTags()) {
if(subNode.getName().equals("a") && subNode.hasAttribute("href")) {
return subNode.getAttributeByName("href").replace("http://", "https://");
}
}
return null;
}
private void insertVersionContent(TagNode tagNode) {
if(mListInProgress != null && mMinecraftVersion != null) {
mOptiFineVersions.minecraftVersions.add(mMinecraftVersion);
mOptiFineVersions.optifineVersions.add(mListInProgress);
}
if(tagNode != null) {
mMinecraftVersion = tagNode.getText().toString();
mListInProgress = new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,40 @@
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;
public class OptiFineUtils {
public static OptiFineVersions downloadOptiFineVersions() throws IOException {
try {
return DownloadUtils.downloadStringCached("https://optifine.net/downloads",
"of_downloads_page", new OptiFineScraper());
}catch (DownloadUtils.ParseException e) {
e.printStackTrace();
return null;
}
}
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;
}
}

View File

@@ -0,0 +1,77 @@
package net.kdt.pojavlaunch.modloaders;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.TextView;
public class OptiFineVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter {
private final OptiFineUtils.OptiFineVersions mOptiFineVersions;
private final LayoutInflater mLayoutInflater;
public OptiFineVersionListAdapter(OptiFineUtils.OptiFineVersions optiFineVersions, LayoutInflater mLayoutInflater) {
mOptiFineVersions = optiFineVersions;
this.mLayoutInflater = mLayoutInflater;
}
@Override
public int getGroupCount() {
return mOptiFineVersions.minecraftVersions.size();
}
@Override
public int getChildrenCount(int i) {
return mOptiFineVersions.optifineVersions.get(i).size();
}
@Override
public Object getGroup(int i) {
return mOptiFineVersions.minecraftVersions.get(i);
}
@Override
public Object getChild(int i, int i1) {
return mOptiFineVersions.optifineVersions.get(i).get(i1);
}
@Override
public long getGroupId(int i) {
return i;
}
@Override
public long getChildId(int i, int i1) {
return i1;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
((TextView) convertView).setText((String)getGroup(i));
return convertView;
}
@Override
public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
((TextView) convertView).setText(((OptiFineUtils.OptiFineVersion)getChild(i,i1)).versionName);
return convertView;
}
@Override
public boolean isChildSelectable(int i, int i1) {
return true;
}
}

View File

@@ -29,25 +29,17 @@ import fr.spse.extended_view.ExtendedTextView;
*/
public class ProfileAdapter extends BaseAdapter {
private Map<String, MinecraftProfile> mProfiles;
public static final String CREATE_PROFILE_MAGIC = "___extra____profile-create";
private final MinecraftProfile dummy = new MinecraftProfile();
private MinecraftProfile mCreateProfile;
private List<String> mProfileList;
private final ProfileAdapterExtra[] mExtraEntires;
public ProfileAdapter(Context context, boolean enableCreateButton) {
public ProfileAdapter(Context context, ProfileAdapterExtra[] extraEntries) {
ProfileIconCache.initDefault(context);
LauncherProfiles.update();
mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles);
if(enableCreateButton) {
mCreateProfile = new MinecraftProfile();
mCreateProfile.name = context.getString(R.string.create_profile);
mCreateProfile.lastVersionId = null;
}
if(extraEntries == null) mExtraEntires = new ProfileAdapterExtra[0];
else mExtraEntires = extraEntries;
mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0])));
if(enableCreateButton) {
mProfileList.add(ProfileAdapter.CREATE_PROFILE_MAGIC);
mProfiles.put(CREATE_PROFILE_MAGIC, mCreateProfile);
}
}
/*
* Gets how much profiles are loaded in the adapter right now
@@ -55,7 +47,7 @@ public class ProfileAdapter extends BaseAdapter {
*/
@Override
public int getCount() {
return mProfileList.size();
return mProfileList.size() + mExtraEntires.length;
}
/*
* Gets the profile at a given index
@@ -64,12 +56,15 @@ public class ProfileAdapter extends BaseAdapter {
*/
@Override
public Object getItem(int position) {
//safe since the second check in the and statement will be skipped if the first one fails
if(position < mProfileList.size() && mProfiles.containsKey(mProfileList.get(position))) {
return mProfileList.get(position);
}else{
return null;
int profileListSize = mProfileList.size();
int extraPosition = position - profileListSize;
if(position < profileListSize){
String profileName = mProfileList.get(position);
if(mProfiles.containsKey(profileName)) return profileName;
}else if(extraPosition >= 0 && extraPosition < mExtraEntires.length) {
return mExtraEntires[extraPosition];
}
return null;
}
public int resolveProfileIndex(String name) {
@@ -85,8 +80,6 @@ public class ProfileAdapter extends BaseAdapter {
public void notifyDataSetChanged() {
mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles);
mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0])));
mProfileList.add(ProfileAdapter.CREATE_PROFILE_MAGIC);
mProfiles.put(CREATE_PROFILE_MAGIC, mCreateProfile);
super.notifyDataSetChanged();
}
@@ -94,7 +87,9 @@ public class ProfileAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_version_profile_layout,parent,false);
setViewProfile(v,mProfileList.get(position), true);
Object profileObject = getItem(position);
if(profileObject instanceof String) setViewProfile(v, (String) profileObject, true);
else if(profileObject instanceof ProfileAdapterExtra) setViewExtra(v, (ProfileAdapterExtra) profileObject);
return v;
}
@@ -131,7 +126,12 @@ public class ProfileAdapter extends BaseAdapter {
String selectedProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"");
extendedTextView.setBackgroundColor(selectedProfile.equals(nm) ? ColorUtils.setAlphaComponent(Color.WHITE,60) : Color.TRANSPARENT);
}else extendedTextView.setBackgroundColor(Color.TRANSPARENT);
}
public void setViewExtra(View v, ProfileAdapterExtra extra) {
ExtendedTextView extendedTextView = (ExtendedTextView) v;
extendedTextView.setCompoundDrawablesRelative(extra.icon, null, extendedTextView.getCompoundsDrawables()[2], null);
extendedTextView.setText(extra.name);
extendedTextView.setBackgroundColor(Color.TRANSPARENT);
}
}

View File

@@ -0,0 +1,15 @@
package net.kdt.pojavlaunch.profiles;
import android.graphics.drawable.Drawable;
public class ProfileAdapterExtra {
public final int id;
public final int name;
public final Drawable icon;
public ProfileAdapterExtra(int id, int name, Drawable icon) {
this.id = id;
this.name = name;
this.icon = icon;
}
}

View File

@@ -1,7 +1,5 @@
package net.kdt.pojavlaunch.profiles;
import static net.kdt.pojavlaunch.profiles.ProfileAdapter.CREATE_PROFILE_MAGIC;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
@@ -46,21 +44,11 @@ public class ProfileIconCache {
icon = ProfileIconCache.submitIcon(resources, profileName, b64Icon.substring(BASE64_PNG_HEADER.length()));
}else{
Log.i("IconParser","Unsupported icon: "+b64Icon);
if(profileName.equals(CREATE_PROFILE_MAGIC)){
icon = ProfileIconCache.pushAddProfileIcon(ResourcesCompat.getDrawable(resources, R.drawable.ic_add, null));
}else{
icon = ProfileIconCache.pushDefaultIcon(profileName);
}
icon = ProfileIconCache.pushDefaultIcon(profileName);
}
return icon;
}
public static Drawable pushAddProfileIcon(Drawable drawable){
sIconCache.put(CREATE_PROFILE_MAGIC, drawable);
return drawable;
}
public static Drawable pushDefaultIcon(String key) {
sIconCache.put(key, sDefaultIcon);

View File

@@ -24,8 +24,11 @@ public class VersionListAdapter extends BaseExpandableListAdapter implements Exp
private final String[] mGroups;
private final String[] mInstalledVersions;
private final List<?>[] mData;
private final boolean mHideCustomVersions;
private final int mSnapshotListPosition;
public VersionListAdapter(JMinecraftVersionList.Version[] versionList, Context ctx){
public VersionListAdapter(JMinecraftVersionList.Version[] versionList, boolean hideCustomVersions, Context ctx){
mHideCustomVersions = hideCustomVersions;
mLayoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
List<JMinecraftVersionList.Version> releaseList = new FilteredSubList<>(versionList, item -> item.type.equals("release"));
@@ -43,6 +46,7 @@ public class VersionListAdapter extends BaseExpandableListAdapter implements Exp
ctx.getString(R.string.mcl_setting_veroption_oldalpha)
};
mData = new List[]{ releaseList, snapshotList, betaList, alphaList};
mSnapshotListPosition = 1;
}else{
mGroups = new String[]{
ctx.getString(R.string.mcl_setting_veroption_installed),
@@ -52,6 +56,7 @@ public class VersionListAdapter extends BaseExpandableListAdapter implements Exp
ctx.getString(R.string.mcl_setting_veroption_oldalpha)
};
mData = new List[]{Arrays.asList(mInstalledVersions), releaseList, snapshotList, betaList, alphaList};
mSnapshotListPosition = 2;
}
}
@@ -116,7 +121,12 @@ public class VersionListAdapter extends BaseExpandableListAdapter implements Exp
return true;
}
public boolean isSnapshotSelected(int groupPosition) {
return groupPosition == mSnapshotListPosition;
}
private boolean areInstalledVersionsAvailable(){
if(mHideCustomVersions) return false;
return !(mInstalledVersions == null || mInstalledVersions.length == 0);
}

View File

@@ -0,0 +1,37 @@
package net.kdt.pojavlaunch.profiles;
import static net.kdt.pojavlaunch.extra.ExtraCore.getValue;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.ExpandableListView;
import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.extra.ExtraConstants;
public class VersionSelectorDialog {
public static void open(Context context, boolean hideCustomVersions, VersionSelectorListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
ExpandableListView expandableListView = (ExpandableListView) LayoutInflater.from(context)
.inflate(R.layout.dialog_expendable_list_view , null);
JMinecraftVersionList jMinecraftVersionList = (JMinecraftVersionList) getValue(ExtraConstants.RELEASE_TABLE);
JMinecraftVersionList.Version[] versionArray;
if(jMinecraftVersionList == null || jMinecraftVersionList.versions == null) versionArray = new JMinecraftVersionList.Version[0];
else versionArray = jMinecraftVersionList.versions;
VersionListAdapter adapter = new VersionListAdapter(versionArray, hideCustomVersions, context);
expandableListView.setAdapter(adapter);
builder.setView(expandableListView);
AlertDialog dialog = builder.show();
expandableListView.setOnChildClickListener((parent, v1, groupPosition, childPosition, id) -> {
String version = adapter.getChild(groupPosition, childPosition);
listener.onVersionSelected(version, adapter.isSnapshotSelected(groupPosition));
dialog.dismiss();
return true;
});
}
}

View File

@@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.profiles;
public interface VersionSelectorListener {
void onVersionSelected(String versionId, boolean isSnapshot);
}

View File

@@ -100,4 +100,8 @@ public class ProgressKeeper {
public static synchronized int getTaskCount() {
return sProgressStates.size();
}
public static boolean hasOngoingTasks() {
return getTaskCount() > 0;
}
}

View File

@@ -6,9 +6,7 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
@@ -28,7 +26,6 @@ import net.kdt.pojavlaunch.progresskeeper.TaskCountListener;
*/
public class ProgressService extends Service implements TaskCountListener {
private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
private NotificationManagerCompat notificationManagerCompat;
/** Simple wrapper to start the service */
@@ -85,7 +82,7 @@ public class ProgressService extends Service implements TaskCountListener {
@Override
public void onUpdateTaskCount(int taskCount) {
mainThreadHandler.post(()->{
Tools.MAIN_HANDLER.post(()->{
if(taskCount > 0) {
mNotificationBuilder.setContentText(getString(R.string.progresslayout_tasks_in_progress, taskCount));
notificationManagerCompat.notify(1, mNotificationBuilder.build());

View File

@@ -68,7 +68,6 @@ public class AsyncAssetManager {
Tools.copyAssetFile(ctx, "launcher_profiles.json", Tools.DIR_GAME_NEW, false);
Tools.copyAssetFile(ctx,"resolv.conf",Tools.DIR_DATA, false);
Tools.copyAssetFile(ctx,"arc_dns_injector.jar",Tools.DIR_DATA, false);
} catch (IOException e) {
Log.e("AsyncAssetManager", "Failed to unpack critical components !");
}
@@ -86,6 +85,8 @@ public class AsyncAssetManager {
// we repack them to a single file here
unpackComponent(ctx, "lwjgl3", false);
unpackComponent(ctx, "security", true);
unpackComponent(ctx, "arc_dns_injector", true);
unpackComponent(ctx, "forge_installer", true);
} catch (IOException e) {
Log.e("AsyncAssetManager", "Failed o unpack components !",e );
}

View File

@@ -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();
}
@@ -183,6 +183,8 @@ public class AsyncMinecraftDownloader {
os.close();
}
}
} catch (DownloaderException e) {
throw e;
} catch (Throwable e) {
Log.e("AsyncMcDownloader", e.toString(),e );
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);

View File

@@ -7,6 +7,8 @@ import android.util.Log;
import androidx.annotation.Nullable;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import net.kdt.pojavlaunch.JMinecraftVersionList;
@@ -22,7 +24,7 @@ import java.io.IOException;
/** Class getting the version list, and that's all really */
public class AsyncVersionList {
public void getVersionList(@Nullable VersionDoneListener listener){
public void getVersionList(@Nullable VersionDoneListener listener, boolean secondPass){
sExecutorService.execute(() -> {
File versionFile = new File(Tools.DIR_DATA + "/version_list.json");
JMinecraftVersionList versionList = null;
@@ -41,6 +43,11 @@ public class AsyncVersionList {
versionList = Tools.GLOBAL_GSON.fromJson(new JsonReader(new FileReader(versionFile)), JMinecraftVersionList.class);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (JsonIOException | JsonSyntaxException e) {
e.printStackTrace();
versionFile.delete();
if(!secondPass)
getVersionList(listener, true);
}
}

View File

@@ -1,5 +1,7 @@
package net.kdt.pojavlaunch.utils;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.*;
@@ -115,6 +117,46 @@ public class DownloadUtils {
conn.disconnect();
}
public static <T> T downloadStringCached(String url, String cacheName, ParseCallback<T> parseCallback) throws IOException, ParseException{
File cacheDestination = new File(Tools.DIR_CACHE, "string_cache/"+cacheName);
File cacheDestinationDir = cacheDestination.getParentFile();
if(cacheDestinationDir != null &&
!cacheDestinationDir.exists() &&
!cacheDestinationDir.mkdirs()) throw new IOException("Failed to create the cache directory");
if(cacheDestination.isFile() &&
cacheDestination.canRead() &&
System.currentTimeMillis() < (cacheDestination.lastModified() + 86400000)) {
try {
String cachedString = Tools.read(new FileInputStream(cacheDestination));
return parseCallback.process(cachedString);
}catch(IOException e) {
Log.i("DownloadUtils", "Failed to read the cached file", e);
}catch (ParseException e) {
Log.i("DownloadUtils", "Failed to parse the cached file", e);
}
}
String urlContent = DownloadUtils.downloadString(url);
// if we download the file and fail parsing it, we will yeet outta there
// and not cache the unparseable sting. We will return this after trying to save the downloaded
// string into cache
T parseResult = parseCallback.process(urlContent);
boolean tryWriteCache = false;
if(cacheDestination.exists()) {
tryWriteCache = cacheDestination.canWrite();
} else {
// if it is null, then cacheDestination is the file system root. Very bad.
// but let's shield ourselves and just never try to cache the file if that happens
if(cacheDestinationDir != null && !cacheDestinationDir.isFile()) tryWriteCache = cacheDestinationDir.canWrite();
}
if(tryWriteCache) try {
Tools.write(cacheDestination.getAbsolutePath(), urlContent);
}catch(IOException e) {
Log.i("DownloadUtils", "Failed to cache the string", e);
}
return parseResult;
}
public static void downloadFileMonitoredWithHeaders(String urlInput,File outputFile, @Nullable byte[] buffer,
Tools.DownloaderFeedback monitor, String userAgent, String cookies) throws IOException {
if (!outputFile.exists()) {
@@ -141,5 +183,13 @@ public class DownloadUtils {
conn.disconnect();
}
public interface ParseCallback<T> {
T process(String input) throws ParseException;
}
public static class ParseException extends Exception {
public ParseException(Exception e) {
super(e);
}
}
}

View File

@@ -173,7 +173,7 @@ public class JREUtils {
envMap.put("POJAV_NATIVEDIR", NATIVE_LIB_DIR);
envMap.put("JAVA_HOME", jreHome);
envMap.put("HOME", Tools.DIR_GAME_HOME);
envMap.put("TMPDIR", activity.getCacheDir().getAbsolutePath());
envMap.put("TMPDIR", Tools.DIR_CACHE.getAbsolutePath());
envMap.put("LIBGL_MIPMAP", "3");
// On certain GLES drivers, overloading default functions shader hack fails, so disable it
@@ -191,7 +191,7 @@ public class JREUtils {
envMap.put("FORCE_VSYNC", String.valueOf(LauncherPreferences.PREF_FORCE_VSYNC));
envMap.put("MESA_GLSL_CACHE_DIR", activity.getCacheDir().getAbsolutePath());
envMap.put("MESA_GLSL_CACHE_DIR", Tools.DIR_CACHE.getAbsolutePath());
if (LOCAL_RENDERER != null) {
envMap.put("MESA_GL_VERSION_OVERRIDE", LOCAL_RENDERER.equals("opengles3_virgl")?"4.3":"4.6");
envMap.put("MESA_GLSL_VERSION_OVERRIDE", LOCAL_RENDERER.equals("opengles3_virgl")?"430":"460");
@@ -200,7 +200,7 @@ public class JREUtils {
envMap.put("allow_higher_compat_version", "true");
envMap.put("allow_glsl_extension_directive_midshader", "true");
envMap.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
envMap.put("VTEST_SOCKET_NAME", activity.getCacheDir().getAbsolutePath() + "/.virgl_test");
envMap.put("VTEST_SOCKET_NAME", new File(Tools.DIR_CACHE, ".virgl_test").getAbsolutePath());
envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH);
envMap.put("PATH", jreHome + "/bin:" + Os.getenv("PATH"));
@@ -328,7 +328,7 @@ public class JREUtils {
ArrayList<String> overridableArguments = new ArrayList<>(Arrays.asList(
"-Djava.home=" + runtimeHome,
"-Djava.io.tmpdir=" + ctx.getCacheDir().getAbsolutePath(),
"-Djava.io.tmpdir=" + Tools.DIR_CACHE.getAbsolutePath(),
"-Duser.home=" + Tools.DIR_GAME_HOME,
"-Duser.language=" + System.getProperty("user.language"),
"-Dos.name=Linux",
@@ -352,7 +352,7 @@ public class JREUtils {
"-Dfml.earlyprogresswindow=false" //Forge 1.14+ workaround
));
if(LauncherPreferences.PREF_ARC_CAPES) {
overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176");
overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector/arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176");
}
List<String> additionalArguments = new ArrayList<>();
for(String arg : overridableArguments) {

View File

@@ -85,7 +85,7 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass
/* open latestlog.txt for writing */
const char* logFilePath = (*env)->GetStringUTFChars(env, logPath, NULL);
latestlog_fd = open(logFilePath, O_WRONLY | O_TRUNC | O_CREAT, 644);
latestlog_fd = open(logFilePath, O_WRONLY | O_TRUNC);
if(latestlog_fd == -1) {
latestlog_fd = 0;
(*env)->ThrowNew(env, ioeClass, strerror(errno));

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_app"
android:paddingHorizontal="@dimen/fragment_padding_medium">
<TextView
android:id="@+id/title_textview"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_large"
android:text="@string/fabric_dl_loader_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view"
style="@style/ThickDivider"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/title_textview" />
<TextView
android:id="@+id/fabric_installer_label_loader_ver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fabric_dl_loader_version"
app:layout_constraintBottom_toTopOf="@+id/fabric_installer_loader_ver_spinner"
app:layout_constraintStart_toStartOf="@+id/fabric_installer_loader_ver_spinner" />
<Spinner
android:id="@+id/fabric_installer_loader_ver_spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_extra_large"
android:background="@drawable/background_line"
android:minHeight="48dp"
android:paddingVertical="0dp"
android:paddingStart="7dp"
android:paddingEnd="7dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/_8sdp"
android:paddingHorizontal="@dimen/_8sdp"
android:rotation="180"
android:src="@drawable/spinner_arrow"
app:layout_constraintBottom_toBottomOf="@+id/fabric_installer_loader_ver_spinner"
app:layout_constraintEnd_toEndOf="@+id/fabric_installer_loader_ver_spinner"
app:layout_constraintTop_toTopOf="@+id/fabric_installer_loader_ver_spinner" />
<LinearLayout
android:id="@+id/fabric_installer_retry_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/fragment_padding_medium"
android:paddingBottom="@dimen/fragment_padding_medium"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/fabric_installer_loader_ver_spinner"
tools:layout_editor_absoluteX="13dp">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/modloader_dl_failed_to_load_list"
android:textColor="#FFFF0000"
android:textStyle="bold" />
<Button
android:id="@+id/fabric_installer_retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/global_retry" />
</LinearLayout>
<TextView
android:id="@+id/fabric_installer_game_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fabric_dl_game_version"
app:layout_constraintBottom_toTopOf="@+id/fabric_installer_version_select_label"
app:layout_constraintStart_toStartOf="@+id/fabric_installer_version_select_label" />
<TextView
android:id="@+id/fabric_installer_version_select_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_extra_large"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:hint="@string/version_select_hint"
android:paddingHorizontal="@dimen/padding_heavy"
app:layout_constraintEnd_toStartOf="@+id/fabric_installer_game_version_change"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fabric_installer_retry_layout" />
<Button
android:id="@+id/fabric_installer_game_version_change"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/global_select"
app:layout_constraintBottom_toBottomOf="@+id/fabric_installer_version_select_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/fabric_installer_version_select_label" />
<com.kdt.mcgui.MineButton
android:id="@+id/fabric_installer_start_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_heavy"
android:layout_marginBottom="@dimen/padding_heavy"
android:enabled="false"
android:text="@string/fabric_dl_install"
app:layout_constraintBottom_toTopOf="@+id/fabric_installer_progress_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fabric_installer_version_select_label"
app:layout_constraintVertical_bias="1.0" />
<ProgressBar
android:id="@+id/fabric_installer_progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:indeterminate="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -21,28 +21,31 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/_16sdp"
android:text="Select Folder"
android:layout_marginStart="@dimen/padding_medium"
android:text="@string/folder_fragment_select"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/file_selector_create_folder"
app:layout_constraintWidth_percent="0.45" />
/>
<com.kdt.mcgui.MineButton
android:id="@+id/file_selector_create_folder"
android:layout_width="185dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/_16sdp"
android:layout_marginEnd="@dimen/padding_medium"
android:text="Create folder"
android:text="@string/folder_fragment_create"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_selector_select_folder"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.45" />
/>
<TextView
android:id="@+id/file_selector_current_path"

View File

@@ -63,7 +63,7 @@
android:background="@android:color/transparent"
android:drawableEnd="@drawable/spinner_arrow"
app:drawableEndSize="@dimen/_12sdp"
app:drawableEndSize="@dimen/padding_heavy"
app:drawableStartIntegerScaling="true"
app:drawableStartSize="@dimen/_36sdp"
app:drawableEndPadding="@dimen/_1sdp"

View File

@@ -52,7 +52,7 @@
android:layout_width="0dp"
android:layout_height="@dimen/_42sdp"
android:layout_marginHorizontal="@dimen/_25sdp"
android:layout_marginTop="@dimen/_12sdp"
android:layout_marginTop="@dimen/padding_heavy"
android:onClick="loginMC"
android:text="@string/login_online_login_label"

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@color/background_app"
android:paddingHorizontal="@dimen/fragment_padding_medium">
<TextView
android:id="@+id/title_textview"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:gravity="center"
android:layout_marginTop="@dimen/padding_large"
android:text="@string/forge_dl_select_version" />
<View
android:id="@+id/view"
style="@style/ThickDivider"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/title_textview"
android:paddingBottom="@dimen/fragment_padding_medium"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text=""
android:textColor="@color/primary_text" />
<ExpandableListView
android:scrollbarThumbVertical="@color/minebutton_color"
android:id="@+id/mod_dl_expandable_version_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</ExpandableListView>
<LinearLayout
android:id="@+id/mod_dl_retry_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/fragment_padding_medium"
android:paddingBottom="@dimen/fragment_padding_medium"
android:visibility="gone">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/modloader_dl_failed_to_load_list"
android:textColor="#FFFF0000"
android:textStyle="bold" />
<Button
android:id="@+id/forge_installer_retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/global_retry" />
</LinearLayout>
<ProgressBar
android:id="@+id/mod_dl_list_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" />
</LinearLayout>

View File

@@ -27,14 +27,14 @@
android:id="@+id/vprof_editor_profile_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_24sdp"
android:layout_marginTop="@dimen/padding_extra_large"
android:background="@drawable/background_line"
android:textSize="@dimen/_13ssp"
android:ems="10"
android:hint="@string/unnamed"
android:inputType="textPersonName"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingHorizontal="@dimen/padding_heavy"
app:layout_constraintTop_toTopOf="parent" />
@@ -51,10 +51,10 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/_4sdp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:hint="Select a version"
android:paddingHorizontal="@dimen/_12sdp"
android:hint="@string/version_select_hint"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@+id/vprof_editor_version_button"
@@ -86,11 +86,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/_4sdp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:hint="@string/use_global_default"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@id/vprof_editor_ctrl_button"
@@ -127,7 +127,7 @@
android:ems="10"
android:hint="@string/use_global_default"
android:inputType="text"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_ctrl_spinner" />
@@ -147,12 +147,12 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/_4sdp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:ems="10"
android:hint=".minecraft"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@id/vprof_editor_path_button"
@@ -240,24 +240,25 @@
android:layout_height="wrap_content"
android:layout_marginVertical="26dp"
android:text="@string/global_save"
android:layout_marginEnd="@dimen/padding_medium"
app:layout_constraintEnd_toStartOf="@+id/vprof_editor_delete_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_profile_renderer"
app:layout_constraintWidth_percent="0.45" />
/>
<com.kdt.mcgui.MineButton
android:id="@+id/vprof_editor_delete_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/padding_medium"
android:text="@string/global_delete"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/vprof_editor_save_button"
app:layout_constraintTop_toTopOf="@+id/vprof_editor_save_button"
app:layout_constraintWidth_percent="0.45" />
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<com.kdt.DefocusableScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_app"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="@dimen/fragment_padding_medium"
android:orientation="vertical">
<!-- Vanilla like version -->
<TextView
android:id="@+id/title_textview"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/create_profile_vanilla_like_versions"
android:layout_gravity="center"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" />
<View
android:id="@+id/view"
style="@style/ThickDivider"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/title_textview"
/>
<com.kdt.mcgui.MineButton
android:id="@+id/vanilla_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_large"
android:text="@string/create_profile_vanilla"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/view" />
<com.kdt.mcgui.MineButton
android:id="@+id/optifine_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_large"
android:text="@string/create_profile_optifine"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/view" />
<!-- Modded versions -->
<TextView
android:id="@+id/title_modded_textview"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/padding_extra_extra_large"
android:text="@string/create_profile_modded_versions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline" />
<View
android:id="@+id/view_modded"
style="@style/ThickDivider"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/padding_large"
app:layout_constraintTop_toBottomOf="@+id/title_modded_textview"
/>
<com.kdt.mcgui.MineButton
android:id="@+id/modded_profile_fabric"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_large"
android:layout_marginTop="@dimen/padding_large"
android:text="@string/modloader_dl_install_fabric"
app:layout_constraintTop_toBottomOf="@+id/view_modded"
tools:layout_editor_absoluteX="50dp" />
<com.kdt.mcgui.MineButton
android:id="@+id/modded_profile_forge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_large"
android:layout_marginTop="@dimen/padding_large"
android:text="@string/modloader_dl_install_forge"
app:layout_constraintTop_toBottomOf="@+id/modded_profile_fabric" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.55" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.25" />
</LinearLayout>
</com.kdt.DefocusableScrollView>

View File

@@ -45,7 +45,7 @@
android:layout_width="@dimen/_35sdp"
android:layout_height="0dp"
android:background="?attr/selectableItemBackground"
android:paddingVertical="@dimen/_12sdp"
android:paddingVertical="@dimen/padding_heavy"
android:scaleType="fitCenter"
android:src="@drawable/ic_menu_delete_forever"

View File

@@ -8,6 +8,6 @@
android:fadeScrollbars="false"
android:scrollbarThumbVertical="@color/minebutton_color"
android:paddingVertical="@dimen/_14sdp"
android:scrollbarSize="@dimen/_4sdp"
android:scrollbarSize="@dimen/padding_medium"
android:background="@color/background_bottom_bar"
android:divider="@null" />

View File

@@ -37,8 +37,8 @@
<ImageView
android:id="@+id/progress_flip_arrow"
android:layout_width="@dimen/_24sdp"
android:layout_height="@dimen/_24sdp"
android:layout_width="@dimen/padding_extra_large"
android:layout_height="@dimen/padding_extra_large"
android:layout_marginEnd="@dimen/_8sdp"
app:layout_constraintBottom_toBottomOf="@id/progress_generic_progressbar"
app:layout_constraintEnd_toEndOf="parent"
@@ -51,8 +51,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:paddingHorizontal="@dimen/_12sdp"
android:paddingTop="@dimen/_12sdp"
android:paddingHorizontal="@dimen/padding_heavy"
android:paddingTop="@dimen/padding_heavy"
android:weightSum="3"

View File

@@ -7,13 +7,13 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Padding -->
<dimen name="padding_tiny">2dp</dimen>
<dimen name="padding_tiny_plus_one">3dp</dimen>
<dimen name="padding_small">4dp</dimen>
<dimen name="padding_medium">8dp</dimen>
<dimen name="padding_large">16dp</dimen>
<dimen name="padding_extra_large">24dp</dimen>
<dimen name="padding_extra_extra_large">32dp</dimen>
<dimen name="padding_tiny">@dimen/_1sdp</dimen>
<dimen name="padding_small">@dimen/_2sdp</dimen>
<dimen name="padding_medium">@dimen/_4sdp</dimen>
<dimen name="padding_heavy">@dimen/_12sdp</dimen>
<dimen name="padding_large">@dimen/_16sdp</dimen>
<dimen name="padding_extra_large">@dimen/_24sdp</dimen>
<dimen name="padding_extra_extra_large">@dimen/_32sdp</dimen>
<!-- Main Activity components -->
<dimen name="empty_icon_width">60dp</dimen>

View File

@@ -128,6 +128,7 @@
<string name="global_error_field_empty">This field can\'t be empty</string>
<string name="global_waiting">Wait</string>
<string name="global_select">Select</string>
<string name="global_retry">Retry</string>
<!-- MainActivity: strings -->
<string name="mcn_exit_title">Application/Game exited with code %d, check latestlog.txt for more details.</string>
@@ -331,6 +332,8 @@
<string name="notification_game_runs">The game is running!</string>
<string name="folder_dialog_create">Create</string>
<string name="folder_dialog_insert_name">Insert folder name</string>
<string name="folder_fragment_create">Create folder</string>
<string name="folder_fragment_select">Select Folder</string>
<string name="main_login_done">Login done</string>
<string name="main_add_account">Add account</string>
<string name="tasks_ongoing">Tasks are in progress, please wait</string>
@@ -372,4 +375,24 @@
<string name="preference_deadzone_scale_description">Increase it if the joystick drifts</string>
<string name="preference_force_big_core_title">Force renderer to run on the big core</string>
<string name="preference_force_big_core_desc">Forces the Minecraft render thread to run on the core with the highest max frequency</string>
<string name="version_select_hint">Select a version</string>
<string name="forge_dl_progress">Downloading installer for %s</string>
<string name="modloader_dl_failed_to_load_list">Failed to load the version list!</string>
<string name="forge_dl_no_installer">Sorry, but this version of Forge does not have an installer, which is not yet supported.</string>
<string name="forge_dl_select_version">Select Forge version</string>
<string name="fabric_dl_progress">Downloading Fabric installer</string>
<string name="fabric_dl_loader_version">Fabric loader version</string>
<string name="fabric_dl_game_version">Minecraft version</string>
<string name="fabric_dl_install">Install</string>
<string name="fabric_dl_cant_read_meta">Failed to read Fabric metadata. Please try again later.</string>
<string name="modloader_dl_install_fabric">Create Fabric profile</string>
<string name="modloader_dl_install_forge">Create Forge profile</string>
<string name="create_profile_vanilla">Create vanilla profile</string>
<string name="create_profile_vanilla_like_versions">Vanilla-like versions</string>
<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 collect data for OptiFine installation</string>
<string name="of_dl_progress">Downloading %s</string>
<string name="create_profile_optifine">Create OptiFine profile</string>
</resources>

View File

@@ -12,4 +12,12 @@
<item name="android:textSize">@dimen/_12ssp</item>
</style>
<style name="ThickDivider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/_1sdp</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
</resources>

View File

@@ -11,5 +11,7 @@ jar {
attributes("Manifest-Version": "1.0",
"PreMain-Class": "git.artdeell.arcdns.ArcDNSInjectorAgent")
}
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/"))
File versionFile = file("../app_pojavlauncher/src/main/assets/components/arc_dns_injector/version")
versionFile.write(String.valueOf(new Date().getTime()))
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/components/arc_dns_injector/"))
}

42
forge_installer/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@@ -0,0 +1,25 @@
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation 'org.json:json:20230618'
}
jar {
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
File versionFile = file("../app_pojavlauncher/src/main/assets/components/forge_installer/version")
versionFile.write(String.valueOf(new Date().getTime()))
manifest {
attributes("Manifest-Version": "1.0",
"PreMain-Class": "git.artdeell.installer_agent.Agent")
}
destinationDirectory.set(file("../app_pojavlauncher/src/main/assets/components/forge_installer/"))
}

View File

@@ -0,0 +1,136 @@
package git.artdeell.installer_agent;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.WindowEvent;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import javax.swing.AbstractButton;
import javax.swing.JDialog;
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 nps, boolean of) {
this.suppressProfileCreation = !nps;
this.optiFineInstallation = of;
}
@Override
public void eventDispatched(AWTEvent event) {
WindowEvent windowEvent = (WindowEvent) event;
Window window = windowEvent.getWindow();
if(windowEvent.getID() != WindowEvent.WINDOW_OPENED) return;
if(forgeWindowHandled && window instanceof JDialog) { // expecting a new dialog
handleDialog(window);
return;
}
if(!forgeWindowHandled) { // false at startup, so we will handle the first window as the Forge one
forgeWindowHandled = handleMainWindow(window);
checkComponentTimer();
}
}
public void checkComponentTimer() {
if(forgeWindowHandled) {
componentTimer.cancel();
componentTimer.purge();
return;
}
componentTimer.schedule(new ComponentTimeoutTask(), 30000);
}
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;
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");
return false;
}else{
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
if(components.size() == 1) {
// another common trait of them - they only have one option pane in them,
// so we can discard the rest of the dialog structure
// also allows us to discard dialogs with progress bars which older installers use
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(optiFineInstallation ? "OptiFine" : "forge", suppressProfileCreation);
System.exit(0); // again, forge doesn't call exit for some reason, so we do that ourselves here
}
}
}
public void insertAllComponents(List<Component> components, Container parent, ComponentFilter filter) {
int componentCount = parent.getComponentCount();
for(int i = 0; i < componentCount; i++) {
Component component = parent.getComponent(i);
if(filter.checkComponent(component)) components.add(component);
if(component instanceof Container) {
insertAllComponents(components, (Container) component, filter);
}
}
}
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(agent,
AWTEvent.WINDOW_EVENT_MASK);
}
}

View File

@@ -0,0 +1,7 @@
package git.artdeell.installer_agent;
import java.awt.*;
public interface ComponentFilter {
boolean checkComponent(Component component);
}

View File

@@ -0,0 +1,11 @@
package git.artdeell.installer_agent;
import java.util.TimerTask;
public class ComponentTimeoutTask extends TimerTask {
@Override
public void run() {
System.out.println("Initialization timed out!");
System.exit(17);
}
}

View File

@@ -0,0 +1,12 @@
package git.artdeell.installer_agent;
import javax.swing.*;
import java.awt.*;
public class DialogFilter implements ComponentFilter{
@Override
public boolean checkComponent(Component component) {
return component instanceof JOptionPane
|| component instanceof JProgressBar;
}
}

View File

@@ -0,0 +1,13 @@
package git.artdeell.installer_agent;
import javax.swing.*;
import java.awt.*;
public class MainWindowFilter implements ComponentFilter{
@Override
public boolean checkComponent(Component component) {
return component instanceof JRadioButton
|| component instanceof JTextField
|| component instanceof JButton;
}
}

View File

@@ -0,0 +1,59 @@
package git.artdeell.installer_agent;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;
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(String profileName) {
try {
JSONObject minecraftProfiles = new JSONObject(
new String(Files.readAllBytes(profilesPath),
StandardCharsets.UTF_8)
);
JSONObject profilesArray = minecraftProfiles.getJSONObject("profiles");
oldProfile = profilesArray.optJSONObject(profileName, null);
}catch (IOException | JSONException e) {
System.out.println("Failed to store Forge profile: "+e);
}
}
private static String pickProfileName(String profileName) {
return profileName+random.nextInt();
}
public static void reinsertProfile(String profileName, boolean suppressProfileCreation) {
try {
JSONObject minecraftProfiles = new JSONObject(
new String(Files.readAllBytes(profilesPath),
StandardCharsets.UTF_8)
);
JSONObject profilesArray = minecraftProfiles.getJSONObject("profiles");
if(oldProfile != null) {
if(suppressProfileCreation) profilesArray.put("forge", oldProfile); // restore the old profile
else {
String name = pickProfileName(profileName);
while(profilesArray.has(name)) name = pickProfileName(profileName);
profilesArray.put(name, oldProfile); // restore the old profile under a new name
}
}else{
if(suppressProfileCreation) profilesArray.remove("forge"); // remove the new profile
// otherwise it wont be removed
}
minecraftProfiles.put("profiles", profilesArray);
Files.write(profilesPath, minecraftProfiles.toString().getBytes(StandardCharsets.UTF_8),
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
}catch (IOException | JSONException e) {
System.out.println("Failed to restore old Forge profile: "+e);
}
}
}

View File

@@ -19,3 +19,4 @@ include ':jre_lwjgl3glfw'
include ':app_pojavlauncher'
include ':arc_dns_injector'
include ':forge_installer'