diff --git a/app_pojavlauncher/build.gradle b/app_pojavlauncher/build.gradle index d621c5f08..06841906c 100644 --- a/app_pojavlauncher/build.gradle +++ b/app_pojavlauncher/build.gradle @@ -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']) diff --git a/app_pojavlauncher/src/main/assets/arc_dns_injector.jar b/app_pojavlauncher/src/main/assets/arc_dns_injector.jar deleted file mode 100644 index fe94aba81..000000000 Binary files a/app_pojavlauncher/src/main/assets/arc_dns_injector.jar and /dev/null differ diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar new file mode 100644 index 000000000..447ec038b Binary files /dev/null and b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar differ diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version new file mode 100644 index 000000000..02de90fae --- /dev/null +++ b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version @@ -0,0 +1 @@ +1687691695196 \ No newline at end of file diff --git a/app_pojavlauncher/src/main/assets/components/forge_installer/forge_installer.jar b/app_pojavlauncher/src/main/assets/components/forge_installer/forge_installer.jar new file mode 100644 index 000000000..2e498a39b Binary files /dev/null and b/app_pojavlauncher/src/main/assets/components/forge_installer/forge_installer.jar differ diff --git a/app_pojavlauncher/src/main/assets/components/forge_installer/version b/app_pojavlauncher/src/main/assets/components/forge_installer/version new file mode 100644 index 000000000..825b64780 --- /dev/null +++ b/app_pojavlauncher/src/main/assets/components/forge_installer/version @@ -0,0 +1 @@ +1688133008591 \ No newline at end of file diff --git a/app_pojavlauncher/src/main/assets/components/lwjgl3/version b/app_pojavlauncher/src/main/assets/components/lwjgl3/version index 92042ee01..750d4da31 100644 --- a/app_pojavlauncher/src/main/assets/components/lwjgl3/version +++ b/app_pojavlauncher/src/main/assets/components/lwjgl3/version @@ -1 +1 @@ -1678189863424 \ No newline at end of file +1687691695259 \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcVersionSpinner.java b/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcVersionSpinner.java index ea0dd8a23..d0cd464c2 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcVersionSpinner.java +++ b/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcVersionSpinner.java @@ -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; } - } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java index 253f3397f..a3739dbd5 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java @@ -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()); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java index 2b73bb817..d38b8db78 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java @@ -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; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java index 218de892f..76a05254d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index dab0f67b8..0741b5cea 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -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 diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java index 579f7daf0..ee67bca05 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java @@ -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 diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index 306eb7cc0..180896b68 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java index bfa10ee92..b4191803b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java @@ -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 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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FabricInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FabricInstallFragment.java new file mode 100644 index 000000000..4626a1df9 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FabricInstallFragment.java @@ -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 mLoaderVersions = FabricUtils.downloadLoaderVersionList(false); + if (mLoaderVersions != null) { + Tools.runOnUiThread(()->{ + Context context = getContext(); + if(context == null) return; + ArrayAdapter 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); + }); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ForgeInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ForgeInstallFragment.java new file mode 100644 index 000000000..fe609697f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ForgeInstallFragment.java @@ -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> { + 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 loadVersionList() throws IOException { + return ForgeUtils.downloadForgeVersions(); + } + + @Override + public ExpandableListAdapter createAdapter(List 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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java index 52dbeba07..29e5b4d33 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java @@ -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) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java new file mode 100644 index 000000000..622ba05b4 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java @@ -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 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); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/OptiFineInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/OptiFineInstallFragment.java new file mode 100644 index 000000000..a37704618 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/OptiFineInstallFragment.java @@ -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 { + 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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java index a2c195f52..5df28efa7 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java @@ -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{ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java new file mode 100644 index 000000000..2c29ffff5 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java @@ -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)); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricDownloadTask.java new file mode 100644 index 000000000..03ea723ad --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricDownloadTask.java @@ -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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricMetaReader.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricMetaReader.java new file mode 100644 index 000000000..151c61c2a --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricMetaReader.java @@ -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; +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricUtils.java new file mode 100644 index 000000000..83f849a78 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/FabricUtils.java @@ -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 downloadLoaderVersionList(boolean onlyStable) throws IOException { + try { + return DownloadUtils.downloadStringCached(FABRIC_LOADER_METADATA_URL, + "fabric_loader_versions", (input)->{ + final List 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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeDownloadTask.java new file mode 100644 index 000000000..60dcd9b2d --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeDownloadTask.java @@ -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); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeUtils.java new file mode 100644 index 000000000..31420de90 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeUtils.java @@ -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 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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListAdapter.java new file mode 100644 index 000000000..4b96cd03e --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListAdapter.java @@ -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 mGameVersions; + private final List> mForgeVersions; + private final LayoutInflater mLayoutInflater; + + public ForgeVersionListAdapter(List 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 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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListHandler.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListHandler.java new file mode 100644 index 000000000..8de024ccd --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ForgeVersionListHandler.java @@ -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 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 getVersions() { + return mForgeVersions; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderDownloadListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderDownloadListener.java new file mode 100644 index 000000000..9260d4cbc --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderDownloadListener.java @@ -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); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderListenerProxy.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderListenerProxy.java new file mode 100644 index 000000000..97713c9e4 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/ModloaderListenerProxy.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OFDownloadPageScraper.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OFDownloadPageScraper.java new file mode 100644 index 000000000..62d74b2eb --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OFDownloadPageScraper.java @@ -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")); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineDownloadTask.java new file mode 100644 index 000000000..c5278340d --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineDownloadTask.java @@ -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(); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineScraper.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineScraper.java new file mode 100644 index 000000000..56a89bd02 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineScraper.java @@ -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 { + private final OptiFineUtils.OptiFineVersions mOptiFineVersions; + private List 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<>(); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineUtils.java new file mode 100644 index 000000000..32cf9938b --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineUtils.java @@ -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 minecraftVersions; + public List> optifineVersions; + } + public static class OptiFineVersion { + public String minecraftVersion; + public String versionName; + public String downloadUrl; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineVersionListAdapter.java new file mode 100644 index 000000000..a63111398 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/OptiFineVersionListAdapter.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java index a9b719441..a3a9266da 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java @@ -29,25 +29,17 @@ import fr.spse.extended_view.ExtendedTextView; */ public class ProfileAdapter extends BaseAdapter { private Map mProfiles; - public static final String CREATE_PROFILE_MAGIC = "___extra____profile-create"; private final MinecraftProfile dummy = new MinecraftProfile(); - private MinecraftProfile mCreateProfile; private List 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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapterExtra.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapterExtra.java new file mode 100644 index 000000000..b09d39b5c --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapterExtra.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java index 5cca10434..ccbd05470 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionListAdapter.java index 28c1ab700..6b54d0474 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionListAdapter.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionListAdapter.java @@ -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 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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorDialog.java new file mode 100644 index 000000000..c88714c7f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorDialog.java @@ -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; + }); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorListener.java new file mode 100644 index 000000000..0d48a9207 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/VersionSelectorListener.java @@ -0,0 +1,5 @@ +package net.kdt.pojavlaunch.profiles; + +public interface VersionSelectorListener { + void onVersionSelected(String versionId, boolean isSnapshot); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/progresskeeper/ProgressKeeper.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/progresskeeper/ProgressKeeper.java index 1c5e837b7..e5a3b31db 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/progresskeeper/ProgressKeeper.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/progresskeeper/ProgressKeeper.java @@ -100,4 +100,8 @@ public class ProgressKeeper { public static synchronized int getTaskCount() { return sProgressStates.size(); } + + public static boolean hasOngoingTasks() { + return getTaskCount() > 0; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java index a086701d7..b5fc6396b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/services/ProgressService.java @@ -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()); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncAssetManager.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncAssetManager.java index dffd6a899..0b13f357d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncAssetManager.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncAssetManager.java @@ -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 ); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncMinecraftDownloader.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncMinecraftDownloader.java index 1f43db675..74112fefd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncMinecraftDownloader.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncMinecraftDownloader.java @@ -46,7 +46,7 @@ public class AsyncMinecraftDownloader { /* Allows each downloading thread to have its own RECYCLED buffer */ private final ConcurrentHashMap 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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java index 02f0dc0af..0a18ac0fb 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java index ed4767fca..73c702ace 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java @@ -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 downloadStringCached(String url, String cacheName, ParseCallback 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 process(String input) throws ParseException; + } + public static class ParseException extends Exception { + public ParseException(Exception e) { + super(e); + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index 6b537a6be..b2e98cf4f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -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 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 additionalArguments = new ArrayList<>(); for(String arg : overridableArguments) { diff --git a/app_pojavlauncher/src/main/jni/stdio_is.c b/app_pojavlauncher/src/main/jni/stdio_is.c index 5986ed4f6..496c7c8a5 100644 --- a/app_pojavlauncher/src/main/jni/stdio_is.c +++ b/app_pojavlauncher/src/main/jni/stdio_is.c @@ -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)); diff --git a/app_pojavlauncher/src/main/res/drawable/ic_add_modded.xml b/app_pojavlauncher/src/main/res/drawable/ic_add_modded.xml new file mode 100644 index 000000000..70046c48f --- /dev/null +++ b/app_pojavlauncher/src/main/res/drawable/ic_add_modded.xml @@ -0,0 +1,5 @@ + + + diff --git a/app_pojavlauncher/src/main/res/layout/fragment_fabric_install.xml b/app_pojavlauncher/src/main/res/layout/fragment_fabric_install.xml new file mode 100644 index 000000000..cf75586d0 --- /dev/null +++ b/app_pojavlauncher/src/main/res/layout/fragment_fabric_install.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + +