Drag clicking skeleton + App icons

This commit is contained in:
downthecrop 2023-08-01 14:58:38 -07:00
parent a0df7b31a4
commit 920bcb7137
158 changed files with 37 additions and 16967 deletions

View file

@ -49,33 +49,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".LauncherActivity"
android:label="@string/app_short_name" />
<activity
android:name=".DummyLauncher"
android:screenOrientation="sensorLandscape"
android:label="@string/app_short_name" />
<activity
android:name=".ImportControlActivity"
android:configChanges="keyboard|keyboardHidden"
android:exported="true"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateVisible">
<intent-filter
android:label="@string/import_control_label"
android:scheme="content"
tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" />
<data android:mimeType="text/json" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".FatalErrorActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"
@ -106,20 +83,6 @@
android:process=":game"
android:screenOrientation="sensorLandscape" />
<provider
android:name=".scoped.FolderProvider"
android:authorities="@string/storageProviderAuthorities"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<service android:name=".services.ProgressService" />
<service
android:name=".services.GameService"
android:process=":game" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View file

@ -1,28 +0,0 @@
package com.kdt.mcgui;
import android.content.*;
import android.graphics.*;
import android.util.*;
import androidx.core.content.res.ResourcesCompat;
import net.kdt.pojavlaunch.R;
public class MineButton extends androidx.appcompat.widget.AppCompatButton {
public MineButton(Context ctx) {
this(ctx, null);
}
public MineButton(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
init();
}
public void init() {
setTypeface(ResourcesCompat.getFont(getContext(), R.font.noto_sans_bold));
setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.mine_button_background, null));
setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen._13ssp));
}
}

View file

@ -1,23 +0,0 @@
package com.kdt.mcgui;
import android.content.*;
import android.util.*;
import android.graphics.*;
import android.widget.EditText;
public class MineEditText extends androidx.appcompat.widget.AppCompatEditText {
public MineEditText(Context ctx) {
super(ctx);
init();
}
public MineEditText(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
init();
}
public void init() {
setBackgroundColor(Color.parseColor("#131313"));
setPadding(5, 5, 5, 5);
}
}

View file

@ -1,328 +0,0 @@
package com.kdt.mcgui;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatSpinner;
import net.kdt.pojavlaunch.PojavProfile;
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.authenticator.microsoft.PresentedException;
import net.kdt.pojavlaunch.authenticator.microsoft.MicrosoftBackgroundLogin;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.extra.ExtraListener;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import fr.spse.extended_view.ExtendedTextView;
public class mcAccountSpinner extends AppCompatSpinner implements AdapterView.OnItemSelectedListener {
public mcAccountSpinner(@NonNull Context context) {
this(context, null);
}
public mcAccountSpinner(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private final List<String> mAccountList = new ArrayList<>(2);
private MinecraftAccount mSelectecAccount = null;
/* Display the head of the current profile, here just to allow bitmap recycling */
private BitmapDrawable mHeadDrawable;
/* Current animator to for the login bar, is swapped when changing step */
private ObjectAnimator mLoginBarAnimator;
private float mLoginBarWidth = -1;
/* Paint used to display the bottom bar, to show the login progress. */
private final Paint mLoginBarPaint = new Paint();
/* When a login is performed in the background, we need to know where we are */
private final static int MAX_LOGIN_STEP = 5;
private int mLoginStep = 0;
/* Login listeners */
private final ProgressListener mProgressListener = step -> {
// Animate the login bar, cosmetic purposes only
mLoginStep = step;
if(mLoginBarAnimator != null){
mLoginBarAnimator.cancel();
mLoginBarAnimator.setFloatValues( mLoginBarWidth, (getWidth()/MAX_LOGIN_STEP * mLoginStep));
}else{
mLoginBarAnimator = ObjectAnimator.ofFloat(this, "LoginBarWidth", mLoginBarWidth, (getWidth()/MAX_LOGIN_STEP * mLoginStep));
}
mLoginBarAnimator.start();
};
private final DoneListener mDoneListener = account -> {
Toast.makeText(getContext(), R.string.main_login_done, Toast.LENGTH_SHORT).show();
// Check if the account being added is not one that is already existing
// Like login twice on the same mc account...
for(String mcAccountName : mAccountList){
if(mcAccountName.equals(account.username)) return;
}
mSelectecAccount = account;
invalidate();
mAccountList.add(account.username);
reloadAccounts(false, mAccountList.size() -1);
};
private final ErrorListener mErrorListener = errorMessage -> {
mLoginBarPaint.setColor(Color.RED);
if(errorMessage instanceof PresentedException) {
PresentedException exception = (PresentedException) errorMessage;
Tools.showError(getContext(), exception.toString(getContext()), exception.getCause());
}else {
Tools.showError(getContext(), errorMessage);
}
invalidate();
};
/* Triggered when we need to do microsoft login */
private final ExtraListener<Uri> mMicrosoftLoginListener = (key, value) -> {
mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
new MicrosoftBackgroundLogin(false, value.getQueryParameter("code")).performLogin(
mProgressListener, mDoneListener, mErrorListener);
return false;
};
/* Triggered when we need to perform mojang login */
private final ExtraListener<String[]> mMojangLoginListener = (key, value) -> {
if(value[1].isEmpty()){ // Test mode
MinecraftAccount account = new MinecraftAccount();
account.username = value[0];
try {
account.save();
}catch (IOException e){
Log.e("McAccountSpinner", "Failed to save the account : " + e);
}
mDoneListener.onLoginDone(account);
}
return false;
};
@SuppressLint("ClickableViewAccessibility")
private void init(){
// Set visual properties
setBackgroundColor(getResources().getColor(R.color.background_status_bar));
mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
mLoginBarPaint.setStrokeWidth(getResources().getDimensionPixelOffset(R.dimen._2sdp));
// Set behavior
reloadAccounts(true, 0);
setOnItemSelectedListener(this);
ExtraCore.addExtraListener(ExtraConstants.MOJANG_LOGIN_TODO, mMojangLoginListener);
ExtraCore.addExtraListener(ExtraConstants.MICROSOFT_LOGIN_TODO, mMicrosoftLoginListener);
}
@Override
public final void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if(position == 0){ // Add account button
if(mAccountList.size() > 1){
ExtraCore.setValue(ExtraConstants.SELECT_AUTH_METHOD, true);
}
return;
}
pickAccount(position);
if(mSelectecAccount != null)
performLogin(mSelectecAccount);
}
@Override
public final void onNothingSelected(AdapterView<?> parent) {}
@Override
protected void onDraw(Canvas canvas) {
if(mLoginBarWidth == -1) mLoginBarWidth = getWidth(); // Initial draw
float bottom = getHeight() - mLoginBarPaint.getStrokeWidth()/2;
canvas.drawLine(0, bottom, mLoginBarWidth, bottom, mLoginBarPaint);
}
public void removeCurrentAccount(){
int position = getSelectedItemPosition();
if(position == 0) return;
File accountFile = new File(Tools.DIR_ACCOUNT_NEW, mAccountList.get(position)+".json");
if(accountFile.exists()) accountFile.delete();
mAccountList.remove(position);
reloadAccounts(false, 0);
}
@Keep
public void setLoginBarWidth(float value){
mLoginBarWidth = value;
invalidate(); // Need to redraw each time this is changed
}
/** Allows checking whether we have an online account */
public boolean isAccountOnline(){
return mSelectecAccount != null && !mSelectecAccount.accessToken.equals("0");
}
public MinecraftAccount getSelectedAccount(){
return mSelectecAccount;
}
public int getLoginState(){
return mLoginStep;
}
public boolean isLoginDone(){
return mLoginStep >= MAX_LOGIN_STEP;
}
@SuppressLint("ClickableViewAccessibility")
private void setNoAccountBehavior(){
// Set custom behavior when no account are present, to make it act as a button
if(mAccountList.size() != 1){
// Remove any touch listener
setOnTouchListener(null);
return;
}
// Make the spinner act like a button, since there is no item to really select
setOnTouchListener((v, event) -> {
if(event.getAction() != MotionEvent.ACTION_UP) return false;
// The activity should intercept this and spawn another fragment
ExtraCore.setValue(ExtraConstants.SELECT_AUTH_METHOD, true);
return true;
});
}
/**
* Reload the spinner, from memory or from scratch. A default account can be selected
* @param fromFiles Whether we use files as the source of truth
* @param overridePosition Force the spinner to be at this position, if not 0
*/
private void reloadAccounts(boolean fromFiles, int overridePosition){
if(fromFiles){
mAccountList.clear();
mAccountList.add(getContext().getString(R.string.main_add_account));
File accountFolder = new File(Tools.DIR_ACCOUNT_NEW);
if(accountFolder.exists()){
for (String fileName : accountFolder.list()) {
mAccountList.add(fileName.substring(0, fileName.length() - 5));
}
}
}
String[] accountArray = mAccountList.toArray(new String[0]);
ArrayAdapter<String> accountAdapter = new ArrayAdapter<>(getContext(), R.layout.item_minecraft_account, accountArray);
accountAdapter.setDropDownViewResource(R.layout.item_minecraft_account);
setAdapter(accountAdapter);
// Pick what's available, might just be the the add account "button"
pickAccount(overridePosition == 0 ? -1 : overridePosition);
if(mSelectecAccount != null)
performLogin(mSelectecAccount);
// Remove or add the behavior if needed
setNoAccountBehavior();
}
private void performLogin(MinecraftAccount minecraftAccount){
if(minecraftAccount.isLocal()) return;
mLoginBarPaint.setColor(getResources().getColor(R.color.minebutton_color));
if(minecraftAccount.isMicrosoft){
if(System.currentTimeMillis() > minecraftAccount.expiresAt){
// Perform login only if needed
new MicrosoftBackgroundLogin(true, minecraftAccount.msaRefreshToken)
.performLogin(mProgressListener, mDoneListener, mErrorListener);
}
return;
}
}
/** Pick the selected account, the one in settings if 0 is passed */
private void pickAccount(int position){
MinecraftAccount selectedAccount;
if(position != -1){
PojavProfile.setCurrentProfile(getContext(), mAccountList.get(position));
selectedAccount = PojavProfile.getCurrentProfileContent(getContext(), mAccountList.get(position));
// WORKAROUND
// Account file corrupted due to previous versions having improper encoding
if (selectedAccount == null){
removeCurrentAccount();
pickAccount(-1);
setSelection(0);
return;
}
setSelection(position);
}else {
// Get the current profile, or the first available profile if the wanted one is unavailable
selectedAccount = PojavProfile.getCurrentProfileContent(getContext(), null);
int spinnerPosition = selectedAccount == null
? mAccountList.size() <= 1 ? 0 : 1
: mAccountList.indexOf(selectedAccount.username);
setSelection(spinnerPosition, false);
}
mSelectecAccount = selectedAccount;
BitmapDrawable oldBitmapDrawable = mHeadDrawable;
if(mSelectecAccount != null){
ExtendedTextView view = ((ExtendedTextView) getSelectedView());
if(view != null){
Bitmap bitmap = mSelectecAccount.getSkinFace();
if(bitmap != null) {
mHeadDrawable = new BitmapDrawable(bitmap);
mHeadDrawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
view.setCompoundDrawables(mHeadDrawable, null, null, null);
view.postProcessDrawables();
}else{
view.setCompoundDrawables(null, null, null, null);
view.postProcessDrawables();
}
}
}
if(oldBitmapDrawable != null){
oldBitmapDrawable.getBitmap().recycle();
}
}
}

View file

@ -1,180 +0,0 @@
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.transition.Slide;
import android.transition.Transition;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;
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.profiles.ProfileAdapterExtra;
import fr.spse.extended_view.ExtendedTextView;
/**
* A class implementing custom spinner like behavior, notably:
* 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();
}
public mcVersionSpinner(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public mcVersionSpinner(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/* 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 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 */
public void setProfileSelection(int position){
setSelection(position);
LauncherPreferences.DEFAULT_PREF.edit()
.putString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,
mProfileAdapter.getItem(position).toString())
.apply();
}
public void setSelection(int position){
if(mListView != null) mListView.setSelection(position);
mProfileAdapter.setViewProfile(this, (String) mProfileAdapter.getItem(position), false);
}
/** Initialize various behaviors */
private void init(){
// Setup various attributes
setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen._12ssp));
setGravity(Gravity.CENTER_VERTICAL);
int startPadding = getContext().getResources().getDimensionPixelOffset(R.dimen._17sdp);
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){
profileIndex = extra_value.equals(DELETED_PROFILE) ? 0
: getProfileAdapter().resolveProfileIndex(extra_value);
}else
profileIndex = mProfileAdapter.resolveProfileIndex(
LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,""));
setProfileSelection(Math.max(0,profileIndex));
// Popup window behavior
setOnClickListener(new OnClickListener() {
final int offset = -getContext().getResources().getDimensionPixelOffset(R.dimen._4sdp);
@Override
public void onClick(View v) {
if(mPopupWindow == null) getPopupWindow();
if(mPopupWindow.isShowing()){
mPopupWindow.dismiss();
return;
}
mPopupWindow.showAsDropDown(mcVersionSpinner.this, 0, offset);
}
});
}
/** Create the listView and popup window for the interface, and set up the click behavior */
@SuppressLint("ClickableViewAccessibility")
private void getPopupWindow(){
mListView = (ListView) inflate(getContext(), R.layout.spinner_mc_version, null);
mListView.setAdapter(mProfileAdapter);
mListView.setOnItemClickListener((parent, view, position, id) -> {
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;
}
}
});
mPopupWindow = new PopupWindow(mListView, MATCH_PARENT, getContext().getResources().getDimensionPixelOffset(R.dimen._184sdp));
mPopupWindow.setElevation(5);
mPopupWindow.setClippingEnabled(false);
// Block clicking outside of the popup window
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setFocusable(true);
mPopupWindow.setTouchInterceptor((v, event) -> {
if(event.getAction() == MotionEvent.ACTION_OUTSIDE){
mPopupWindow.dismiss();
return true;
}
return false;
});
// Custom animation, nice slide in
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
mPopupAnimation = new Slide(Gravity.BOTTOM);
mPopupWindow.setEnterTransition((Transition) mPopupAnimation);
mPopupWindow.setExitTransition((Transition) mPopupAnimation);
}
}
private void hidePopup(boolean animate) {
if(mPopupWindow == null) return;
if(!animate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mPopupWindow.setEnterTransition(null);
mPopupWindow.setExitTransition(null);
mPopupWindow.dismiss();
mPopupWindow.setEnterTransition((Transition) mPopupAnimation);
mPopupWindow.setExitTransition((Transition) mPopupAnimation);
}else {
mPopupWindow.dismiss();
}
}
public ProfileAdapter getProfileAdapter() {
return mProfileAdapter;
}
}

View file

@ -24,7 +24,7 @@ public class FatalErrorActivity extends AppCompatActivity {
.setTitle(R.string.error_fatal)
.setMessage(errHeader + "\n\n" + stackTrace)
.setPositiveButton(android.R.string.ok, (p1, p2) -> finish())
.setNegativeButton(R.string.global_restart, (p1, p2) -> startActivity(new Intent(FatalErrorActivity.this, LauncherActivity.class)))
.setNegativeButton(R.string.global_restart, (p1, p2) -> startActivity(new Intent(FatalErrorActivity.this, DummyLauncher.class)))
.setNeutralButton(android.R.string.copy, (p1, p2) -> {
ClipboardManager mgr = (ClipboardManager) FatalErrorActivity.this.getSystemService(CLIPBOARD_SERVICE);
mgr.setPrimaryClip(ClipData.newPlainText("error", stackTrace));

View file

@ -1,187 +0,0 @@
package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.utils.FileUtils;
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* An activity dedicated to importing control files.
*/
@SuppressWarnings("IOStreamConstructor")
public class ImportControlActivity extends Activity {
private Uri mUriData;
private boolean mHasIntentChanged = true;
private volatile boolean mIsFileVerified = false;
private EditText mEditText;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Tools.initContextConstants(getApplicationContext());
setContentView(R.layout.activity_import_control);
mEditText = findViewById(R.id.editText_import_control_file_name);
}
/**
* Override the previous loaded intent
* @param intent the intent used to replace the old one.
*/
@Override
protected void onNewIntent(Intent intent) {
if(intent != null) setIntent(intent);
mHasIntentChanged = true;
}
/**
* Update all over again if the intent changed.
*/
@Override
protected void onPostResume() {
super.onPostResume();
if(!mHasIntentChanged) return;
mIsFileVerified = false;
getUriData();
if(mUriData == null) {
finishAndRemoveTask();
return;
}
mEditText.setText(trimFileName(Tools.getFileName(this, mUriData)));
mHasIntentChanged = false;
//Import and verify thread
//Kill the app if the file isn't valid.
new Thread(() -> {
importControlFile();
if(verify())mIsFileVerified = true;
else runOnUiThread(() -> {
Toast.makeText(
ImportControlActivity.this,
getText(R.string.import_control_invalid_file),
Toast.LENGTH_SHORT).show();
finishAndRemoveTask();
});
}).start();
//Auto show the keyboard
Tools.MAIN_HANDLER.postDelayed(() -> {
InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
mEditText.setSelection(mEditText.getText().length());
}, 100);
}
/**
* Start the import.
* @param view the view which called the function
*/
public void startImport(View view) {
String fileName = trimFileName(mEditText.getText().toString());
//Step 1 check for suffixes.
if(!isFileNameValid(fileName)){
Toast.makeText(this, getText(R.string.import_control_invalid_name), Toast.LENGTH_SHORT).show();
return;
}
if(!mIsFileVerified){
Toast.makeText(this, getText(R.string.import_control_verifying_file), Toast.LENGTH_LONG).show();
return;
}
new File(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json").renameTo(new File(Tools.CTRLMAP_PATH + "/" + fileName + ".json"));
Toast.makeText(getApplicationContext(), getText(R.string.import_control_done), Toast.LENGTH_SHORT).show();
finishAndRemoveTask();
}
/**
* Copy a the file from the Intent data with a provided name into the controlmap folder.
*/
private void importControlFile(){
InputStream is;
try {
is = getContentResolver().openInputStream(mUriData);
OutputStream os = new FileOutputStream(Tools.CTRLMAP_PATH + "/" + "TMP_IMPORT_FILE" + ".json");
IOUtils.copy(is, os);
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Tell if the clean version of the filename is valid.
* @param fileName the string to test
* @return whether the filename is valid
*/
private static boolean isFileNameValid(String fileName){
fileName = trimFileName(fileName);
if(fileName.isEmpty()) return false;
return !FileUtils.exists(Tools.CTRLMAP_PATH + "/" + fileName + ".json");
}
/**
* Remove or undesirable chars from the string
* @param fileName The string to trim
* @return The trimmed string
*/
private static String trimFileName(String fileName){
return fileName
.replace(".json", "")
.replaceAll("%..", "/")
.replace("/", "")
.replace("\\", "")
.trim();
}
/**
* Tries to get an Uri from the various sources
*/
private void getUriData(){
mUriData = getIntent().getData();
if(mUriData != null) return;
try {
mUriData = getIntent().getClipData().getItemAt(0).getUri();
}catch (Exception ignored){}
}
/**
* Verify if the control file is valid
* @return Whether the control file is valid
*/
private static boolean verify(){
try{
String jsonLayoutData = Tools.read(Tools.CTRLMAP_PATH + "/TMP_IMPORT_FILE.json");
JSONObject layoutJobj = new JSONObject(jsonLayoutData);
return layoutJobj.has("version") && layoutJobj.has("mControlDataList");
}catch (JSONException | IOException e) {
e.printStackTrace();
return false;
}
}
}

View file

@ -2,7 +2,6 @@ package net.kdt.pojavlaunch;
import androidx.annotation.Keep;
import java.util.*;
import net.kdt.pojavlaunch.value.*;
@Keep
@SuppressWarnings("unused") // all unused fields here are parts of JSON structures
@ -23,10 +22,8 @@ public class JMinecraftVersionList {
public AssetIndex assetIndex;
public String assets;
public Map<String, MinecraftClientInfo> downloads;
public String inheritsFrom;
public JavaVersionInfo javaVersion;
public DependentLibrary[] libraries;
public LoggingConfig logging;
public String mainClass;
public String minecraftArguments;

View file

@ -8,8 +8,6 @@ import android.util.Log;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import java.io.IOException;
@ -49,46 +47,6 @@ public class JRE17Util {
return NEW_JRE_NAME.equals(runtime.name);
}
/** @return true if everything is good, false otherwise. */
public static boolean installNewJreIfNeeded(Activity activity, JMinecraftVersionList.Version versionInfo) {
//Now we have the reliable information to check if our runtime settings are good enough
if (versionInfo.javaVersion == null || versionInfo.javaVersion.component.equalsIgnoreCase("jre-legacy"))
return true;
LauncherProfiles.update();
MinecraftProfile minecraftProfile = LauncherProfiles.getCurrentProfile();
String selectedRuntime = Tools.getSelectedRuntime(minecraftProfile);
Runtime runtime = MultiRTUtils.read(selectedRuntime);
if (runtime.javaVersion >= versionInfo.javaVersion.majorVersion) {
return true;
}
String appropriateRuntime = MultiRTUtils.getNearestJreName(versionInfo.javaVersion.majorVersion);
if (appropriateRuntime != null) {
if (JRE17Util.isInternalNewJRE(appropriateRuntime)) {
JRE17Util.checkInternalNewJre(activity.getAssets());
}
minecraftProfile.javaDir = Tools.LAUNCHERPROFILES_RTPREFIX + appropriateRuntime;
LauncherProfiles.update();
} else {
if (versionInfo.javaVersion.majorVersion <= 17) { // there's a chance we have an internal one for this case
if (!JRE17Util.checkInternalNewJre(activity.getAssets())){
showRuntimeFail(activity, versionInfo);
return false;
} else {
minecraftProfile.javaDir = Tools.LAUNCHERPROFILES_RTPREFIX + JRE17Util.NEW_JRE_NAME;
LauncherProfiles.update();
}
} else {
showRuntimeFail(activity, versionInfo);
return false;
}
}
return true;
}
private static void showRuntimeFail(Activity activity, JMinecraftVersionList.Version verInfo) {
Tools.dialogOnUiThread(activity, activity.getString(R.string.global_error),

View file

@ -1,305 +0,0 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MainActivity.INTENT_MINECRAFT_VERSION;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import androidx.fragment.app.FragmentManager;
import com.kdt.mcgui.ProgressLayout;
import com.kdt.mcgui.mcAccountSpinner;
import net.kdt.pojavlaunch.fragments.MainMenuFragment;
import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.extra.ExtraListener;
import net.kdt.pojavlaunch.fragments.SelectAuthFragment;
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.services.ProgressServiceKeeper;
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
import net.kdt.pojavlaunch.tasks.AsyncVersionList;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
public class LauncherActivity extends BaseActivity {
public static final String SETTING_FRAGMENT_TAG = "SETTINGS_FRAGMENT";
private final int REQUEST_STORAGE_REQUEST_CODE = 1;
private final Object mLockStoragePerm = new Object();
private mcAccountSpinner mAccountSpinner;
private FragmentContainerView mFragmentView;
private ImageButton mSettingsButton, mDeleteAccountButton;
private ProgressLayout mProgressLayout;
private ProgressServiceKeeper mProgressServiceKeeper;
/* Allows to switch from one button "type" to another */
private final FragmentManager.FragmentLifecycleCallbacks mFragmentCallbackListener = new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
mSettingsButton.setImageDrawable(ContextCompat.getDrawable(getBaseContext(), f instanceof MainMenuFragment
? R.drawable.ic_menu_settings : R.drawable.ic_menu_home));
}
};
/* Listener for the back button in settings */
private final ExtraListener<String> mBackPreferenceListener = (key, value) -> {
if(value.equals("true")) onBackPressed();
return false;
};
/* Listener for the auth method selection screen */
private final ExtraListener<Boolean> mSelectAuthMethod = (key, value) -> {
Fragment fragment = getSupportFragmentManager().findFragmentById(mFragmentView.getId());
// Allow starting the add account only from the main menu, should it be moved to fragment itself ?
if(!(fragment instanceof MainMenuFragment)) return false;
Tools.swapFragment(this, SelectAuthFragment.class, SelectAuthFragment.TAG, true, null);
return false;
};
/* Listener for the settings fragment */
private final View.OnClickListener mSettingButtonListener = v -> {
Fragment fragment = getSupportFragmentManager().findFragmentById(mFragmentView.getId());
if(fragment instanceof MainMenuFragment){
Tools.swapFragment(this, LauncherPreferenceFragment.class, SETTING_FRAGMENT_TAG, true, null);
} else{
// The setting button doubles as a home button now
while(!(getSupportFragmentManager().findFragmentById(mFragmentView.getId()) instanceof MainMenuFragment)){
getSupportFragmentManager().popBackStackImmediate();
}
}
};
/* Listener for account deletion */
private final View.OnClickListener mAccountDeleteButtonListener = v -> new AlertDialog.Builder(this)
.setMessage(R.string.warning_remove_account)
.setPositiveButton(android.R.string.cancel, null)
.setNeutralButton(R.string.global_delete, (dialog, which) -> mAccountSpinner.removeCurrentAccount())
.show();
private final ExtraListener<Boolean> mLaunchGameListener = (key, value) -> {
if(mProgressLayout.hasProcesses()){
Toast.makeText(this, R.string.tasks_ongoing, Toast.LENGTH_LONG).show();
return false;
}
String selectedProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"");
if (LauncherProfiles.mainProfileJson == null || !LauncherProfiles.mainProfileJson.profiles.containsKey(selectedProfile)){
Toast.makeText(this, R.string.error_no_version, Toast.LENGTH_LONG).show();
return false;
}
MinecraftProfile prof = LauncherProfiles.mainProfileJson.profiles.get(selectedProfile);
if (prof == null || prof.lastVersionId == null || "Unknown".equals(prof.lastVersionId)){
Toast.makeText(this, R.string.error_no_version, Toast.LENGTH_LONG).show();
return false;
}
if(mAccountSpinner.getSelectedAccount() == null){
Toast.makeText(this, R.string.no_saved_accounts, Toast.LENGTH_LONG).show();
ExtraCore.setValue(ExtraConstants.SELECT_AUTH_METHOD, true);
return false;
}
String normalizedVersionId = AsyncMinecraftDownloader.normalizeVersionId(prof.lastVersionId);
JMinecraftVersionList.Version mcVersion = AsyncMinecraftDownloader.getListedVersion(normalizedVersionId);
new AsyncMinecraftDownloader(this, mcVersion, normalizedVersionId, new AsyncMinecraftDownloader.DoneListener() {
@Override
public void onDownloadDone() {
ProgressKeeper.waitUntilDone(()-> runOnUiThread(() -> {
try {
Intent mainIntent = new Intent(getBaseContext(), MainActivity.class);
mainIntent.putExtra(INTENT_MINECRAFT_VERSION, normalizedVersionId);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(mainIntent);
finish();
android.os.Process.killProcess(android.os.Process.myPid()); //You should kill yourself, NOW!
} catch (Throwable e) {
Tools.showError(getBaseContext(), e);
}
}));
}
@Override
public void onDownloadFailed(Throwable th) {
if(th != null) Tools.showError(LauncherActivity.this, R.string.mc_download_failed, th);
}
});
return false;
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pojav_launcher);
getWindow().setBackgroundDrawable(null);
bindViews();
ProgressKeeper.addTaskCountListener((mProgressServiceKeeper = new ProgressServiceKeeper(this)));
askForStoragePermission(); // Will wait here
mSettingsButton.setOnClickListener(mSettingButtonListener);
mDeleteAccountButton.setOnClickListener(mAccountDeleteButtonListener);
ProgressKeeper.addTaskCountListener(mProgressLayout);
ExtraCore.addExtraListener(ExtraConstants.BACK_PREFERENCE, mBackPreferenceListener);
ExtraCore.addExtraListener(ExtraConstants.SELECT_AUTH_METHOD, mSelectAuthMethod);
ExtraCore.addExtraListener(ExtraConstants.LAUNCH_GAME, mLaunchGameListener);
new AsyncVersionList().getVersionList(versions -> ExtraCore.setValue(ExtraConstants.RELEASE_TABLE, versions), false);
mProgressLayout.observe(ProgressLayout.DOWNLOAD_MINECRAFT);
mProgressLayout.observe(ProgressLayout.UNPACK_RUNTIME);
mProgressLayout.observe(ProgressLayout.INSTALL_MODPACK);
mProgressLayout.observe(ProgressLayout.AUTHENTICATE_MICROSOFT);
mProgressLayout.observe(ProgressLayout.DOWNLOAD_VERSION_LIST);
}
@Override
public boolean setFullscreen() {
return false;
}
@Override
protected void onStart() {
super.onStart();
getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentCallbackListener, true);
}
@Override
protected void onDestroy() {
super.onDestroy();
mProgressLayout.cleanUpObservers();
ProgressKeeper.removeTaskCountListener(mProgressLayout);
ProgressKeeper.removeTaskCountListener(mProgressServiceKeeper);
ExtraCore.removeExtraListenerFromValue(ExtraConstants.BACK_PREFERENCE, mBackPreferenceListener);
ExtraCore.removeExtraListenerFromValue(ExtraConstants.SELECT_AUTH_METHOD, mSelectAuthMethod);
ExtraCore.removeExtraListenerFromValue(ExtraConstants.LAUNCH_GAME, mLaunchGameListener);
getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(mFragmentCallbackListener);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode != RESULT_OK) return;
if(requestCode == Tools.RUN_MOD_INSTALLER && data != null){
Tools.launchModInstaller(this, data);
return;
}
if(requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME && data != null){
Tools.installRuntimeFromUri(this, data.getData());
}
}
/** Custom implementation to feel more natural when a backstack isn't present */
@Override
public void onBackPressed() {
MicrosoftLoginFragment fragment = (MicrosoftLoginFragment) getVisibleFragment(MicrosoftLoginFragment.TAG);
if(fragment != null){
if(fragment.canGoBack()){
fragment.goBack();
return;
}
}
super.onBackPressed();
}
@Override
public void onAttachedToWindow() {
LauncherPreferences.computeNotchSize(this);
}
@SuppressWarnings("SameParameterValue")
private Fragment getVisibleFragment(String tag){
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
if(fragment != null && fragment.isVisible()) {
return fragment;
}
return null;
}
@SuppressWarnings("unused")
private Fragment getVisibleFragment(int id){
Fragment fragment = getSupportFragmentManager().findFragmentById(id);
if(fragment != null && fragment.isVisible()) {
return fragment;
}
return null;
}
private void askForStoragePermission(){
int revokeCount = 0;
while (Build.VERSION.SDK_INT >= 23 && Build.VERSION.SDK_INT < 29 && !isStorageAllowed()) { //Do not ask for storage at all on Android 10+
try {
revokeCount++;
if (revokeCount >= 3) {
Toast.makeText(this, R.string.toast_permission_denied, Toast.LENGTH_LONG).show();
finish();
}
requestStoragePermission();
synchronized (mLockStoragePerm) {
mLockStoragePerm.wait();
}
} catch (InterruptedException e) {
Log.e("LauncherActivity", e.toString());
}
}
}
private boolean isStorageAllowed() {
//Getting the permission status
int result1 = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int result2 = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
//If permission is granted returning true
return result1 == PackageManager.PERMISSION_GRANTED &&
result2 == PackageManager.PERMISSION_GRANTED;
}
private void requestStoragePermission() {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_STORAGE_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_STORAGE_REQUEST_CODE) {
synchronized (mLockStoragePerm) {
mLockStoragePerm.notifyAll();
}
}
}
/** Stuff all the view boilerplate here */
private void bindViews(){
mFragmentView = findViewById(R.id.container_fragment);
mSettingsButton = findViewById(R.id.setting_button);
mDeleteAccountButton = findViewById(R.id.delete_account_button);
mAccountSpinner = findViewById(R.id.account_spinner);
mProgressLayout = findViewById(R.id.progress_layout);
}
}

View file

@ -14,7 +14,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
@ -48,12 +47,7 @@ import net.kdt.pojavlaunch.customcontrols.EditorExitable;
import net.kdt.pojavlaunch.customcontrols.keyboard.LwjglCharSender;
import net.kdt.pojavlaunch.customcontrols.keyboard.TouchCharInput;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.services.GameService;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import org.lwjgl.glfw.CallbackBridge;
@ -76,8 +70,6 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
private GyroControl mGyroControl = null;
public static ControlLayout mControlLayout;
MinecraftProfile minecraftProfile;
private ArrayAdapter<String> gameActionArrayAdapter;
private AdapterView.OnItemClickListener gameActionClickListener;
public ArrayAdapter<String> ingameControlsEditorArrayAdapter;
@ -86,7 +78,6 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GameService.startService(this);
initLayout(R.layout.activity_basemain);
CallbackBridge.addGrabListener(touchpad);
CallbackBridge.addGrabListener(minecraftGLView);

View file

@ -18,6 +18,7 @@ import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
@ -53,14 +54,15 @@ public class MinecraftGLSurface extends View implements GrabListener {
float startX = 0;
float startY = 0;
ScaleGestureDetector scaleGestureDetector;
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector longPressDetector;
/* The RemapperView.Builder object allows you to set which buttons to remap */
private final RemapperManager mInputManager = new RemapperManager(getContext(), new RemapperView.Builder(null)
.remapA(true)
.remapB(true)
.remapX(true)
.remapY(true)
.remapDpad(true)
.remapLeftJoystick(true)
.remapRightJoystick(true)
@ -215,6 +217,30 @@ public class MinecraftGLSurface extends View implements GrabListener {
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
});
this.longPressDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
boolean isDragClicking = false;
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
if(!isDragClicking) {
isDragClicking = true;
AWTInputBridge.sendKey((char)AWTInputEvent.VK_F5, AWTInputEvent.VK_F5);
}
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if(isDragClicking) {
isDragClicking = false;
AWTInputBridge.sendKey((char)AWTInputEvent.VK_F5, AWTInputEvent.VK_F5);
}
return super.onSingleTapUp(e);
}
});
//this.longPressDetector.setIsLongpressEnabled(true);
((ViewGroup)getParent()).addView(textureView);
}
}
@ -228,9 +254,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
@SuppressWarnings("accessibility")
public boolean onTouchEvent(MotionEvent e) {
scaleGestureDetector.onTouchEvent(e);
Log.i("downthecrop","ya I still see these events");
//longPressDetector.onTouchEvent(e);
// Kinda need to send this back to the layout
if(((ControlLayout)getParent()).getModifiable()) return false;
@ -252,7 +276,8 @@ public class MinecraftGLSurface extends View implements GrabListener {
CallbackBridge.mouseX = (e.getX() * mScaleFactor);
CallbackBridge.mouseY = (e.getY() * mScaleFactor);
//One android click = one MC click
if(mSingleTapDetector.onTouchEvent(e)){ // Touch Mode
if(mSingleTapDetector.onTouchEvent(e)){ //
//longPressDetector.onTouchEvent(e);// Touch Mode
CallbackBridge.putMouseEventWithCoords(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, CallbackBridge.mouseX, CallbackBridge.mouseY);
return true;
}
@ -397,25 +422,6 @@ public class MinecraftGLSurface extends View implements GrabListener {
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
}
break;
case MotionEvent.ACTION_POINTER_DOWN: // 5
//TODO Hey we could have some sort of middle click detection ?
mScrollLastInitialX = e.getX();
mScrollLastInitialY = e.getY();
//Checking if we are pressing the hotbar to select the item
hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1));
if(hudKeyHandled != -1){
sendKeyPress(hudKeyHandled);
if(hasDoubleTapped && hudKeyHandled == mLastHotbarKey){
//Prevent double tapping Event on two different slots
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
}
}
mLastHotbarKey = hudKeyHandled;
break;
}
// Actualise the pointer count

View file

@ -1,49 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.value.MinecraftAccount;
public class PojavProfile {
private static final String PROFILE_PREF = "pojav_profile";
private static final String PROFILE_PREF_FILE = "file";
public static SharedPreferences getPrefs(Context ctx) {
return ctx.getSharedPreferences(PROFILE_PREF, Context.MODE_PRIVATE);
}
public static MinecraftAccount getCurrentProfileContent(@NonNull Context ctx, @Nullable String profileName) {
return MinecraftAccount.load(profileName == null ? getCurrentProfileName(ctx) : profileName);
}
public static String getCurrentProfileName(Context ctx) {
String name = getPrefs(ctx).getString(PROFILE_PREF_FILE, "");
// A dirty fix
if (!name.isEmpty() && name.startsWith(Tools.DIR_ACCOUNT_NEW) && name.endsWith(".json")) {
name = name.substring(0, name.length() - 5).replace(Tools.DIR_ACCOUNT_NEW, "").replace(".json", "");
setCurrentProfile(ctx, name);
}
return name;
}
public static void setCurrentProfile(@NonNull Context ctx, @Nullable Object obj) {
SharedPreferences.Editor pref = getPrefs(ctx).edit();
try { if (obj instanceof String) {
String acc = (String) obj;
pref.putString(PROFILE_PREF_FILE, acc);
//MinecraftAccount.clearTempAccount();
} else if (obj == null) {
pref.putString(PROFILE_PREF_FILE, "");
} else {
throw new IllegalArgumentException("Profile must be String.class or null");
}
} finally {
pref.apply();
}
}
}

View file

@ -49,16 +49,11 @@ import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.JSONUtils;
import net.kdt.pojavlaunch.utils.OldVersionsUtils;
import net.kdt.pojavlaunch.value.DependentLibrary;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
@ -188,15 +183,6 @@ public final class Tools {
JREUtils.launchJavaVM(activity, runtime, gamedir, javaArgList, args);
}
public static File getGameDirPath(@NonNull MinecraftProfile minecraftProfile){
if(minecraftProfile.gameDir != null){
if(minecraftProfile.gameDir.startsWith(Tools.LAUNCHERPROFILES_RTPREFIX))
return new File(minecraftProfile.gameDir.replace(Tools.LAUNCHERPROFILES_RTPREFIX,Tools.DIR_GAME_HOME+"/"));
else
return new File(Tools.DIR_GAME_HOME,minecraftProfile.gameDir);
}
return new File(Tools.DIR_GAME_NEW);
}
public static void buildNotificationChannel(Context context){
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
@ -276,72 +262,6 @@ public final class Tools {
javaArgList.add(cacioClasspath.toString());
}
public static String[] getMinecraftJVMArgs(String versionName, File gameDir) {
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(versionName, true);
// Parse Forge 1.17+ additional JVM Arguments
if (versionInfo.inheritsFrom == null || versionInfo.arguments == null || versionInfo.arguments.jvm == null) {
return new String[0];
}
Map<String, String> varArgMap = new ArrayMap<>();
varArgMap.put("classpath_separator", ":");
varArgMap.put("library_directory", DIR_HOME_LIBRARY);
varArgMap.put("version_name", versionInfo.id);
varArgMap.put("natives_directory", Tools.NATIVE_LIB_DIR);
List<String> minecraftArgs = new ArrayList<>();
if (versionInfo.arguments != null) {
for (Object arg : versionInfo.arguments.jvm) {
if (arg instanceof String) {
minecraftArgs.add((String) arg);
} //TODO: implement (?maybe?)
}
}
return JSONUtils.insertJSONValueList(minecraftArgs.toArray(new String[0]), varArgMap);
}
public static String[] getMinecraftClientArgs(MinecraftAccount profile, JMinecraftVersionList.Version versionInfo, File gameDir) {
String username = profile.username;
String versionName = versionInfo.id;
if (versionInfo.inheritsFrom != null) {
versionName = versionInfo.inheritsFrom;
}
String userType = "mojang";
Map<String, String> varArgMap = new ArrayMap<>();
varArgMap.put("auth_session", profile.accessToken); // For legacy versions of MC
varArgMap.put("auth_access_token", profile.accessToken);
varArgMap.put("auth_player_name", username);
varArgMap.put("auth_uuid", profile.profileId.replace("-", ""));
varArgMap.put("auth_xuid", profile.xuid);
varArgMap.put("assets_root", Tools.ASSETS_PATH);
varArgMap.put("assets_index_name", versionInfo.assets);
varArgMap.put("game_assets", Tools.ASSETS_PATH);
varArgMap.put("game_directory", gameDir.getAbsolutePath());
varArgMap.put("user_properties", "{}");
varArgMap.put("user_type", userType);
varArgMap.put("version_name", versionName);
varArgMap.put("version_type", versionInfo.type);
List<String> minecraftArgs = new ArrayList<>();
if (versionInfo.arguments != null) {
// Support Minecraft 1.13+
for (Object arg : versionInfo.arguments.game) {
if (arg instanceof String) {
minecraftArgs.add((String) arg);
} //TODO: implement else clause
}
}
return JSONUtils.insertJSONValueList(
splitAndFilterEmpty(
versionInfo.minecraftArguments == null ?
fromStringArray(minecraftArgs.toArray(new String[0])):
versionInfo.minecraftArguments
), varArgMap
);
}
public static String fromStringArray(String[] strArr) {
StringBuilder builder = new StringBuilder();
@ -364,15 +284,6 @@ public final class Tools {
return strList.toArray(new String[0]);
}
public static String artifactToPath(DependentLibrary library) {
if (library.downloads != null &&
library.downloads.artifact != null &&
library.downloads.artifact.path != null)
return library.downloads.artifact.path;
String[] libInfos = library.name.split(":");
return libInfos[0].replaceAll("\\.", "/") + "/" + libInfos[1] + "/" + libInfos[2] + "/" + libInfos[1] + "-" + libInfos[2] + ".jar";
}
public static String getPatchedFile(String version) {
return DIR_HOME_VERSION + "/" + version + "/" + version + ".jar";
}
@ -394,27 +305,6 @@ public final class Tools {
}
private final static boolean isClientFirst = false;
public static String generateLaunchClassPath(JMinecraftVersionList.Version info,String actualname) {
StringBuilder libStr = new StringBuilder(); //versnDir + "/" + version + "/" + version + ".jar:";
String[] classpath = generateLibClasspath(info);
if (isClientFirst) {
libStr.append(getPatchedFile(actualname));
}
for (String perJar : classpath) {
if (!new File(perJar).exists()) {
Log.d(APP_NAME, "Ignored non-exists file: " + perJar);
continue;
}
libStr.append((isClientFirst ? ":" : "")).append(perJar).append(!isClientFirst ? ":" : "");
}
if (!isClientFirst) {
libStr.append(getPatchedFile(actualname));
}
return libStr.toString();
}
public static DisplayMetrics getDisplayMetrics(Activity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
@ -595,105 +485,9 @@ public final class Tools {
}
return true; // allow if none match
}
public static String[] generateLibClasspath(JMinecraftVersionList.Version info) {
List<String> libDir = new ArrayList<>();
for (DependentLibrary libItem: info.libraries) {
if(!checkRules(libItem.rules)) continue;
libDir.add(Tools.DIR_HOME_LIBRARY + "/" + artifactToPath(libItem));
}
return libDir.toArray(new String[0]);
}
public static JMinecraftVersionList.Version getVersionInfo(String versionName) {
return getVersionInfo(versionName, false);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static JMinecraftVersionList.Version getVersionInfo(String versionName, boolean skipInheriting) {
try {
JMinecraftVersionList.Version customVer = Tools.GLOBAL_GSON.fromJson(read(DIR_HOME_VERSION + "/" + versionName + "/" + versionName + ".json"), JMinecraftVersionList.Version.class);
if (skipInheriting || customVer.inheritsFrom == null || customVer.inheritsFrom.equals(customVer.id)) {
return customVer;
} else {
JMinecraftVersionList.Version inheritsVer;
//If it won't download, just search for it
try{
inheritsVer = Tools.GLOBAL_GSON.fromJson(read(DIR_HOME_VERSION + "/" + customVer.inheritsFrom + "/" + customVer.inheritsFrom + ".json"), JMinecraftVersionList.Version.class);
}catch(IOException e) {
throw new RuntimeException("Can't find the source version for "+ versionName +" (req version="+customVer.inheritsFrom+")");
}
//inheritsVer.inheritsFrom = inheritsVer.id;
insertSafety(inheritsVer, customVer,
"assetIndex", "assets", "id",
"mainClass", "minecraftArguments",
"releaseTime", "time", "type"
);
List<DependentLibrary> libList = new ArrayList<>(Arrays.asList(inheritsVer.libraries));
try {
loop_1:
for (DependentLibrary lib : customVer.libraries) {
String libName = lib.name.substring(0, lib.name.lastIndexOf(":"));
for (int i = 0; i < libList.size(); i++) {
DependentLibrary libAdded = libList.get(i);
String libAddedName = libAdded.name.substring(0, libAdded.name.lastIndexOf(":"));
if (libAddedName.equals(libName)) {
Log.d(APP_NAME, "Library " + libName + ": Replaced version " +
libName.substring(libName.lastIndexOf(":") + 1) + " with " +
libAddedName.substring(libAddedName.lastIndexOf(":") + 1));
libList.set(i, lib);
continue loop_1;
}
}
libList.add(0, lib);
}
} finally {
inheritsVer.libraries = libList.toArray(new DependentLibrary[0]);
}
// Inheriting Minecraft 1.13+ with append custom args
if (inheritsVer.arguments != null && customVer.arguments != null) {
List totalArgList = new ArrayList(Arrays.asList(inheritsVer.arguments.game));
int nskip = 0;
for (int i = 0; i < customVer.arguments.game.length; i++) {
if (nskip > 0) {
nskip--;
continue;
}
Object perCustomArg = customVer.arguments.game[i];
if (perCustomArg instanceof String) {
String perCustomArgStr = (String) perCustomArg;
// Check if there is a duplicate argument on combine
if (perCustomArgStr.startsWith("--") && totalArgList.contains(perCustomArgStr)) {
perCustomArg = customVer.arguments.game[i + 1];
if (perCustomArg instanceof String) {
perCustomArgStr = (String) perCustomArg;
// If the next is argument value, skip it
if (!perCustomArgStr.startsWith("--")) {
nskip++;
}
}
} else {
totalArgList.add(perCustomArgStr);
}
} else if (!totalArgList.contains(perCustomArg)) {
totalArgList.add(perCustomArg);
}
}
inheritsVer.arguments.game = totalArgList.toArray(new Object[0]);
}
return inheritsVer;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Prevent NullPointerException
private static void insertSafety(JMinecraftVersionList.Version targetVer, JMinecraftVersionList.Version fromVer, String... keyArr) {
@ -868,18 +662,6 @@ public final class Tools {
return fileName;
}
/** Swap the main fragment with another */
public static void swapFragment(FragmentActivity fragmentActivity , Class<? extends Fragment> fragmentClass,
@Nullable String fragmentTag, boolean addCurrentToBackstack, @Nullable Bundle bundle) {
// When people tab out, it might happen
//TODO handle custom animations
FragmentTransaction transaction = fragmentActivity.getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.container_fragment, fragmentClass, bundle, fragmentTag);
if(addCurrentToBackstack) transaction.addToBackStack(null);
transaction.commit();
}
/** Remove the current fragment */
public static void removeCurrentFragment(FragmentActivity fragmentActivity){
@ -994,34 +776,10 @@ public final class Tools {
return prefixedName.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length());
}
public static String getSelectedRuntime(MinecraftProfile minecraftProfile) {
String runtime = LauncherPreferences.PREF_DEFAULT_RUNTIME;
String profileRuntime = getRuntimeName(minecraftProfile.javaDir);
if(profileRuntime != null) {
if(MultiRTUtils.forceReread(profileRuntime).versionString != null) {
runtime = profileRuntime;
}
}
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);
Runtime pickedRuntime = MultiRTUtils.read(runtime);
if(runtime == null || pickedRuntime.javaVersion == 0 || pickedRuntime.javaVersion < targetJavaVersion) {
String preferredRuntime = MultiRTUtils.getNearestJreName(targetJavaVersion);
if(preferredRuntime == null) throw new RuntimeException("Failed to autopick runtime!");
if(profileRuntime != null) minecraftProfile.javaDir = Tools.LAUNCHERPROFILES_RTPREFIX+preferredRuntime;
runtime = preferredRuntime;
}
return runtime;
}
/** Triggers the share intent chooser, with the latestlog file attached to it */
public static void shareLog(Context context){
Uri contentUri = DocumentsContract.buildDocumentUri(context.getString(R.string.storageProviderAuthorities), Tools.DIR_GAME_HOME + "/latestlog.txt");

View file

@ -1,8 +0,0 @@
package net.kdt.pojavlaunch.authenticator.listener;
import net.kdt.pojavlaunch.value.MinecraftAccount;
/** Called when the login is done and the account received. guaranteed to be on the UI Thread */
public interface DoneListener {
void onLoginDone(MinecraftAccount account);
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch.authenticator.listener;
/** Called when there is a complete failure, guaranteed to be on the UI Thread */
public interface ErrorListener {
void onLoginError(Throwable errorMessage);
}

View file

@ -1,7 +0,0 @@
package net.kdt.pojavlaunch.authenticator.listener;
/** Called when a step is started, guaranteed to be in the UI Thread */
public interface ProgressListener {
/** */
void onLoginProgress(int step);
}

View file

@ -1,336 +0,0 @@
package net.kdt.pojavlaunch.authenticator.microsoft;
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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 org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
/** Allow to perform a background login on a given account */
// TODO handle connection errors !
public class MicrosoftBackgroundLogin {
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
private final boolean mIsRefresh;
private final String mAuthCode;
private static final Map<Long, Integer> XSTS_ERRORS;
static {
XSTS_ERRORS = new ArrayMap<>();
XSTS_ERRORS.put(2148916233L, R.string.xerr_no_account);
XSTS_ERRORS.put(2148916235L, R.string.xerr_not_available);
XSTS_ERRORS.put(2148916236L ,R.string.xerr_adult_verification);
XSTS_ERRORS.put(2148916237L ,R.string.xerr_adult_verification);
XSTS_ERRORS.put(2148916238L ,R.string.xerr_child);
}
/* Fields used to fill the account */
public String msRefreshToken;
public String mcName;
public String mcToken;
public String mcUuid;
public boolean doesOwnGame;
public long expiresAt;
public MicrosoftBackgroundLogin(boolean isRefresh, String authCode){
mIsRefresh = isRefresh;
mAuthCode = authCode;
}
/** Performs a full login, calling back listeners appropriately */
public void performLogin(@Nullable final ProgressListener progressListener,
@Nullable final DoneListener doneListener,
@Nullable final ErrorListener errorListener){
sExecutorService.execute(() -> {
try {
notifyProgress(progressListener, 1);
String accessToken = acquireAccessToken(mIsRefresh, mAuthCode);
notifyProgress(progressListener, 2);
String xboxLiveToken = acquireXBLToken(accessToken);
notifyProgress(progressListener, 3);
String[] xsts = acquireXsts(xboxLiveToken);
notifyProgress(progressListener, 4);
String mcToken = acquireMinecraftToken(xsts[0], xsts[1]);
notifyProgress(progressListener, 5);
checkMcProfile(mcToken);
MinecraftAccount acc = MinecraftAccount.load(mcName);
if(acc == null) acc = new MinecraftAccount();
if (doesOwnGame) {
acc.xuid = xsts[0];
acc.clientToken = "0"; /* FIXME */
acc.accessToken = mcToken;
acc.username = mcName;
acc.profileId = mcUuid;
acc.isMicrosoft = true;
acc.msaRefreshToken = msRefreshToken;
acc.expiresAt = expiresAt;
acc.updateSkinFace();
}
acc.save();
if(doneListener != null) {
MinecraftAccount finalAcc = acc;
Tools.runOnUiThread(() -> doneListener.onLoginDone(finalAcc));
}
}catch (Exception e){
Log.e("MicroAuth", e.toString());
if(errorListener != null)
Tools.runOnUiThread(() -> errorListener.onLoginError(e));
}
ProgressLayout.clearProgress(ProgressLayout.AUTHENTICATE_MICROSOFT);
});
}
public String acquireAccessToken(boolean isRefresh, String authcode) throws IOException, JSONException {
URL url = new URL(authTokenUrl);
Log.i("MicrosoftLogin", "isRefresh=" + isRefresh + ", authCode= "+authcode);
String formData = convertToFormData(
"client_id", "00000000402b5328",
isRefresh ? "refresh_token" : "code", authcode,
"grant_type", isRefresh ? "refresh_token" : "authorization_code",
"redirect_url", "https://login.live.com/oauth20_desktop.srf",
"scope", "service::user.auth.xboxlive.com::MBI_SSL"
);
Log.i("MicroAuth", formData);
//да пошла yf[eq1 она ваша джава 11
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(formData.getBytes(StandardCharsets.UTF_8).length));
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(formData.getBytes(StandardCharsets.UTF_8));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
msRefreshToken = jo.getString("refresh_token");
conn.disconnect();
Log.i("MicrosoftLogin","Acess Token = " + jo.getString("access_token"));
return jo.getString("access_token");
//acquireXBLToken(jo.getString("access_token"));
}else{
throw getResponseThrowable(conn);
}
}
private String acquireXBLToken(String accessToken) throws IOException, JSONException {
URL url = new URL(xblAuthUrl);
JSONObject data = new JSONObject();
JSONObject properties = new JSONObject();
properties.put("AuthMethod", "RPS");
properties.put("SiteName", "user.auth.xboxlive.com");
properties.put("RpsTicket", accessToken);
data.put("Properties",properties);
data.put("RelyingParty", "http://auth.xboxlive.com");
data.put("TokenType", "JWT");
String req = data.toString();
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
setCommonProperties(conn, req);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes(StandardCharsets.UTF_8));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
conn.disconnect();
Log.i("MicrosoftLogin","Xbl Token = "+jo.getString("Token"));
return jo.getString("Token");
//acquireXsts(jo.getString("Token"));
}else{
throw getResponseThrowable(conn);
}
}
/** @return [uhs, token]*/
private @NonNull String[] acquireXsts(String xblToken) throws IOException, JSONException {
URL url = new URL(xstsAuthUrl);
JSONObject data = new JSONObject();
JSONObject properties = new JSONObject();
properties.put("SandboxId", "RETAIL");
properties.put("UserTokens", new JSONArray(Collections.singleton(xblToken)));
data.put("Properties", properties);
data.put("RelyingParty", "rp://api.minecraftservices.com/");
data.put("TokenType", "JWT");
String req = data.toString();
Log.i("MicroAuth", req);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
setCommonProperties(conn, req);
Log.i("MicroAuth", conn.getRequestMethod());
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes(StandardCharsets.UTF_8));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
String uhs = jo.getJSONObject("DisplayClaims").getJSONArray("xui").getJSONObject(0).getString("uhs");
String token = jo.getString("Token");
conn.disconnect();
Log.i("MicrosoftLogin","Xbl Xsts = " + token + "; Uhs = " + uhs);
return new String[]{uhs, token};
//acquireMinecraftToken(uhs,jo.getString("Token"));
}else if(conn.getResponseCode() == 401) {
String responseContents = Tools.read(conn.getErrorStream());
JSONObject jo = new JSONObject(responseContents);
long xerr = jo.optLong("XErr", -1);
Integer locale_id = XSTS_ERRORS.get(xerr);
if(locale_id != null) {
throw new PresentedException(new RuntimeException(responseContents), locale_id);
}
throw new PresentedException(new RuntimeException(responseContents), R.string.xerr_unknown, xerr);
}else{
throw getResponseThrowable(conn);
}
}
private String acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, JSONException {
URL url = new URL(mcLoginUrl);
JSONObject data = new JSONObject();
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
String req = data.toString();
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
setCommonProperties(conn, req);
conn.connect();
try(OutputStream wr = conn.getOutputStream()) {
wr.write(req.getBytes(StandardCharsets.UTF_8));
}
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
expiresAt = System.currentTimeMillis() + 86400000;
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
conn.disconnect();
Log.i("MicrosoftLogin","MC token: "+jo.getString("access_token"));
mcToken = jo.getString("access_token");
//checkMcProfile(jo.getString("access_token"));
return jo.getString("access_token");
}else{
throw getResponseThrowable(conn);
}
}
private void checkMcProfile(String mcAccessToken) throws IOException, JSONException {
URL url = new URL(mcProfileUrl);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
conn.setUseCaches(false);
conn.connect();
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
String s= Tools.read(conn.getInputStream());
conn.disconnect();
Log.i("MicrosoftLogin","profile:" + s);
JSONObject jsonObject = new JSONObject(s);
String name = (String) jsonObject.get("name");
String uuid = (String) jsonObject.get("id");
String uuidDashes = uuid.replaceFirst(
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
);
doesOwnGame = true;
Log.i("MicrosoftLogin","UserName = " + name);
Log.i("MicrosoftLogin","Uuid Minecraft = " + uuidDashes);
mcName=name;
mcUuid=uuidDashes;
}else{
Log.i("MicrosoftLogin","It seems that this Microsoft Account does not own the game.");
doesOwnGame = false;
throw new PresentedException(new RuntimeException(conn.getResponseMessage()), R.string.minecraft_not_owned);
//throwResponseError(conn);
}
}
/** Wrapper to ease notifying the listener */
private void notifyProgress(@Nullable ProgressListener listener, int step){
if(listener != null){
Tools.runOnUiThread(() -> listener.onLoginProgress(step));
}
ProgressLayout.setProgress(ProgressLayout.AUTHENTICATE_MICROSOFT, step*20);
}
/** Set common properties for the connection. Given that all requests are POST, interactivity is always enabled */
private static void setCommonProperties(HttpURLConnection conn, String formData) {
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("charset", "utf-8");
try {
conn.setRequestProperty("Content-Length", Integer.toString(formData.getBytes(StandardCharsets.UTF_8).length));
conn.setRequestMethod("POST");
}catch (ProtocolException e) {
Log.e("MicrosoftAuth", e.toString());
}
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
}
/**
* @param data A series a strings: key1, value1, key2, value2...
* @return the data converted as a form string for a POST request
*/
private static String convertToFormData(String... data) throws UnsupportedEncodingException {
StringBuilder builder = new StringBuilder();
for(int i=0; i<data.length; i+=2){
if (builder.length() > 0) builder.append("&");
builder.append(URLEncoder.encode(data[i], "UTF-8"))
.append("=")
.append(URLEncoder.encode(data[i+1], "UTF-8"));
}
return builder.toString();
}
private RuntimeException getResponseThrowable(HttpURLConnection conn) throws IOException {
Log.i("MicrosoftLogin", "Error code: " + conn.getResponseCode() + ": " + conn.getResponseMessage());
if(conn.getResponseCode() == 429) {
return new PresentedException(R.string.microsoft_login_retry_later);
}
return new RuntimeException(conn.getResponseMessage());
}
}

View file

@ -1,23 +0,0 @@
package net.kdt.pojavlaunch.authenticator.microsoft;
import android.content.Context;
public class PresentedException extends RuntimeException {
final int localizationStringId;
final Object[] extraArgs;
public PresentedException(int localizationStringId, Object... extraArgs) {
this.localizationStringId = localizationStringId;
this.extraArgs = extraArgs;
}
public PresentedException(Throwable throwable, int localizationStringId, Object... extraArgs) {
super(throwable);
this.localizationStringId = localizationStringId;
this.extraArgs = extraArgs;
}
public String toString(Context context) {
return context.getString(localizationStringId, extraArgs);
}
}

View file

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

View file

@ -1,111 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import android.app.AlertDialog;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.kdt.pickafile.FileListView;
import com.kdt.pickafile.FileSelectedListener;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import java.io.File;
public class FileSelectorFragment extends Fragment {
public static final String TAG = "FileSelectorFragment";
public static final String BUNDLE_SELECT_FOLDER = "select_folder";
public static final String BUNDLE_SHOW_FILE = "show_file";
public static final String BUNDLE_SHOW_FOLDER = "show_folder";
public static final String BUNDLE_ROOT_PATH = "root_path";
private Button mSelectFolderButton, mCreateFolderButton;
private FileListView mFileListView;
private TextView mFilePathView;
private boolean mSelectFolder = true;
private boolean mShowFiles = true;
private boolean mShowFolders = true;
private String mRootPath = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
? Tools.DIR_GAME_NEW
: Environment.getExternalStorageDirectory().getAbsolutePath();
public FileSelectorFragment(){
super(R.layout.fragment_file_selector);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
bindViews(view);
parseBundle();
if(!mSelectFolder) mSelectFolderButton.setVisibility(View.GONE);
else mSelectFolderButton.setVisibility(View.VISIBLE);
mFileListView.setShowFiles(mShowFiles);
mFileListView.setShowFolders(mShowFolders);
mFileListView.lockPathAt(new File(mRootPath));
mFileListView.setDialogTitleListener((title)->mFilePathView.setText(removeLockPath(title)));
mFileListView.refreshPath();
mCreateFolderButton.setOnClickListener(v -> {
final EditText editText = new EditText(getContext());
new AlertDialog.Builder(getContext())
.setTitle(R.string.folder_dialog_insert_name)
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.folder_dialog_create, (dialog, which) -> {
File folder = new File(mFileListView.getFullPath(), editText.getText().toString());
boolean success = folder.mkdir();
if(success){
mFileListView.listFileAt(new File(mFileListView.getFullPath(),editText.getText().toString()));
}else{
mFileListView.refreshPath();
}
}).show();
});
mSelectFolderButton.setOnClickListener(v -> {
ExtraCore.setValue(ExtraConstants.FILE_SELECTOR, removeLockPath(mFileListView.getFullPath().getAbsolutePath()));
Tools.removeCurrentFragment(requireActivity());
});
mFileListView.setFileSelectedListener(new FileSelectedListener() {
@Override
public void onFileSelected(File file, String path) {
ExtraCore.setValue(ExtraConstants.FILE_SELECTOR, removeLockPath(path));
Tools.removeCurrentFragment(requireActivity());
}
});
}
private String removeLockPath(String path){
return path.replace(mRootPath, ".");
}
private void parseBundle(){
Bundle bundle = getArguments();
if(bundle == null) return;
mSelectFolder = bundle.getBoolean(BUNDLE_SELECT_FOLDER, mSelectFolder);
mShowFiles = bundle.getBoolean(BUNDLE_SHOW_FILE, mShowFiles);
mShowFolders = bundle.getBoolean(BUNDLE_SHOW_FOLDER, mShowFolders);
mRootPath = bundle.getString(BUNDLE_ROOT_PATH, mRootPath);
}
private void bindViews(@NonNull View view){
mSelectFolderButton = view.findViewById(R.id.file_selector_select_folder);
mCreateFolderButton = view.findViewById(R.id.file_selector_create_folder);
mFileListView = view.findViewById(R.id.file_selector);
mFilePathView = view.findViewById(R.id.file_selector_current_path);
}
}

View file

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

View file

@ -1,53 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
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.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import java.io.File;
public class LocalLoginFragment extends Fragment {
public static final String TAG = "LOCAL_LOGIN_FRAGMENT";
private EditText mUsernameEditText;
public LocalLoginFragment(){
super(R.layout.fragment_local_login);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mUsernameEditText = view.findViewById(R.id.login_edit_email);
view.findViewById(R.id.login_button).setOnClickListener(v -> {
if(!checkEditText()) return;
ExtraCore.setValue(ExtraConstants.MOJANG_LOGIN_TODO, new String[]{
mUsernameEditText.getText().toString(), "" });
Tools.swapFragment(requireActivity(), MainMenuFragment.class, MainMenuFragment.TAG, false, null);
});
}
/** @return Whether the mail (and password) text are eligible to make an auth request */
private boolean checkEditText(){
String text = mUsernameEditText.getText().toString();
return !(text.isEmpty()
|| text.length() < 3
|| text.length() > 16
|| !text.matches("\\w+")
|| new File(Tools.DIR_ACCOUNT_NEW + "/" + text + ".json").exists()
);
}
}

View file

@ -1,64 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import static net.kdt.pojavlaunch.Tools.shareLog;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.Toast;
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;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
public class MainMenuFragment extends Fragment {
public static final String TAG = "MainMenuFragment";
public MainMenuFragment(){
super(R.layout.fragment_launcher);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Button mNewsButton = view.findViewById(R.id.news_button);
Button mCustomControlButton = view.findViewById(R.id.custom_control_button);
Button mInstallJarButton = view.findViewById(R.id.install_jar_button);
Button mShareLogsButton = view.findViewById(R.id.share_logs_button);
ImageButton mEditProfileButton = view.findViewById(R.id.edit_profile_button);
Button mPlayButton = view.findViewById(R.id.play_button);
mNewsButton.setOnClickListener(v -> Tools.openURL(requireActivity(), Tools.URL_HOME));
mCustomControlButton.setOnClickListener(v -> startActivity(new Intent(requireContext(), CustomControlsActivity.class)));
mInstallJarButton.setOnClickListener(v -> runInstallerWithConfirmation(false));
mInstallJarButton.setOnLongClickListener(v->{
runInstallerWithConfirmation(true);
return true;
});
mEditProfileButton.setOnClickListener(v -> Tools.swapFragment(requireActivity(), ProfileEditorFragment.class, ProfileEditorFragment.TAG, true, null));
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)
Tools.installMod(requireActivity(), isCustomArgs);
else
Toast.makeText(requireContext(), R.string.tasks_ongoing, Toast.LENGTH_LONG).show();
}
}

View file

@ -1,89 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
public class MicrosoftLoginFragment extends Fragment {
public static final String TAG = "MICROSOFT_LOGIN_FRAGMENT";
private WebView mWebview;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mWebview = (WebView) inflater.inflate(R.layout.fragment_microsoft_login, container, false);
CookieManager.getInstance().removeAllCookies(this::onCookiesRemoved);
return mWebview;
}
@SuppressLint("SetJavaScriptEnabled") // required for Microsoft log-in
public void onCookiesRemoved(Boolean b) {
WebSettings settings = mWebview.getSettings();
settings.setJavaScriptEnabled(true);
mWebview.clearHistory();
mWebview.clearCache(true);
mWebview.clearFormData();
mWebview.clearHistory();
mWebview.setWebViewClient(new WebViewTrackClient());
mWebview.loadUrl("https://login.live.com/oauth20_authorize.srf" +
"?client_id=00000000402b5328" +
"&response_type=code" +
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
"&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf");
}
/* Expose webview actions to others */
public boolean canGoBack(){ return mWebview.canGoBack();}
public void goBack(){ mWebview.goBack();}
/** Client to track when to sent the data to the launcher */
class WebViewTrackClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.startsWith("ms-xal-00000000402b5328")) {
// Should be captured by the activity to kill the fragment and get
ExtraCore.setValue(ExtraConstants.MICROSOFT_LOGIN_TODO, Uri.parse(url));
Toast.makeText(view.getContext(), "Login started !", Toast.LENGTH_SHORT).show();
Tools.removeCurrentFragment(requireActivity());
return true;
}
// Sometimes, the user just clicked cancel
if(url.contains("res=cancel")){
requireActivity().onBackPressed();
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {}
@Override
public void onPageFinished(WebView view, String url) {}
}
}

View file

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

View file

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

View file

@ -1,214 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
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.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
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.VersionSelectorDialog;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class ProfileEditorFragment extends Fragment {
public static final String TAG = "ProfileEditorFragment";
public static final String DELETED_PROFILE = "deleted_profile";
private String mProfileKey;
private MinecraftProfile mTempProfile = null;
private String mValueToConsume = "";
private Button mSaveButton, mDeleteButton, mControlSelectButton, mGameDirButton, mVersionSelectButton;
private Spinner mDefaultRuntime, mDefaultRenderer;
private EditText mDefaultName, mDefaultJvmArgument;
private TextView mDefaultPath, mDefaultVersion, mDefaultControl;
private List<String> mRenderNames;
public ProfileEditorFragment(){
super(R.layout.fragment_profile_editor);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Paths, which can be changed
String value = (String) ExtraCore.consumeValue(ExtraConstants.FILE_SELECTOR);
if(value != null){
if(mValueToConsume.equals(FileSelectorFragment.BUNDLE_SELECT_FOLDER)){
mTempProfile.gameDir = value;
}else{
mTempProfile.controlFile = value;
}
}
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mRenderNames = Arrays.asList(getResources().getStringArray(R.array.renderer_values));
bindViews(view);
// Renderer spinner
List<String> renderList = new ArrayList<>(5);
Collections.addAll(renderList, getResources().getStringArray(R.array.renderer));
renderList.add("Default");
mDefaultRenderer.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, renderList));
// Set up behaviors
mSaveButton.setOnClickListener(v -> {
save();
Tools.removeCurrentFragment(requireActivity());
});
mDeleteButton.setOnClickListener(v -> {
LauncherProfiles.mainProfileJson.profiles.remove(mProfileKey);
LauncherProfiles.update();
ExtraCore.setValue(ExtraConstants.REFRESH_VERSION_SPINNER, DELETED_PROFILE);
Tools.removeCurrentFragment(requireActivity());
});
mGameDirButton.setOnClickListener(v -> {
Bundle bundle = new Bundle(2);
bundle.putBoolean(FileSelectorFragment.BUNDLE_SELECT_FOLDER, true);
bundle.putString(FileSelectorFragment.BUNDLE_ROOT_PATH, Tools.DIR_GAME_HOME);
bundle.putBoolean(FileSelectorFragment.BUNDLE_SHOW_FILE, false);
mValueToConsume = FileSelectorFragment.BUNDLE_SELECT_FOLDER;
Tools.swapFragment(requireActivity(),
FileSelectorFragment.class, FileSelectorFragment.TAG, true, bundle);
});
mControlSelectButton.setOnClickListener(v -> {
Bundle bundle = new Bundle(3);
bundle.putBoolean(FileSelectorFragment.BUNDLE_SELECT_FOLDER, false);
bundle.putString(FileSelectorFragment.BUNDLE_ROOT_PATH, Tools.CTRLMAP_PATH);
Tools.swapFragment(requireActivity(),
FileSelectorFragment.class, FileSelectorFragment.TAG, true, bundle);
});
// Setup the expendable list behavior
mVersionSelectButton.setOnClickListener(v -> VersionSelectorDialog.open(v.getContext(), false, (id, snapshot)->{
mTempProfile.lastVersionId = id;
mDefaultVersion.setText(id);
}));
loadValues(LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE, ""), view.getContext());
}
private void loadValues(@NonNull String profile, @NonNull Context context){
if(mTempProfile == null){
mTempProfile = getProfile(profile);
}
// Runtime spinner
List<Runtime> runtimes = MultiRTUtils.getRuntimes();
int jvmIndex = runtimes.indexOf(new Runtime("<Default>"));
if (mTempProfile.javaDir != null) {
String selectedRuntime = mTempProfile.javaDir.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length());
int nindex = runtimes.indexOf(new Runtime(selectedRuntime));
if (nindex != -1) jvmIndex = nindex;
}
mDefaultRuntime.setAdapter(new RTSpinnerAdapter(context, runtimes));
if(jvmIndex == -1) jvmIndex = runtimes.size() - 1;
mDefaultRuntime.setSelection(jvmIndex);
// Renderer spinner
int rendererIndex = mDefaultRenderer.getAdapter().getCount() - 1;
if(mTempProfile.pojavRendererName != null) {
int nindex = mRenderNames.indexOf(mTempProfile.pojavRendererName);
if(nindex != -1) rendererIndex = nindex;
}
mDefaultRenderer.setSelection(rendererIndex);
mDefaultVersion.setText(mTempProfile.lastVersionId);
mDefaultJvmArgument.setText(mTempProfile.javaArgs == null ? "" : mTempProfile.javaArgs);
mDefaultName.setText(mTempProfile.name);
mDefaultPath.setText(mTempProfile.gameDir == null ? "" : mTempProfile.gameDir);
mDefaultControl.setText(mTempProfile.controlFile == null ? "" : mTempProfile.controlFile);
}
private MinecraftProfile getProfile(@NonNull String profile){
MinecraftProfile minecraftProfile;
if(getArguments() == null) {
minecraftProfile = new MinecraftProfile(LauncherProfiles.mainProfileJson.profiles.get(profile));
mProfileKey = profile;
}else{
minecraftProfile = MinecraftProfile.createTemplate();
String uuid = UUID.randomUUID().toString();
while(LauncherProfiles.mainProfileJson.profiles.containsKey(uuid)) {
uuid = UUID.randomUUID().toString();
}
mProfileKey = uuid;
}
return minecraftProfile;
}
private void bindViews(@NonNull View view){
mDefaultControl = view.findViewById(R.id.vprof_editor_ctrl_spinner);
mDefaultRuntime = view.findViewById(R.id.vprof_editor_spinner_runtime);
mDefaultRenderer = view.findViewById(R.id.vprof_editor_profile_renderer);
mDefaultVersion = view.findViewById(R.id.vprof_editor_version_spinner);
mDefaultPath = view.findViewById(R.id.vprof_editor_path);
mDefaultName = view.findViewById(R.id.vprof_editor_profile_name);
mDefaultJvmArgument = view.findViewById(R.id.vprof_editor_jre_args);
mSaveButton = view.findViewById(R.id.vprof_editor_save_button);
mDeleteButton = view.findViewById(R.id.vprof_editor_delete_button);
mControlSelectButton = view.findViewById(R.id.vprof_editor_ctrl_button);
mVersionSelectButton = view.findViewById(R.id.vprof_editor_version_button);
mGameDirButton = view.findViewById(R.id.vprof_editor_path_button);
}
private void save(){
//First, check for potential issues in the inputs
mTempProfile.lastVersionId = mDefaultVersion.getText().toString();
mTempProfile.controlFile = mDefaultControl.getText().toString();
mTempProfile.name = mDefaultName.getText().toString();
mTempProfile.javaArgs = mDefaultJvmArgument.getText().toString();
mTempProfile.gameDir = mDefaultPath.getText().toString();
if(mTempProfile.controlFile.isEmpty()) mTempProfile.controlFile = null;
if(mTempProfile.javaArgs.isEmpty()) mTempProfile.javaArgs = null;
if(mTempProfile.gameDir.isEmpty()) mTempProfile.gameDir = null;
Runtime selectedRuntime = (Runtime) mDefaultRuntime.getSelectedItem();
mTempProfile.javaDir = (selectedRuntime.name.equals("<Default>") || selectedRuntime.versionString == null)
? null : Tools.LAUNCHERPROFILES_RTPREFIX + selectedRuntime.name;
if(mDefaultRenderer.getSelectedItemPosition() == mRenderNames.size()) mTempProfile.pojavRendererName = null;
else mTempProfile.pojavRendererName = mRenderNames.get(mDefaultRenderer.getSelectedItemPosition());
LauncherProfiles.mainProfileJson.profiles.put(mProfileKey, mTempProfile);
LauncherProfiles.update();
ExtraCore.setValue(ExtraConstants.REFRESH_VERSION_SPINNER, mProfileKey);
}
}

View file

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

View file

@ -1,29 +0,0 @@
package net.kdt.pojavlaunch.fragments;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
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 SelectAuthFragment extends Fragment {
public static final String TAG = "AUTH_SELECT_FRAGMENT";
public SelectAuthFragment(){
super(R.layout.fragment_select_auth_method);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Button mMicrosoftButton = view.findViewById(R.id.button_microsoft_authentication);
Button mLocalButton = view.findViewById(R.id.button_local_authentication);
mMicrosoftButton.setOnClickListener(v -> Tools.swapFragment(requireActivity(), MicrosoftLoginFragment.class, MicrosoftLoginFragment.TAG, false, null));
mLocalButton.setOnClickListener(v -> Tools.swapFragment(requireActivity(), LocalLoginFragment.class, LocalLoginFragment.TAG, false, null));
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,121 +0,0 @@
package net.kdt.pojavlaunch.modloaders;
import com.kdt.mcgui.ProgressLayout;
import net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class OptiFineDownloadTask implements Runnable, Tools.DownloaderFeedback, AsyncMinecraftDownloader.DoneListener {
private static final Pattern sMcVersionPattern = Pattern.compile("([0-9]+)\\.([0-9]+)\\.?([0-9]+)?");
private final OptiFineUtils.OptiFineVersion mOptiFineVersion;
private final File mDestinationFile;
private final ModloaderDownloadListener mListener;
private final Object mMinecraftDownloadLock = new Object();
private Throwable mDownloaderThrowable;
public OptiFineDownloadTask(OptiFineUtils.OptiFineVersion mOptiFineVersion, File mDestinationFile, ModloaderDownloadListener mListener) {
this.mOptiFineVersion = mOptiFineVersion;
this.mDestinationFile = mDestinationFile;
this.mListener = mListener;
}
@Override
public void run() {
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.of_dl_progress, mOptiFineVersion.versionName);
try {
if(runCatching()) mListener.onDownloadFinished(mDestinationFile);
}catch (IOException e) {
mListener.onDownloadError(e);
}
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, -1, -1);
}
public boolean runCatching() throws IOException {
String downloadUrl = scrapeDownloadsPage();
if(downloadUrl == null) return false;
String minecraftVersion = determineMinecraftVersion();
if(minecraftVersion == null) return false;
if(!downloadMinecraft(minecraftVersion)) {
if(mDownloaderThrowable instanceof Exception) {
mListener.onDownloadError((Exception) mDownloaderThrowable);
}else {
Exception exception = new Exception(mDownloaderThrowable);
mListener.onDownloadError(exception);
}
return false;
}
DownloadUtils.downloadFileMonitored(downloadUrl, mDestinationFile, new byte[8192], this);
return true;
}
public String scrapeDownloadsPage() throws IOException{
String scrapeResult = OFDownloadPageScraper.run(mOptiFineVersion.downloadUrl);
if(scrapeResult == null) mListener.onDataNotAvailable();
return scrapeResult;
}
public String determineMinecraftVersion() {
Matcher matcher = sMcVersionPattern.matcher(mOptiFineVersion.minecraftVersion);
if(matcher.find()) {
StringBuilder mcVersionBuilder = new StringBuilder();
mcVersionBuilder.append(matcher.group(1));
mcVersionBuilder.append('.');
mcVersionBuilder.append(matcher.group(2));
String thirdGroup = matcher.group(3);
if(thirdGroup != null && !thirdGroup.isEmpty() && !"0".equals(thirdGroup)) {
mcVersionBuilder.append('.');
mcVersionBuilder.append(thirdGroup);
}
return mcVersionBuilder.toString();
}else{
mListener.onDataNotAvailable();
return null;
}
}
public boolean downloadMinecraft(String minecraftVersion) {
// the string is always normalized
JMinecraftVersionList.Version minecraftJsonVersion = AsyncMinecraftDownloader.getListedVersion(minecraftVersion);
if(minecraftJsonVersion == null) return false;
try {
synchronized (mMinecraftDownloadLock) {
new AsyncMinecraftDownloader(null, minecraftJsonVersion, minecraftVersion, this);
mMinecraftDownloadLock.wait();
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return mDownloaderThrowable == null;
}
@Override
public void updateProgress(int curr, int max) {
int progress100 = (int)(((float)curr / (float)max)*100f);
ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.of_dl_progress, mOptiFineVersion.versionName);
}
@Override
public void onDownloadDone() {
synchronized (mMinecraftDownloadLock) {
mDownloaderThrowable = null;
mMinecraftDownloadLock.notifyAll();
}
}
@Override
public void onDownloadFailed(Throwable throwable) {
synchronized (mMinecraftDownloadLock) {
mDownloaderThrowable = throwable;
mMinecraftDownloadLock.notifyAll();
}
}
}

View file

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

View file

@ -1,40 +0,0 @@
package net.kdt.pojavlaunch.modloaders;
import android.content.Intent;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class OptiFineUtils {
public static OptiFineVersions downloadOptiFineVersions() throws IOException {
try {
return DownloadUtils.downloadStringCached("https://optifine.net/downloads",
"of_downloads_page", new OptiFineScraper());
}catch (DownloadUtils.ParseException e) {
e.printStackTrace();
return null;
}
}
public static void addAutoInstallArgs(Intent intent, File modInstallerJar) {
intent.putExtra("javaArgs", "-javaagent:"+ Tools.DIR_DATA+"/forge_installer/forge_installer.jar"
+ "=OFNPS" +// No Profile Suppression
" -jar "+modInstallerJar.getAbsolutePath());
intent.putExtra("skipDetectMod", true);
}
public static class OptiFineVersions {
public List<String> minecraftVersions;
public List<List<OptiFineVersion>> optifineVersions;
}
public static class OptiFineVersion {
public String minecraftVersion;
public String versionName;
public String downloadUrl;
}
}

View file

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

View file

@ -1,21 +0,0 @@
package net.kdt.pojavlaunch.plugins;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
public class FFmpegPlugin {
public static boolean isAvailable = false;
public static String libraryPath;
public static void discover(Context context) {
PackageManager manager = context.getPackageManager();
try {
PackageInfo ffmpegPluginInfo = manager.getPackageInfo("net.kdt.pojavlaunch.ffmpeg", PackageManager.GET_SHARED_LIBRARY_FILES);
libraryPath = ffmpegPluginInfo.applicationInfo.nativeLibraryDir;
isAvailable = true;
}catch (Exception e) {
Log.i("FFmpegPlugin", "Failed to discover plugin", e);
}
}
}

View file

@ -1,13 +1,10 @@
package net.kdt.pojavlaunch.prefs;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import androidx.preference.Preference;
import net.kdt.pojavlaunch.LauncherActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;

View file

@ -1,137 +0,0 @@
package net.kdt.pojavlaunch.profiles;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import androidx.core.graphics.ColorUtils;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.spse.extended_view.ExtendedTextView;
/*
* Adapter for listing launcher profiles in a Spinner
*/
public class ProfileAdapter extends BaseAdapter {
private Map<String, MinecraftProfile> mProfiles;
private final MinecraftProfile dummy = new MinecraftProfile();
private List<String> mProfileList;
private final ProfileAdapterExtra[] mExtraEntires;
public ProfileAdapter(Context context, ProfileAdapterExtra[] extraEntries) {
ProfileIconCache.initDefault(context);
LauncherProfiles.update();
mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles);
if(extraEntries == null) mExtraEntires = new ProfileAdapterExtra[0];
else mExtraEntires = extraEntries;
mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0])));
}
/*
* Gets how much profiles are loaded in the adapter right now
* @returns loaded profile count
*/
@Override
public int getCount() {
return mProfileList.size() + mExtraEntires.length;
}
/*
* Gets the profile at a given index
* @param position index to retreive
* @returns MinecraftProfile name or null
*/
@Override
public Object getItem(int position) {
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) {
return mProfileList.indexOf(name);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public void notifyDataSetChanged() {
mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles);
mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0])));
super.notifyDataSetChanged();
}
@Override
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);
Object profileObject = getItem(position);
if(profileObject instanceof String) setViewProfile(v, (String) profileObject, true);
else if(profileObject instanceof ProfileAdapterExtra) setViewExtra(v, (ProfileAdapterExtra) profileObject);
return v;
}
public void setViewProfile(View v, String nm, boolean displaySelection) {
ExtendedTextView extendedTextView = (ExtendedTextView) v;
MinecraftProfile minecraftProfile = mProfiles.get(nm);
if(minecraftProfile == null) minecraftProfile = dummy;
Drawable cachedIcon = ProfileIconCache.getCachedIcon(nm);
if(cachedIcon == null) {
cachedIcon = ProfileIconCache.tryResolveIcon(v.getResources(), nm, minecraftProfile.icon);
}
extendedTextView.setCompoundDrawablesRelative(cachedIcon, null, extendedTextView.getCompoundsDrawables()[2], null);
if(Tools.isValidString(minecraftProfile.name))
extendedTextView.setText(minecraftProfile.name);
else
extendedTextView.setText(R.string.unnamed);
if(minecraftProfile.lastVersionId != null){
if(minecraftProfile.lastVersionId.equalsIgnoreCase("latest-release")){
extendedTextView.setText( String.format("%s - %s", extendedTextView.getText(), v.getContext().getText(R.string.profiles_latest_release)));
} else if(minecraftProfile.lastVersionId.equalsIgnoreCase("latest-snapshot")){
extendedTextView.setText( String.format("%s - %s", extendedTextView.getText(), v.getContext().getText(R.string.profiles_latest_snapshot)));
} else {
extendedTextView.setText( String.format("%s - %s", extendedTextView.getText(), minecraftProfile.lastVersionId));
}
} else extendedTextView.setText(extendedTextView.getText());
// Set selected background if needed
if(displaySelection){
String selectedProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"");
extendedTextView.setBackgroundColor(selectedProfile.equals(nm) ? ColorUtils.setAlphaComponent(Color.WHITE,60) : Color.TRANSPARENT);
}else extendedTextView.setBackgroundColor(Color.TRANSPARENT);
}
public void setViewExtra(View v, ProfileAdapterExtra extra) {
ExtendedTextView extendedTextView = (ExtendedTextView) v;
extendedTextView.setCompoundDrawablesRelative(extra.icon, null, extendedTextView.getCompoundsDrawables()[2], null);
extendedTextView.setText(extra.name);
extendedTextView.setBackgroundColor(Color.TRANSPARENT);
}
}

View file

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

View file

@ -1,57 +0,0 @@
package net.kdt.pojavlaunch.profiles;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Base64;
import android.util.Log;
import androidx.core.content.res.ResourcesCompat;
import net.kdt.pojavlaunch.R;
import java.util.HashMap;
import java.util.Map;
public class ProfileIconCache {
private static final String BASE64_PNG_HEADER = "data:image/png;base64,";
private static final Map<String, Drawable> sIconCache = new HashMap<>();
private static Drawable sDefaultIcon;
public static void initDefault(Context context) {
if(sDefaultIcon != null) return;
sDefaultIcon = ResourcesCompat.getDrawable(context.getResources(), R.mipmap.ic_launcher_foreground, null);
if(sDefaultIcon != null) sDefaultIcon.setBounds(0, 0, 10, 10);
}
public static Drawable getCachedIcon(String key) {
return sIconCache.get(key);
}
public static Drawable submitIcon(Resources resources, String key, String base64) {
byte[] pngBytes = Base64.decode(base64, Base64.DEFAULT);
Drawable drawable = new BitmapDrawable(resources, BitmapFactory.decodeByteArray(pngBytes, 0, pngBytes.length));
sIconCache.put(key, drawable);
return drawable;
}
public static Drawable tryResolveIcon(Resources resources, String profileName, String b64Icon) {
Drawable icon;
if (b64Icon != null && b64Icon.startsWith(BASE64_PNG_HEADER)) {
icon = ProfileIconCache.submitIcon(resources, profileName, b64Icon.substring(BASE64_PNG_HEADER.length()));
}else{
Log.i("IconParser","Unsupported icon: "+b64Icon);
icon = ProfileIconCache.pushDefaultIcon(profileName);
}
return icon;
}
public static Drawable pushDefaultIcon(String key) {
sIconCache.put(key, sDefaultIcon);
return sDefaultIcon;
}
}

View file

@ -1,136 +0,0 @@
package net.kdt.pojavlaunch.profiles;
import android.content.Context;
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 net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.FilteredSubList;
import java.io.File;
import java.util.Arrays;
import java.util.List;
public class VersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter {
private final LayoutInflater mLayoutInflater;
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, boolean hideCustomVersions, Context ctx){
mHideCustomVersions = hideCustomVersions;
mLayoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
List<JMinecraftVersionList.Version> releaseList = new FilteredSubList<>(versionList, item -> item.type.equals("release"));
List<JMinecraftVersionList.Version> snapshotList = new FilteredSubList<>(versionList, item -> item.type.equals("snapshot"));
List<JMinecraftVersionList.Version> betaList = new FilteredSubList<>(versionList, item -> item.type.equals("old_beta"));
List<JMinecraftVersionList.Version> alphaList = new FilteredSubList<>(versionList, item -> item.type.equals("old_alpha"));
// Query installed versions
mInstalledVersions = new File(Tools.DIR_GAME_NEW + "/versions").list();
if(!areInstalledVersionsAvailable()){
mGroups = new String[]{
ctx.getString(R.string.mcl_setting_veroption_release),
ctx.getString(R.string.mcl_setting_veroption_snapshot),
ctx.getString(R.string.mcl_setting_veroption_oldbeta),
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),
ctx.getString(R.string.mcl_setting_veroption_release),
ctx.getString(R.string.mcl_setting_veroption_snapshot),
ctx.getString(R.string.mcl_setting_veroption_oldbeta),
ctx.getString(R.string.mcl_setting_veroption_oldalpha)
};
mData = new List[]{Arrays.asList(mInstalledVersions), releaseList, snapshotList, betaList, alphaList};
mSnapshotListPosition = 2;
}
}
@Override
public int getGroupCount() {
return mGroups.length;
}
@Override
public int getChildrenCount(int groupPosition) {
return mData[groupPosition].size();
}
@Override
public Object getGroup(int groupPosition) {
return mData[groupPosition];
}
@Override
public String getChild(int groupPosition, int childPosition) {
if(isInstalledVersionSelected(groupPosition)){
return mInstalledVersions[childPosition];
}
return ((JMinecraftVersionList.Version)mData[groupPosition].get(childPosition)).id;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
((TextView) convertView).setText(mGroups[groupPosition]);
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if(convertView == null)
convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
((TextView) convertView).setText(getChild(groupPosition, childPosition));
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public boolean isSnapshotSelected(int groupPosition) {
return groupPosition == mSnapshotListPosition;
}
private boolean areInstalledVersionsAvailable(){
if(mHideCustomVersions) return false;
return !(mInstalledVersions == null || mInstalledVersions.length == 0);
}
private boolean isInstalledVersionSelected(int groupPosition){
return groupPosition == 0 && areInstalledVersionsAvailable();
}
}

View file

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

View file

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

View file

@ -1,335 +0,0 @@
package net.kdt.pojavlaunch.scoped;
import android.annotation.TargetApi;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* A document provider for the Storage Access Framework which exposes the files in the
* $HOME/ directory to other apps.
* <p/>
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
* <p/>
* "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
* support both of them simultaneously, your app will appear twice in the system picker UI,
* offering two different ways of accessing your stored data. This would be confusing for users."
* - <a href="http://developer.android.com/guide/topics/providers/document-provider.html#43">...</a>
*/
public class FolderProvider extends DocumentsProvider {
private static final String ALL_MIME_TYPES = "*/*";
private static final File BASE_DIR = new File(Tools.DIR_GAME_HOME);
// The default columns to return information about a root if no specific
// columns are requested in a query.
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
// The default columns to return information about a document if no specific
// columns are requested in a query.
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE
};
@Override
public Cursor queryRoots(String[] projection) {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
final String applicationName = getContext().getString(R.string.app_short_name);
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_SUMMARY, null);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, applicationName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result, documentId, null);
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId);
final File[] children = parent.listFiles();
if(children == null) throw new FileNotFoundException("Unable to list files in "+parent.getAbsolutePath());
for (File file : children) {
includeFile(result, null, file);
}
return result;
}
@Override
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final int accessMode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, accessMode);
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, 0, file.length());
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File newFile = new File(parentDocumentId, displayName);
int noConflictId = 2;
while (newFile.exists()) {
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
}
try {
boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
succeeded = newFile.mkdir();
} else {
succeeded = newFile.createNewFile();
}
if (!succeeded) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
} catch (IOException e) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
return newFile.getPath();
}
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
File sourceFile = getFileForDocId(documentId);
File sourceParent = sourceFile.getParentFile();
if(sourceParent == null) throw new FileNotFoundException("Cannot rename root");
File targetFile = new File(getDocIdForFile(sourceParent) + "/" + displayName);
if(!sourceFile.renameTo(targetFile)){
throw new FileNotFoundException("Couldn't rename the document with id" + documentId);
}
return getDocIdForFile(targetFile);
}
@Override
public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException {
File sourceFile = getFileForDocId(sourceParentDocumentId + sourceDocumentId);
File targetFile = new File(targetParentDocumentId + sourceDocumentId);
if(!sourceFile.renameTo(targetFile)){
throw new FileNotFoundException("Failed to move the document with id " + sourceFile.getPath());
}
return getDocIdForFile(targetFile);
}
@Override
public void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
deleteDocument(parentDocumentId + "/" + documentId);
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
if(file.isDirectory()){
try {
FileUtils.deleteDirectory(file);
} catch (IOException e) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
}else{
if (!file.delete()) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
}
}
@Override
public String getDocumentType(String documentId) throws FileNotFoundException {
Log.i("FolderPRovider", "getDocumentType("+documentId+")");
File file = getFileForDocId(documentId);
return getMimeType(file);
}
@Override
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(rootId);
// This example implementation searches file names for the query and doesn't rank search
// results, so we can stop as soon as we find a sufficient number of matches. Other
// implementations might rank results and use other data about files, rather than the file
// name, to produce a match.
final LinkedList<File> pending = new LinkedList<>();
pending.add(parent);
final int MAX_SEARCH_RESULTS = 50;
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
final File file = pending.removeFirst();
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search
// through the whole SD card).
boolean isInsideHome;
try {
isInsideHome = file.getCanonicalPath().startsWith(Tools.DIR_GAME_HOME);
} catch (IOException e) {
isInsideHome = true;
}
if (isInsideHome) {
if (file.isDirectory()) {
File[] listing = file.listFiles();
if(listing != null) Collections.addAll(pending, listing);
} else {
if (file.getName().toLowerCase().contains(query)) {
includeFile(result, null, file);
}
}
}
}
return result;
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
return documentId.startsWith(parentDocumentId);
}
/**
* Get the document id given a file. This document id must be consistent across time as other
* applications may save the ID and use it to reference documents later.
* <p/>
* The reverse of @{link #getFileForDocId}.
*/
private static String getDocIdForFile(File file) {
return file.getAbsolutePath();
}
/**
* Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}).
*/
private static File getFileForDocId(String docId) throws FileNotFoundException {
final File f = new File(docId);
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
return f;
}
private static String getMimeType(File file) {
if (file.isDirectory()) {
return Document.MIME_TYPE_DIR;
} else {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime;
}
return "application/octet-stream";
}
}
/**
* Add a representation of a file to a cursor.
*
* @param result the cursor to modify
* @param docId the document ID representing the desired file (may be null if given file)
* @param file the File object representing the desired file (may be null if given docID)
*/
private void includeFile(MatrixCursor result, String docId, File file)
throws FileNotFoundException {
if (docId == null) {
docId = getDocIdForFile(file);
} else {
file = getFileForDocId(docId);
}
int flags = 0;
if (file.isDirectory()) {
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE;
}
File parent = file.getParentFile();
if(parent != null) { // Only fails in one case: when the parent is /, which you can't delete.
if(parent.canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
}
final String displayName = file.getName();
final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
}
@Override
@TargetApi(26)
public DocumentsContract.Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException {
File source = BASE_DIR;
if(parentDocumentId != null) source = getFileForDocId(parentDocumentId);
File destination = getFileForDocId(childDocumentId);
List<String> pathIds = new ArrayList<>();
while(!source.equals(destination) && destination != null) {
pathIds.add(getDocIdForFile(destination));
destination = destination.getParentFile();
}
pathIds.add(getDocIdForFile(source));
Collections.reverse(pathIds);
Log.i("FolderProvider", pathIds.toString());
return new DocumentsContract.Path(getDocIdForFile(source), pathIds);
}
}

View file

@ -1,64 +0,0 @@
package net.kdt.pojavlaunch.services;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import java.lang.ref.WeakReference;
public class GameService extends Service {
private static final WeakReference<Service> sGameService = new WeakReference<>(null);
public static void startService(Context context) {
Intent intent = new Intent(context, GameService.class);
ContextCompat.startForegroundService(context, intent);
}
@Override
public void onCreate() {
Tools.buildNotificationChannel(getApplicationContext());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent != null && intent.getBooleanExtra("kill", false)) {
stopSelf();
Process.killProcess(Process.myPid());
return START_NOT_STICKY;
}
Intent killIntent = new Intent(getApplicationContext(), GameService.class);
killIntent.putExtra("kill", true);
PendingIntent pendingKillIntent = PendingIntent.getService(this, 0, killIntent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle(getString(R.string.lazy_service_default_title))
.setContentText(getString(R.string.notification_game_runs))
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
.setSmallIcon(R.drawable.notif_icon)
.setNotificationSilent();
startForeground(2, notificationBuilder.build());
return START_NOT_STICKY; // non-sticky so android wont try restarting the game after the user uses the "Quit" button
}
@Override
public void onTaskRemoved(Intent rootIntent) {
//At this point in time only the game runs and the user poofed the window, time to die
stopSelf();
Process.killProcess(Process.myPid());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View file

@ -1,435 +0,0 @@
package net.kdt.pojavlaunch.tasks;
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
import static net.kdt.pojavlaunch.utils.DownloadUtils.downloadFileMonitored;
import android.app.Activity;
import android.util.Log;
import androidx.annotation.NonNull;
import com.kdt.mcgui.ProgressLayout;
import net.kdt.pojavlaunch.JAssetInfo;
import net.kdt.pojavlaunch.JAssets;
import net.kdt.pojavlaunch.JMinecraftVersionList;
import net.kdt.pojavlaunch.JRE17Util;
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.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.utils.DownloadUtils;
import net.kdt.pojavlaunch.value.DependentLibrary;
import net.kdt.pojavlaunch.value.MinecraftClientInfo;
import net.kdt.pojavlaunch.value.MinecraftLibraryArtifact;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class AsyncMinecraftDownloader {
private static final float BYTE_TO_MB = 1024 * 1024;
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
/* Allows each downloading thread to have its own RECYCLED buffer */
private final ConcurrentHashMap<Thread, byte[]> mThreadBuffers = new ConcurrentHashMap<>(5);
public AsyncMinecraftDownloader(Activity activity, JMinecraftVersionList.Version version, String realVersion,
@NonNull DoneListener listener){ // this was there for a reason
sExecutorService.execute(() -> {
try {
downloadGame(activity, version, realVersion);
listener.onDownloadDone();
}catch (DownloaderException e) {
listener.onDownloadFailed(e.getCause());
}
});
}
/* we do the throws DownloaderException thing to avoid blanket-catching Exception as a form of anti-lazy-developer protection */
private void downloadGame(Activity activity, JMinecraftVersionList.Version verInfo, String versionName) throws DownloaderException {
final String downVName = "/" + versionName + "/" + versionName;
//Downloading libraries
String minecraftMainJar = Tools.DIR_HOME_VERSION + downVName + ".jar";
JAssets assets = null;
try {
File verJsonDir = new File(Tools.DIR_HOME_VERSION + downVName + ".json");
if (verInfo != null && verInfo.url != null) {
downloadVersionJson(versionName, verJsonDir, verInfo);
}
JMinecraftVersionList.Version originalVersion = Tools.getVersionInfo(versionName, true);
if(Tools.isValidString(originalVersion.inheritsFrom)) {
Log.i("Downloader", "probe: inheritsFrom="+originalVersion.inheritsFrom);
String version = originalVersion.inheritsFrom;
String downName = Tools.DIR_HOME_VERSION+"/"+version+"/"+version+".json";
JMinecraftVersionList.Version listedVersion = getListedVersion(originalVersion.inheritsFrom);
if(listedVersion != null) {
Log.i("Downloader", "probe: verifying "+version);
downloadVersionJson(version, new File(downName), listedVersion);
}else{
Log.i("Downloader", "failed to test source version before downloading.");
Log.i("Downloader", "Inheriting from versions not in the Mojang list?");
Log.i("Downloader", "If so, feel free to open a PR at our GitHub repository to add this feature!");
}
}
verInfo = Tools.getVersionInfo(versionName);
// THIS one function need the activity in the case of an error
if(activity != null && !JRE17Util.installNewJreIfNeeded(activity, verInfo)){
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
throw new DownloaderException();
}
try {
if(verInfo.assets != null)
assets = downloadIndex(verInfo, new File(Tools.ASSETS_PATH, "indexes/" + verInfo.assets + ".json"));
} catch (IOException e) {
Log.e("AsyncMcDownloader", e.toString(), e);
throw new DownloaderException(e);
}
File outLib;
// Patch the Log4J RCE (CVE-2021-44228)
if (verInfo.logging != null) {
outLib = new File(Tools.DIR_DATA + "/security", verInfo.logging.client.file.id.replace("client", "log4j-rce-patch"));
boolean useLocal = outLib.exists();
if (!useLocal) {
outLib = new File(Tools.DIR_GAME_NEW, verInfo.logging.client.file.id);
}
if (outLib.exists() && !useLocal) {
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA) {
if(!Tools.compareSHA1(outLib,verInfo.logging.client.file.sha1)) {
outLib.delete();
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_fail,verInfo.logging.client.file.id);
}else{
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_pass,verInfo.logging.client.file.id);
}
} else if (outLib.length() != verInfo.logging.client.file.size) {
// force updating anyways
outLib.delete();
}
}
if (!outLib.exists()) {
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_downloading, verInfo.logging.client.file.id);
JMinecraftVersionList.Version finalVerInfo = verInfo;
downloadFileMonitored(
verInfo.logging.client.file.url, outLib, getByteBuffer(),
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, finalVerInfo.logging.client.file.id, curr/BYTE_TO_MB, max/BYTE_TO_MB)
);
}
}
for (final DependentLibrary libItem : verInfo.libraries) {
if(libItem.name.startsWith("org.lwjgl")){ //Black list
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, "Ignored " + libItem.name);
continue;
}
String libArtifact = Tools.artifactToPath(libItem);
outLib = new File(Tools.DIR_HOME_LIBRARY + "/" + libArtifact);
outLib.getParentFile().mkdirs();
if (!outLib.exists()) {
downloadLibrary(libItem,libArtifact,outLib);
}else{
if(libItem.downloads != null && libItem.downloads.artifact != null && libItem.downloads.artifact.sha1 != null && !libItem.downloads.artifact.sha1.isEmpty()) {
if(!Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1)) {
outLib.delete();
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_fail,libItem.name);
downloadLibrary(libItem,libArtifact,outLib);
}else{
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_pass,libItem.name);
}
}else{
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_unknown,libItem.name);
}
}
}
File minecraftMainFile = new File(minecraftMainJar);
Log.i("Downloader", "originalVersion.inheritsFrom="+originalVersion.inheritsFrom);
Log.i("Downloader", "originalVersion.downloads="+originalVersion.downloads);
MinecraftClientInfo originalClientInfo;
if(originalVersion.inheritsFrom == null) {
if (originalVersion.downloads != null && (originalClientInfo = originalVersion.downloads.get("client")) != null) {
verifyAndDownloadMainJar(originalClientInfo.url, originalClientInfo.sha1, minecraftMainFile);
}
}else if(!minecraftMainFile.exists() || minecraftMainFile.length() == 0) {
File minecraftSourceFile = new File(Tools.DIR_HOME_VERSION + "/" + verInfo.id + "/" + verInfo.id + ".jar");
MinecraftClientInfo inheritedClientInfo;
if(verInfo.downloads != null && (inheritedClientInfo = verInfo.downloads.get("client")) != null) {
verifyAndDownloadMainJar(inheritedClientInfo.url, inheritedClientInfo.sha1, minecraftSourceFile);
}
if(minecraftSourceFile.exists()) {
FileInputStream is = new FileInputStream(minecraftSourceFile);
FileOutputStream os = new FileOutputStream(minecraftMainFile);
IOUtils.copy(is, os);
is.close();
os.close();
}
}
} catch (DownloaderException e) {
throw e;
} catch (Throwable e) {
Log.e("AsyncMcDownloader", e.toString(),e );
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
throw new DownloaderException(e);
}
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_cleancache);
new File(Tools.DIR_HOME_VERSION).mkdir();
for (File f : new File(Tools.DIR_HOME_VERSION).listFiles()) {
if(f.getName().endsWith(".part")) {
Log.d(Tools.APP_NAME, "Cleaning cache: " + f);
f.delete();
}
}
try {
if(assets != null)
downloadAssets(assets, verInfo.assets, assets.mapToResources ? new File(Tools.OBSOLETE_RESOURCES_PATH) : new File(Tools.ASSETS_PATH));
} catch (Exception e) {
Log.e("AsyncMcDownloader", e.toString(), e);
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
throw new DownloaderException(e);
}
ProgressKeeper.submitProgress(ProgressLayout.DOWNLOAD_MINECRAFT, -1, -1);
}
public void verifyAndDownloadMainJar(String url, String sha1, File destination) throws Exception{
while(!destination.exists() || (destination.exists() && !Tools.compareSHA1(destination, sha1))) downloadFileMonitored(
url,
destination, getByteBuffer(),
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, destination.getName(), curr/BYTE_TO_MB, max/BYTE_TO_MB));
}
public void downloadAssets(final JAssets assets, String assetsVersion, final File outputDir) {
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
final ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 500, TimeUnit.MILLISECONDS, workQueue);
Log.i("AsyncMcDownloader","Assets begin time: " + System.currentTimeMillis());
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_download_assets);
Map<String, JAssetInfo> assetsObjects = assets.objects;
int assetsSizeBytes = 0;
AtomicInteger downloadedSize = new AtomicInteger(0);
AtomicBoolean localInterrupt = new AtomicBoolean(false);
File objectsDir = new File(outputDir, "objects");
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_downloading, "assets");
for(String assetKey : assetsObjects.keySet()) {
JAssetInfo asset = assetsObjects.get(assetKey);
assetsSizeBytes += asset.size;
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = assets.mapToResources ? new File(outputDir,"/"+assetKey) : new File(objectsDir, assetPath);
boolean skip = outFile.exists();// skip if the file exists
if(LauncherPreferences.PREF_CHECK_LIBRARY_SHA && skip)
skip = Tools.compareSHA1(outFile, asset.hash);
if(skip) {
downloadedSize.addAndGet(asset.size);
continue;
}
if(outFile.exists())
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.dl_library_sha_fail, assetKey);
executor.execute(()->{
try {
if (!assets.mapToResources) downloadAsset(asset, objectsDir, downloadedSize);
else downloadAssetMapped(asset, assetKey, outputDir, downloadedSize);
}catch (IOException e) {
Log.e("AsyncMcManager", e.toString());
localInterrupt.set(true);
}
});
}
executor.shutdown();
try {
Log.i("AsyncMcDownloader","Queue size: " + workQueue.size());
while ((!executor.awaitTermination(1000, TimeUnit.MILLISECONDS))&&(!localInterrupt.get()) /*&&mActivity.mIsAssetsProcessing*/) {
int DLSize = downloadedSize.get();
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,(int) Math.max((float) DLSize/assetsSizeBytes*100, 0),
R.string.mcl_launch_downloading_progress, "assets", (float)DLSize/BYTE_TO_MB, (float)assetsSizeBytes/BYTE_TO_MB);
}
executor.shutdownNow();
while (!executor.awaitTermination(250, TimeUnit.MILLISECONDS)) {}
Log.i("AsyncMcDownloader","Fully shut down!");
}catch(InterruptedException e) {
Log.e("AsyncMcDownloader", e.toString());
}
Log.i("AsyncMcDownloader", "Assets end time: " + System.currentTimeMillis());
}
public void downloadAsset(JAssetInfo asset, File objectsDir, AtomicInteger downloadCounter) throws IOException {
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = new File(objectsDir, assetPath);
downloadFileMonitored(MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
new Tools.DownloaderFeedback() {
int prevCurr;
@Override
public void updateProgress(int curr, int max) {
downloadCounter.addAndGet(curr - prevCurr);
prevCurr = curr;
}
});
}
public void downloadAssetMapped(JAssetInfo asset, String assetName, File resDir, AtomicInteger downloadCounter) throws IOException {
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
File outFile = new File(resDir,"/"+assetName);
downloadFileMonitored(MINECRAFT_RES + assetPath, outFile, getByteBuffer(),
new Tools.DownloaderFeedback() {
int prevCurr;
@Override
public void updateProgress(int curr, int max) {
downloadCounter.addAndGet(curr - prevCurr);
prevCurr = curr;
}
});
}
protected void downloadLibrary(DependentLibrary libItem, String libArtifact, File outLib) throws Throwable{
String libPathURL;
boolean skipIfFailed = false;
if (libItem.downloads == null || libItem.downloads.artifact == null) {
MinecraftLibraryArtifact artifact = new MinecraftLibraryArtifact();
artifact.url = (libItem.url == null
? "https://libraries.minecraft.net/"
: libItem.url.replace("http://","https://")) + libArtifact;
libItem.downloads = new DependentLibrary.LibraryDownloads(artifact);
skipIfFailed = true;
}
try {
libPathURL = libItem.downloads.artifact.url;
boolean isFileGood = false;
byte timesChecked=0;
while(!isFileGood) {
timesChecked++;
if(timesChecked > 5) throw new RuntimeException("Library download failed after 5 retries");
downloadFileMonitored(libPathURL, outLib, getByteBuffer(),
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, outLib.getName(), curr/BYTE_TO_MB, max/BYTE_TO_MB)
);
isFileGood = (libItem.downloads.artifact.sha1 == null
|| LauncherPreferences.PREF_CHECK_LIBRARY_SHA)
|| Tools.compareSHA1(outLib,libItem.downloads.artifact.sha1);
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0,
isFileGood ? R.string.dl_library_sha_pass : R.string.dl_library_sha_unknown
,outLib.getName());
}
} catch (Throwable th) {
Log.e("AsyncMcDownloader", th.toString(), th);
if (!skipIfFailed) {
throw th;
} else {
th.printStackTrace();
}
}
}
public JAssets downloadIndex(JMinecraftVersionList.Version version, File output) throws IOException {
if (!output.exists()) {
output.getParentFile().mkdirs();
DownloadUtils.downloadFile(version.assetIndex != null
? version.assetIndex.url
: "https://s3.amazonaws.com/Minecraft.Download/indexes/" + version.assets + ".json", output);
}
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
}
public void downloadVersionJson(String versionName, File verJsonDir, JMinecraftVersionList.Version verInfo) throws IOException {
if(!LauncherPreferences.PREF_CHECK_LIBRARY_SHA) Log.w("Chk","Checker is off");
boolean isManifestGood = verJsonDir.exists()
&& (!LauncherPreferences.PREF_CHECK_LIBRARY_SHA
|| verInfo.sha1 == null
|| Tools.compareSHA1(verJsonDir, verInfo.sha1));
if(!isManifestGood) {
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.mcl_launch_downloading, versionName + ".json");
verJsonDir.delete();
downloadFileMonitored(verInfo.url, verJsonDir, getByteBuffer(),
(curr, max) -> ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT,
(int) Math.max((float)curr/max*100,0), R.string.mcl_launch_downloading_progress, versionName + ".json", curr/BYTE_TO_MB, max/BYTE_TO_MB)
);
}
}
public static String normalizeVersionId(String versionString) {
JMinecraftVersionList versionList = (JMinecraftVersionList) ExtraCore.getValue(ExtraConstants.RELEASE_TABLE);
if(versionList == null || versionList.versions == null) return versionString;
if("latest-release".equals(versionString)) versionString = versionList.latest.get("release");
if("latest-snapshot".equals(versionString)) versionString = versionList.latest.get("snapshot");
return versionString;
}
public static JMinecraftVersionList.Version getListedVersion(String normalizedVersionString) {
JMinecraftVersionList versionList = (JMinecraftVersionList) ExtraCore.getValue(ExtraConstants.RELEASE_TABLE);
if(versionList == null || versionList.versions == null) return null; // can't have listed versions if there's no list
for(JMinecraftVersionList.Version version : versionList.versions) {
if(version.id.equals(normalizedVersionString)) return version;
}
return null;
}
/**@return A byte buffer bound to a thread, useful to recycle it across downloads */
private byte[] getByteBuffer(){
byte[] buffer = mThreadBuffers.get(Thread.currentThread());
if (buffer == null){
buffer = new byte[8192];
mThreadBuffers.put(Thread.currentThread(), buffer);
}
return buffer;
}
public interface DoneListener{
void onDownloadDone();
void onDownloadFailed(Throwable throwable);
}
private static class DownloaderException extends Exception {
public DownloaderException() {}
public DownloaderException(Throwable e) {
super(e);
}
}
}

View file

@ -1,88 +0,0 @@
package net.kdt.pojavlaunch.tasks;
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
import static net.kdt.pojavlaunch.utils.DownloadUtils.downloadString;
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;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
/** Class getting the version list, and that's all really */
public class AsyncVersionList {
public void getVersionList(@Nullable VersionDoneListener listener, boolean secondPass){
sExecutorService.execute(() -> {
File versionFile = new File(Tools.DIR_DATA + "/version_list.json");
JMinecraftVersionList versionList = null;
try{
if(!versionFile.exists() || (System.currentTimeMillis() > versionFile.lastModified() + 86400000 )){
versionList = downloadVersionList(LauncherPreferences.PREF_VERSION_REPOS);
}
}catch (Exception e){
Log.e("AsyncVersionList", "Refreshing version list failed :" + e);
e.printStackTrace();
}
// Fallback when no network or not needed
if (versionList == null) {
try {
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);
}
}
if(listener != null)
listener.onVersionDone(versionList);
});
}
@SuppressWarnings("SameParameterValue")
private JMinecraftVersionList downloadVersionList(String mirror){
JMinecraftVersionList list = null;
try{
Log.i("ExtVL", "Syncing to external: " + mirror);
String jsonString = downloadString(mirror);
list = Tools.GLOBAL_GSON.fromJson(jsonString, JMinecraftVersionList.class);
Log.i("ExtVL","Downloaded the version list, len=" + list.versions.length);
// Then save the version list
//TODO make it not save at times ?
FileOutputStream fos = new FileOutputStream(Tools.DIR_DATA + "/version_list.json");
fos.write(jsonString.getBytes());
fos.close();
}catch (IOException e){
Log.e("AsyncVersionList", e.toString());
}
return list;
}
/** Basic listener, acting as a callback */
public interface VersionDoneListener{
void onVersionDone(JMinecraftVersionList versions);
}
}

View file

@ -24,7 +24,6 @@ import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.multirt.Runtime;
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
import net.kdt.pojavlaunch.prefs.*;
import org.lwjgl.glfw.*;
@ -145,9 +144,6 @@ public class JREUtils {
String libName = is64BitsDevice() ? "lib64" : "lib";
StringBuilder ldLibraryPath = new StringBuilder();
if(FFmpegPlugin.isAvailable) {
ldLibraryPath.append(FFmpegPlugin.libraryPath).append(":");
}
ldLibraryPath.append(jreHome)
.append("/").append(Tools.DIRNAME_HOME_JRE)
.append("/jli:").append(jreHome).append("/").append(Tools.DIRNAME_HOME_JRE)
@ -195,9 +191,6 @@ public class JREUtils {
envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH);
envMap.put("PATH", jreHome + "/bin:" + Os.getenv("PATH"));
if(FFmpegPlugin.isAvailable) {
envMap.put("PATH", FFmpegPlugin.libraryPath+":"+envMap.get("PATH"));
}
envMap.put("REGAL_GL_VENDOR", "Android");
envMap.put("REGAL_GL_RENDERER", "Regal");

View file

@ -1,21 +0,0 @@
package net.kdt.pojavlaunch.value;
import androidx.annotation.Keep;
import net.kdt.pojavlaunch.JMinecraftVersionList.Arguments.ArgValue.ArgRules;
@Keep
public class DependentLibrary {
public ArgRules[] rules;
public String name;
public LibraryDownloads downloads;
public String url;
@Keep
public static class LibraryDownloads {
public final MinecraftLibraryArtifact artifact;
public LibraryDownloads(MinecraftLibraryArtifact artifact) {
this.artifact = artifact;
}
}
}

View file

@ -1,109 +0,0 @@
package net.kdt.pojavlaunch.value;
import android.graphics.BitmapFactory;
import android.util.Log;
import net.kdt.pojavlaunch.*;
import java.io.*;
import com.google.gson.*;
import android.graphics.Bitmap;
import android.util.Base64;
import androidx.annotation.Keep;
import org.apache.commons.io.IOUtils;
@SuppressWarnings("IOStreamConstructor")
@Keep
public class MinecraftAccount {
public String accessToken = "0"; // access token
public String clientToken = "0"; // clientID: refresh and invalidate
public String profileId = "00000000-0000-0000-0000-000000000000"; // profile UUID, for obtaining skin
public String username = "Steve";
public String selectedVersion = "1.7.10";
public boolean isMicrosoft = false;
public String msaRefreshToken = "0";
public String xuid;
public String skinFaceBase64;
public long expiresAt;
void updateSkinFace(String uuid) {
try {
File skinFile = File.createTempFile("skin", ".png", new File(Tools.DIR_DATA, "cache"));
Tools.downloadFile("https://mc-heads.net/head/" + uuid + "/100", skinFile.getAbsolutePath());
skinFaceBase64 = Base64.encodeToString(IOUtils.toByteArray(new FileInputStream(skinFile)), Base64.DEFAULT);
Log.i("SkinLoader", "Update skin face success");
} catch (IOException e) {
// Skin refresh limit, no internet connection, etc...
// Simply ignore updating skin face
Log.w("SkinLoader", "Could not update skin face", e);
}
}
public boolean isLocal(){
return accessToken.equals("0");
}
public void updateSkinFace() {
updateSkinFace(profileId);
}
public String save(String outPath) throws IOException {
Tools.write(outPath, Tools.GLOBAL_GSON.toJson(this));
return username;
}
public String save() throws IOException {
return save(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json");
}
public static MinecraftAccount parse(String content) throws JsonSyntaxException {
return Tools.GLOBAL_GSON.fromJson(content, MinecraftAccount.class);
}
public static MinecraftAccount load(String name) {
if(!accountExists(name)) return null;
try {
MinecraftAccount acc = parse(Tools.read(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json"));
if (acc.accessToken == null) {
acc.accessToken = "0";
}
if (acc.clientToken == null) {
acc.clientToken = "0";
}
if (acc.profileId == null) {
acc.profileId = "00000000-0000-0000-0000-000000000000";
}
if (acc.username == null) {
acc.username = "0";
}
if (acc.selectedVersion == null) {
acc.selectedVersion = "1.7.10";
}
if (acc.msaRefreshToken == null) {
acc.msaRefreshToken = "0";
}
if (acc.skinFaceBase64 == null) {
// acc.updateSkinFace("MHF_Steve");
}
return acc;
} catch(IOException | JsonSyntaxException e) {
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the profile",e);
return null;
}
}
public Bitmap getSkinFace(){
if(skinFaceBase64 == null){
return null;
}
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
}
private static boolean accountExists(String username){
return new File(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json").exists();
}
}

View file

@ -1,10 +0,0 @@
package net.kdt.pojavlaunch.value;
import androidx.annotation.Keep;
@Keep
public class MinecraftClientInfo {
public String sha1;
public int size;
public String url;
}

View file

@ -1,8 +0,0 @@
package net.kdt.pojavlaunch.value;
import androidx.annotation.Keep;
@Keep
public class MinecraftLibraryArtifact extends MinecraftClientInfo {
public String path;
}

View file

@ -1,83 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import android.util.Log;
import androidx.annotation.NonNull;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
public class LauncherProfiles {
public static MinecraftLauncherProfiles mainProfileJson;
public static final File launcherProfilesFile = new File(Tools.DIR_GAME_NEW, "launcher_profiles.json");
public static MinecraftLauncherProfiles update() {
try {
if (mainProfileJson == null) {
if (launcherProfilesFile.exists()) {
mainProfileJson = Tools.GLOBAL_GSON.fromJson(Tools.read(launcherProfilesFile.getAbsolutePath()), MinecraftLauncherProfiles.class);
if(mainProfileJson.profiles == null) mainProfileJson.profiles = new HashMap<>();
else if(LauncherProfiles.normalizeProfileIds(mainProfileJson)){
LauncherProfiles.update();
}
} else {
mainProfileJson = new MinecraftLauncherProfiles();
mainProfileJson.profiles = new HashMap<>();
}
} else {
Tools.write(launcherProfilesFile.getAbsolutePath(), mainProfileJson.toJson());
}
// insertMissing();
return mainProfileJson;
} catch (Throwable th) {
throw new RuntimeException(th);
}
}
public static @NonNull MinecraftProfile getCurrentProfile() {
if(mainProfileJson == null) LauncherProfiles.update();
String defaultProfileName = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE, "");
MinecraftProfile profile = mainProfileJson.profiles.get(defaultProfileName);
if(profile == null) throw new RuntimeException("The current profile stopped existing :(");
return profile;
}
/**
* For all keys to be UUIDs, effectively isolating profile created by installers
* This avoids certain profiles to be erased by the installer
* @return Whether some profiles have been normalized
*/
private static boolean normalizeProfileIds(MinecraftLauncherProfiles launcherProfiles){
boolean hasNormalized = false;
ArrayList<String> keys = new ArrayList<>();
// Detect denormalized keys
for(String profileKey : launcherProfiles.profiles.keySet()){
try{
if(!UUID.fromString(profileKey).toString().equals(profileKey)) keys.add(profileKey);
}catch (IllegalArgumentException exception){
keys.add(profileKey);
Log.w(LauncherProfiles.class.toString(), "Illegal profile uuid: " + profileKey);
}
}
// Swap the new keys
for(String profileKey: keys){
String uuid = UUID.randomUUID().toString();
while(launcherProfiles.profiles.containsKey(uuid)) {
uuid = UUID.randomUUID().toString();
}
launcherProfiles.profiles.put(uuid, launcherProfiles.profiles.get(profileKey));
launcherProfiles.profiles.remove(profileKey);
hasNormalized = true;
}
return hasNormalized;
}
}

View file

@ -1,12 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
@Keep
public class MinecraftAuthenticationDatabase {
public String accessToken;
public String displayName;
public String username;
public String uuid;
// public MinecraftProfile[] profiles;
}

View file

@ -1,22 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
import java.util.*;
import net.kdt.pojavlaunch.*;
@Keep
public class MinecraftLauncherProfiles {
public Map<String, MinecraftProfile> profiles = new HashMap<>();
public boolean profilesWereMigrated;
public String clientToken;
public Map<String, MinecraftAuthenticationDatabase> authenticationDatabase;
// public Map launcherVersion;
public MinecraftLauncherSettings settings;
// public Map analyticsToken;
public int analyticsFailcount;
public MinecraftSelectedUser selectedUser;
public String toJson() {
return Tools.GLOBAL_GSON.toJson(this);
}
}

View file

@ -1,17 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
@Keep
public class MinecraftLauncherSettings {
public boolean enableSnapshots;
public boolean enableAdvanced;
public boolean keepLauncherOpen;
public boolean showGameLog;
public String locale;
public boolean showMenu;
public boolean enableHistorical;
public String profileSorting;
public boolean crashAssistance;
public boolean enableAnalytics;
}

View file

@ -1,49 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
@Keep
public class MinecraftProfile {
public String name;
public String type;
public String created;
public String lastUsed;
public String icon;
public String lastVersionId;
public String gameDir;
public String javaDir;
public String javaArgs;
public String logConfig;
public boolean logConfigIsXML;
public String pojavRendererName;
public String controlFile;
public MinecraftResolution[] resolution;
public static MinecraftProfile createTemplate(){
MinecraftProfile TEMPLATE = new MinecraftProfile();
TEMPLATE.name = "New";
TEMPLATE.lastVersionId = "latest-release";
return TEMPLATE;
}
public MinecraftProfile(){}
public MinecraftProfile(MinecraftProfile profile){
name = profile.name;
type = profile.type;
created = profile.created;
lastUsed = profile.lastUsed;
icon = profile.icon;
lastVersionId = profile.lastVersionId;
gameDir = profile.gameDir;
javaDir = profile.javaDir;
javaArgs = profile.javaArgs;
logConfig = profile.logConfig;
logConfigIsXML = profile.logConfigIsXML;
pojavRendererName = profile.pojavRendererName;
controlFile = profile.controlFile;
resolution = profile.resolution;
}
}

View file

@ -1,9 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
@Keep
public class MinecraftResolution {
public int width;
public int height;
}

View file

@ -1,9 +0,0 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
import androidx.annotation.Keep;
@Keep
public class MinecraftSelectedUser {
public String account;
public String profile;
}

View file

@ -1,64 +0,0 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<com.kdt.mcgui.mcAccountSpinner
android:id="@+id/account_spinner"
android:layout_width="match_parent"
android:layout_height="@dimen/_42sdp"
android:dropDownWidth="wrap_content"
android:dropDownVerticalOffset="@dimen/_42sdp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageButton
android:id="@+id/setting_button"
android:layout_width="@dimen/_52sdp"
android:layout_height="@dimen/_42sdp"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_menu_settings"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/delete_account_button"
android:layout_width="@dimen/_52sdp"
android:layout_height="@dimen/_42sdp"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_menu_delete_forever"
android:scaleType="fitCenter"
app:layout_constraintEnd_toStartOf="@id/setting_button"
app:layout_constraintTop_toTopOf="parent" />
<!-- Holding most of the dynamic content -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_fragment"
android:name="net.kdt.pojavlaunch.fragments.MainMenuFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/progress_layout"
app:layout_constraintTop_toBottomOf="@+id/account_spinner" />
<com.kdt.mcgui.ProgressLayout
android:id="@+id/progress_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,115 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_menu_main"
android:gravity="top"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="@color/background_app"
>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/news_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:text="@string/mcl_tab_wiki"
android:drawableStart="@drawable/ic_menu_news"
app:layout_constraintTop_toTopOf="parent"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/custom_control_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@drawable/ic_menu_custom_controls"
android:text="@string/mcl_option_customcontrol"
app:layout_constraintTop_toBottomOf="@id/news_button"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/install_jar_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@drawable/ic_menu_install_jar"
android:text="@string/main_install_jar_file"
app:layout_constraintTop_toBottomOf="@id/custom_control_button"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/share_logs_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@android:drawable/ic_menu_share"
android:text="@string/main_share_logs"
app:layout_constraintTop_toBottomOf="@id/install_jar_button"
/>
<com.kdt.mcgui.mcVersionSpinner
android:id="@+id/mc_version_spinner"
android:layout_width="0dp"
android:layout_height="@dimen/_32sdp"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/transparent"
android:drawableEnd="@drawable/spinner_arrow"
app:drawableEndPadding="@dimen/_1sdp"
app:drawableEndSize="@dimen/_12sdp"
app:drawableStartIntegerScaling="true"
app:drawableStartSize="@dimen/_36sdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/edit_profile_button"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/edit_profile_button"
android:layout_width="@dimen/_32sdp"
android:layout_height="0dp"
android:layout_marginEnd="@dimen/_8sdp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_edit_profile"
app:layout_constraintBottom_toBottomOf="@+id/mc_version_spinner"
app:layout_constraintEnd_toStartOf="@+id/play_button"
app:layout_constraintTop_toTopOf="@+id/mc_version_spinner" />
<!-- This view is for cosmetic purpose only -->
<View
android:id="@+id/_background_display_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="-8dp"
android:background="@color/background_bottom_bar"
android:translationZ="-1dp"
app:layout_constraintTop_toTopOf="@id/mc_version_spinner" />
<com.kdt.mcgui.MineButton
android:id="@+id/play_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/_8sdp"
android:text="@string/main_play"
android:textAllCaps="true"
app:layout_constraintBottom_toBottomOf="@+id/mc_version_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/mc_version_spinner" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="-10dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_setting_sign_in_background" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/_15sdp"
android:background="@drawable/menu_background"
android:paddingVertical="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3"
tools:layout_editor_absoluteX="16dp">
<TextView
android:id="@+id/textView_import_control_file_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="5dp"
android:text="File name:"
app:layout_constraintBottom_toTopOf="@+id/editText_import_control_file_name"
app:layout_constraintStart_toStartOf="@+id/editText_import_control_file_name" />
<com.kdt.mcgui.MineEditText
android:id="@+id/editText_import_control_file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="35dp"
android:layout_marginBottom="20dp"
android:hint="File name"
android:imeOptions="flagForceAscii"
app:layout_constraintBottom_toTopOf="@+id/mineButton_import_control" />
<com.kdt.mcgui.MineButton
android:id="@+id/mineButton_import_control"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:background="@drawable/mine_button_background"
android:onClick="startImport"
android:text="@string/import_control_import_button"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/editText_import_control_file_name"
app:layout_constraintStart_toStartOf="@+id/editText_import_control_file_name"
app:layout_constraintVertical_bias="0.70" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,65 +0,0 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<com.kdt.mcgui.mcAccountSpinner
android:id="@+id/account_spinner"
android:layout_width="match_parent"
android:layout_height="@dimen/_52sdp"
android:dropDownWidth="@dimen/_250sdp"
android:dropDownVerticalOffset="@dimen/_52sdp"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/setting_button"
android:layout_width="@dimen/_52sdp"
android:layout_height="@dimen/_52sdp"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_menu_settings"
android:scaleType="fitCenter"
android:paddingVertical="@dimen/_8sdp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/delete_account_button"
android:layout_width="@dimen/_52sdp"
android:layout_height="@dimen/_52sdp"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_menu_delete_forever"
android:scaleType="fitCenter"
android:paddingVertical="@dimen/_8sdp"
app:layout_constraintEnd_toStartOf="@id/setting_button"
app:layout_constraintTop_toTopOf="parent" />
<!-- Holding most of the dynamic content -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_fragment"
android:name="net.kdt.pojavlaunch.fragments.MainMenuFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/progress_layout"
app:layout_constraintTop_toBottomOf="@+id/account_spinner" />
<com.kdt.mcgui.ProgressLayout
android:id="@+id/progress_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

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

View file

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_app"
android:paddingHorizontal="@dimen/fragment_padding_medium">
<com.kdt.pickafile.FileListView
android:id="@+id/file_selector"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toTopOf="@id/file_selector_create_folder"
app:layout_constraintTop_toBottomOf="@+id/file_selector_current_path" />
<com.kdt.mcgui.MineButton
android:id="@+id/file_selector_select_folder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/_16sdp"
android:layout_marginStart="@dimen/padding_medium"
android:text="@string/folder_fragment_select"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/file_selector_create_folder"
/>
<com.kdt.mcgui.MineButton
android:id="@+id/file_selector_create_folder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/_16sdp"
android:layout_marginEnd="@dimen/padding_medium"
android:text="@string/folder_fragment_create"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_selector_select_folder"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
/>
<TextView
android:id="@+id/file_selector_current_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="."
android:textColor="@color/primary_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_menu_main"
android:gravity="top"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="@color/background_app"
>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/news_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:text="@string/mcl_tab_wiki"
android:drawableStart="@drawable/ic_menu_news"
app:layout_constraintTop_toTopOf="parent"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/custom_control_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@drawable/ic_menu_custom_controls"
android:text="@string/mcl_option_customcontrol"
app:layout_constraintTop_toBottomOf="@id/news_button"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/install_jar_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@drawable/ic_menu_install_jar"
android:text="@string/main_install_jar_file"
app:layout_constraintTop_toBottomOf="@id/custom_control_button"
/>
<com.kdt.mcgui.LauncherMenuButton
android:id="@+id/share_logs_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_66sdp"
android:background="?android:attr/selectableItemBackground"
android:drawableStart="@android:drawable/ic_menu_share"
android:text="@string/main_share_logs"
app:layout_constraintTop_toBottomOf="@id/install_jar_button"
/>
<com.kdt.mcgui.mcVersionSpinner
android:id="@+id/mc_version_spinner"
android:layout_width="0dp"
android:layout_height="@dimen/_52sdp"
android:background="@android:color/transparent"
android:drawableEnd="@drawable/spinner_arrow"
app:drawableEndSize="@dimen/padding_heavy"
app:drawableStartIntegerScaling="true"
app:drawableStartSize="@dimen/_36sdp"
app:drawableEndPadding="@dimen/_1sdp"
app:layout_constraintBottom_toTopOf="@id/play_button"
app:layout_constraintEnd_toStartOf="@+id/edit_profile_button"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/edit_profile_button"
android:layout_width="@dimen/_52sdp"
android:layout_height="@dimen/_52sdp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_edit_profile"
app:layout_constraintBottom_toBottomOf="@+id/mc_version_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/mc_version_spinner" />
<!-- This view is for cosmetic purpose only -->
<View
android:id="@+id/_background_display_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/background_bottom_bar"
android:translationZ="-1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@id/mc_version_spinner" />
<com.kdt.mcgui.MineButton
android:id="@+id/play_button"
android:layout_width="match_parent"
android:layout_height="@dimen/_62sdp"
android:layout_marginHorizontal="@dimen/_17sdp"
android:layout_marginBottom="@dimen/_15sdp"
android:textAllCaps="true"
android:text="@string/main_play"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_app"
android:paddingHorizontal="@dimen/fragment_padding_medium"
>
<View
android:id="@+id/login_menu"
android:layout_width="match_parent"
android:layout_height="@dimen/_200sdp"
android:background="@drawable/background_card"
android:translationZ="-1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_online_username_hint"
app:layout_constraintBottom_toTopOf="@+id/login_edit_email"
app:layout_constraintStart_toStartOf="@+id/login_edit_email" />
<com.kdt.mcgui.MineEditText
android:id="@+id/login_edit_email"
android:layout_width="0dp"
android:layout_height="@dimen/_37sdp"
android:layout_marginHorizontal="@dimen/_25sdp"
android:layout_marginTop="@dimen/_60sdp"
android:imeOptions="flagNoExtractUi"
android:inputType="textEmailAddress"
android:textSize="@dimen/_16ssp"
app:layout_constraintEnd_toEndOf="@+id/login_menu"
app:layout_constraintStart_toStartOf="@+id/login_menu"
app:layout_constraintTop_toTopOf="@+id/login_menu"
app:layout_constraintVertical_bias="0.251" />
<com.kdt.mcgui.MineButton
android:id="@+id/login_button"
android:layout_width="0dp"
android:layout_height="@dimen/_42sdp"
android:layout_marginHorizontal="@dimen/_25sdp"
android:layout_marginTop="@dimen/padding_heavy"
android:onClick="loginMC"
android:text="@string/login_online_login_label"
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_edit_email" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,7 +0,0 @@
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>

View file

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

View file

@ -1,264 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_app"
android:fadeScrollbars="true"
android:scrollbarThumbVertical="@color/minebutton_color"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/fragment_padding_medium">
<TextView
android:id="@+id/vprof_editor_profile_name_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profiles_profile_name"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_profile_name"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_profile_name" />
<EditText
android:id="@+id/vprof_editor_profile_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_extra_large"
android:background="@drawable/background_line"
android:textSize="@dimen/_13ssp"
android:ems="10"
android:hint="@string/unnamed"
android:inputType="textPersonName"
android:paddingHorizontal="@dimen/padding_heavy"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profiles_profile_version"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_version_spinner"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_version_spinner" />
<TextView
android:id="@+id/vprof_editor_version_spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:hint="@string/version_select_hint"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@+id/vprof_editor_version_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_profile_name" />
<Button
android:id="@+id/vprof_editor_version_button"
android:layout_width="@dimen/_72sdp"
android:layout_height="0dp"
android:text="@string/global_select"
app:layout_constraintBottom_toBottomOf="@+id/vprof_editor_version_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/vprof_editor_version_spinner" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/default_control"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_ctrl_spinner"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_ctrl_spinner" />
<TextView
android:id="@+id/vprof_editor_ctrl_spinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:hint="@string/use_global_default"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@id/vprof_editor_ctrl_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_version_spinner" />
<Button
android:id="@+id/vprof_editor_ctrl_button"
android:layout_width="@dimen/_72sdp"
android:layout_height="0dp"
android:text="@string/global_select"
app:layout_constraintBottom_toBottomOf="@id/vprof_editor_ctrl_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/vprof_editor_ctrl_spinner" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pvc_jvmArgs"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_jre_args"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_jre_args" />
<EditText
android:id="@+id/vprof_editor_jre_args"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/background_line"
android:ems="10"
android:hint="@string/use_global_default"
android:inputType="text"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_ctrl_spinner" />
<TextView
android:id="@+id/vprof_editor_beginPathView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Custom path"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_path"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_path" />
<TextView
android:id="@+id/vprof_editor_path"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="@dimen/padding_medium"
android:background="@drawable/background_line"
android:ems="10"
android:hint=".minecraft"
android:paddingHorizontal="@dimen/padding_heavy"
android:textSize="@dimen/_13ssp"
app:layout_constraintEnd_toStartOf="@id/vprof_editor_path_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_jre_args" />
<Button
android:id="@+id/vprof_editor_path_button"
android:layout_width="@dimen/_72sdp"
android:layout_height="0dp"
android:text="@string/global_select"
app:layout_constraintBottom_toBottomOf="@id/vprof_editor_path"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/vprof_editor_path" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pedit_java_runtime"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_spinner_runtime"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_spinner_runtime" />
<Spinner
android:id="@+id/vprof_editor_spinner_runtime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/background_line"
android:paddingVertical="0px"
android:textSize="@dimen/_13ssp"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_path"
tools:paddingVertical="@dimen/_16sdp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/_8sdp"
android:paddingHorizontal="@dimen/_8sdp"
android:rotation="180"
android:src="@drawable/spinner_arrow"
app:layout_constraintBottom_toBottomOf="@id/vprof_editor_spinner_runtime"
app:layout_constraintEnd_toEndOf="@id/vprof_editor_spinner_runtime"
app:layout_constraintTop_toTopOf="@id/vprof_editor_spinner_runtime" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pedit_renderer"
app:layout_constraintBottom_toTopOf="@+id/vprof_editor_profile_renderer"
app:layout_constraintStart_toStartOf="@+id/vprof_editor_profile_renderer" />
<Spinner
android:id="@+id/vprof_editor_profile_renderer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/background_line"
android:dropDownWidth="match_parent"
android:paddingVertical="0px"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_spinner_runtime"
tools:paddingVertical="@dimen/_16sdp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/_8sdp"
android:paddingHorizontal="@dimen/_8sdp"
android:rotation="180"
android:src="@drawable/spinner_arrow"
app:layout_constraintBottom_toBottomOf="@id/vprof_editor_profile_renderer"
app:layout_constraintEnd_toEndOf="@id/vprof_editor_profile_renderer"
app:layout_constraintTop_toTopOf="@id/vprof_editor_profile_renderer" />
<com.kdt.mcgui.MineButton
android:id="@+id/vprof_editor_save_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="26dp"
android:text="@string/global_save"
android:layout_marginEnd="@dimen/padding_medium"
app:layout_constraintEnd_toStartOf="@+id/vprof_editor_delete_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vprof_editor_profile_renderer"
/>
<com.kdt.mcgui.MineButton
android:id="@+id/vprof_editor_delete_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/padding_medium"
android:text="@string/global_delete"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/vprof_editor_save_button"
app:layout_constraintTop_toTopOf="@+id/vprof_editor_save_button"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

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

View file

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/background_app"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="@dimen/fragment_padding_medium">
<View
android:id="@+id/login_menu"
android:layout_width="match_parent"
android:layout_height="@dimen/_200sdp"
android:background="@drawable/background_card"
android:translationZ="-1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.kdt.mcgui.MineButton
android:id="@+id/button_microsoft_authentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Microsoft Account"
android:textSize="@dimen/_12ssp"
android:layout_marginHorizontal="@dimen/_25sdp"
android:layout_marginBottom="@dimen/_20sdp"
app:layout_constraintBottom_toTopOf="@+id/button_local_authentication"
app:layout_constraintEnd_toEndOf="@+id/login_menu"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/login_menu"
app:layout_constraintTop_toTopOf="@id/login_menu"
app:layout_constraintVertical_chainStyle="packed"
/>
<com.kdt.mcgui.MineButton
android:id="@+id/button_local_authentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Local account"
android:textSize="@dimen/_12ssp"
android:layout_marginHorizontal="@dimen/_25sdp"
app:layout_constraintBottom_toBottomOf="@id/login_menu"
app:layout_constraintEnd_toEndOf="@+id/login_menu"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/login_menu"
app:layout_constraintTop_toBottomOf="@+id/button_microsoft_authentication" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Some files were not shown because too many files have changed in this diff Show more