[Change project structure to remove RoboVM depends)
This commit is contained in:
1 parent
0936f4f31d
commit
3c371a10ed
113 files changed
+1900
-6
No files matched your search
@@ -0,0 +1,884 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ArrayMap is a generic key->value mapping data structure that is
|
||||
* designed to be more memory efficient than a traditional {@link java.util.HashMap}.
|
||||
* It keeps its mappings in an array data structure -- an integer array of hash
|
||||
* codes for each item, and an Object array of the key/value pairs. This allows it to
|
||||
* avoid having to create an extra object for every entry put in to the map, and it
|
||||
* also tries to control the growth of the size of these arrays more aggressively
|
||||
* (since growing them only requires copying the entries in the array, not rebuilding
|
||||
* a hash map).
|
||||
*
|
||||
* <p>Note that this implementation is not intended to be appropriate for data structures
|
||||
* that may contain large numbers of items. It is generally slower than a traditional
|
||||
* HashMap, since lookups require a binary search and adds and removes require inserting
|
||||
* and deleting entries in the array. For containers holding up to hundreds of items,
|
||||
* the performance difference is not significant, less than 50%.</p>
|
||||
*
|
||||
* <p>Because this container is intended to better balance memory use, unlike most other
|
||||
* standard Java containers it will shrink its array as items are removed from it. Currently
|
||||
* you have no control over this shrinking -- if you set a capacity and then remove an
|
||||
* item, it may reduce the capacity to better match the current size. In the future an
|
||||
* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
|
||||
*/
|
||||
public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "ArrayMap";
|
||||
|
||||
/**
|
||||
* The minimum amount by which the capacity of a ArrayMap will increase.
|
||||
* This is tuned to be relatively space-efficient.
|
||||
*/
|
||||
private static final int BASE_SIZE = 4;
|
||||
|
||||
/**
|
||||
* Maximum number of entries to have in array caches.
|
||||
*/
|
||||
private static final int CACHE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Special hash array value that indicates the container is immutable.
|
||||
*/
|
||||
static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
|
||||
|
||||
/**
|
||||
* @hide Special immutable empty ArrayMap.
|
||||
*/
|
||||
public static final ArrayMap EMPTY = new ArrayMap(true);
|
||||
|
||||
/**
|
||||
* Caches of small array objects to avoid spamming garbage. The cache
|
||||
* Object[] variable is a pointer to a linked list of array objects.
|
||||
* The first entry in the array is a pointer to the next array in the
|
||||
* list; the second entry is a pointer to the int[] hash code array for it.
|
||||
*/
|
||||
static Object[] mBaseCache;
|
||||
static int mBaseCacheSize;
|
||||
static Object[] mTwiceBaseCache;
|
||||
static int mTwiceBaseCacheSize;
|
||||
|
||||
int[] mHashes;
|
||||
Object[] mArray;
|
||||
int mSize;
|
||||
MapCollections<K, V> mCollections;
|
||||
|
||||
int indexOf(Object key, int hash) {
|
||||
final int N = mSize;
|
||||
|
||||
// Important fast case: if nothing is in here, nothing to look for.
|
||||
if (N == 0) {
|
||||
return ~0;
|
||||
}
|
||||
|
||||
int index = ContainerHelpers.binarySearch(mHashes, N, hash);
|
||||
|
||||
// If the hash code wasn't found, then we have no entry for this key.
|
||||
if (index < 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// If the key at the returned index matches, that's what we want.
|
||||
if (key.equals(mArray[index<<1])) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// Search for a matching key after the index.
|
||||
int end;
|
||||
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
|
||||
if (key.equals(mArray[end << 1])) return end;
|
||||
}
|
||||
|
||||
// Search for a matching key before the index.
|
||||
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
|
||||
if (key.equals(mArray[i << 1])) return i;
|
||||
}
|
||||
|
||||
// Key not found -- return negative value indicating where a
|
||||
// new entry for this key should go. We use the end of the
|
||||
// hash chain to reduce the number of array entries that will
|
||||
// need to be copied when inserting.
|
||||
return ~end;
|
||||
}
|
||||
|
||||
int indexOfNull() {
|
||||
final int N = mSize;
|
||||
|
||||
// Important fast case: if nothing is in here, nothing to look for.
|
||||
if (N == 0) {
|
||||
return ~0;
|
||||
}
|
||||
|
||||
int index = ContainerHelpers.binarySearch(mHashes, N, 0);
|
||||
|
||||
// If the hash code wasn't found, then we have no entry for this key.
|
||||
if (index < 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// If the key at the returned index matches, that's what we want.
|
||||
if (null == mArray[index<<1]) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// Search for a matching key after the index.
|
||||
int end;
|
||||
for (end = index + 1; end < N && mHashes[end] == 0; end++) {
|
||||
if (null == mArray[end << 1]) return end;
|
||||
}
|
||||
|
||||
// Search for a matching key before the index.
|
||||
for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
|
||||
if (null == mArray[i << 1]) return i;
|
||||
}
|
||||
|
||||
// Key not found -- return negative value indicating where a
|
||||
// new entry for this key should go. We use the end of the
|
||||
// hash chain to reduce the number of array entries that will
|
||||
// need to be copied when inserting.
|
||||
return ~end;
|
||||
}
|
||||
|
||||
private void allocArrays(final int size) {
|
||||
if (mHashes == EMPTY_IMMUTABLE_INTS) {
|
||||
throw new UnsupportedOperationException("ArrayMap is immutable");
|
||||
}
|
||||
if (size == (BASE_SIZE*2)) {
|
||||
synchronized (ArrayMap.class) {
|
||||
if (mTwiceBaseCache != null) {
|
||||
final Object[] array = mTwiceBaseCache;
|
||||
mArray = array;
|
||||
mTwiceBaseCache = (Object[])array[0];
|
||||
mHashes = (int[])array[1];
|
||||
array[0] = array[1] = null;
|
||||
mTwiceBaseCacheSize--;
|
||||
if (DEBUG) System.out.println("Retrieving 2x cache " + mHashes
|
||||
+ " now have " + mTwiceBaseCacheSize + " entries");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (size == BASE_SIZE) {
|
||||
synchronized (ArrayMap.class) {
|
||||
if (mBaseCache != null) {
|
||||
final Object[] array = mBaseCache;
|
||||
mArray = array;
|
||||
mBaseCache = (Object[])array[0];
|
||||
mHashes = (int[])array[1];
|
||||
array[0] = array[1] = null;
|
||||
mBaseCacheSize--;
|
||||
if (DEBUG) System.out.println("Retrieving 1x cache " + mHashes
|
||||
+ " now have " + mBaseCacheSize + " entries");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mHashes = new int[size];
|
||||
mArray = new Object[size<<1];
|
||||
}
|
||||
|
||||
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
|
||||
if (hashes.length == (BASE_SIZE*2)) {
|
||||
synchronized (ArrayMap.class) {
|
||||
if (mTwiceBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = mTwiceBaseCache;
|
||||
array[1] = hashes;
|
||||
for (int i=(size<<1)-1; i>=2; i--) {
|
||||
array[i] = null;
|
||||
}
|
||||
mTwiceBaseCache = array;
|
||||
mTwiceBaseCacheSize++;
|
||||
if (DEBUG) System.out.println("Storing 2x cache " + array
|
||||
+ " now have " + mTwiceBaseCacheSize + " entries");
|
||||
}
|
||||
}
|
||||
} else if (hashes.length == BASE_SIZE) {
|
||||
synchronized (ArrayMap.class) {
|
||||
if (mBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = mBaseCache;
|
||||
array[1] = hashes;
|
||||
for (int i=(size<<1)-1; i>=2; i--) {
|
||||
array[i] = null;
|
||||
}
|
||||
mBaseCache = array;
|
||||
mBaseCacheSize++;
|
||||
if (DEBUG) System.out.println("Storing 1x cache " + array
|
||||
+ " now have " + mBaseCacheSize + " entries");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new empty ArrayMap. The default capacity of an array map is 0, and
|
||||
* will grow once items are added to it.
|
||||
*/
|
||||
public ArrayMap() {
|
||||
mHashes = EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ArrayMap with a given initial capacity.
|
||||
*/
|
||||
public ArrayMap(int capacity) {
|
||||
if (capacity == 0) {
|
||||
mHashes = EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
} else {
|
||||
allocArrays(capacity);
|
||||
}
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
private ArrayMap(boolean immutable) {
|
||||
// If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
|
||||
// instance instead of the usual EmptyArray.INT. The reference
|
||||
// is checked later to see if the array is allowed to grow.
|
||||
mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ArrayMap with the mappings from the given ArrayMap.
|
||||
*/
|
||||
public ArrayMap(ArrayMap<K, V> map) {
|
||||
this();
|
||||
if (map != null) {
|
||||
putAll(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the array map empty. All storage is released.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
if (mSize > 0) {
|
||||
freeArrays(mHashes, mArray, mSize);
|
||||
mHashes = EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
mSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
|
||||
*/
|
||||
public void erase() {
|
||||
if (mSize > 0) {
|
||||
final int N = mSize<<1;
|
||||
final Object[] array = mArray;
|
||||
for (int i=0; i<N; i++) {
|
||||
array[i] = null;
|
||||
}
|
||||
mSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the array map can hold at least <var>minimumCapacity</var>
|
||||
* items.
|
||||
*/
|
||||
public void ensureCapacity(int minimumCapacity) {
|
||||
if (mHashes.length < minimumCapacity) {
|
||||
final int[] ohashes = mHashes;
|
||||
final Object[] oarray = mArray;
|
||||
allocArrays(minimumCapacity);
|
||||
if (mSize > 0) {
|
||||
System.arraycopy(ohashes, 0, mHashes, 0, mSize);
|
||||
System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
|
||||
}
|
||||
freeArrays(ohashes, oarray, mSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a key exists in the array.
|
||||
*
|
||||
* @param key The key to search for.
|
||||
* @return Returns true if the key exists, else false.
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return indexOfKey(key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of a key in the set.
|
||||
*
|
||||
* @param key The key to search for.
|
||||
* @return Returns the index of the key if it exists, else a negative integer.
|
||||
*/
|
||||
public int indexOfKey(Object key) {
|
||||
return key == null ? indexOfNull() : indexOf(key, key.hashCode());
|
||||
}
|
||||
|
||||
int indexOfValue(Object value) {
|
||||
final int N = mSize*2;
|
||||
final Object[] array = mArray;
|
||||
if (value == null) {
|
||||
for (int i=1; i<N; i+=2) {
|
||||
if (array[i] == null) {
|
||||
return i>>1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=1; i<N; i+=2) {
|
||||
if (value.equals(array[i])) {
|
||||
return i>>1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a value exists in the array. This requires a linear search
|
||||
* through the entire array.
|
||||
*
|
||||
* @param value The value to search for.
|
||||
* @return Returns true if the value exists, else false.
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return indexOfValue(value) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the array.
|
||||
* @param key The key of the value to retrieve.
|
||||
* @return Returns the value associated with the given key,
|
||||
* or null if there is no such key.
|
||||
*/
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
final int index = indexOfKey(key);
|
||||
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key at the given index in the array.
|
||||
* @param index The desired index, must be between 0 and {@link #size()}-1.
|
||||
* @return Returns the key stored at the given index.
|
||||
*/
|
||||
public K keyAt(int index) {
|
||||
return (K)mArray[index << 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value at the given index in the array.
|
||||
* @param index The desired index, must be between 0 and {@link #size()}-1.
|
||||
* @return Returns the value stored at the given index.
|
||||
*/
|
||||
public V valueAt(int index) {
|
||||
return (V)mArray[(index << 1) + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value at a given index in the array.
|
||||
* @param index The desired index, must be between 0 and {@link #size()}-1.
|
||||
* @param value The new value to store at this index.
|
||||
* @return Returns the previous value at the given index.
|
||||
*/
|
||||
public V setValueAt(int index, V value) {
|
||||
index = (index << 1) + 1;
|
||||
V old = (V)mArray[index];
|
||||
mArray[index] = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the array map contains no items.
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return mSize <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new value to the array map.
|
||||
* @param key The key under which to store the value. If
|
||||
* this key already exists in the array, its value will be replaced.
|
||||
* @param value The value to store for the given key.
|
||||
* @return Returns the old value that was stored for the given key, or null if there
|
||||
* was no such key.
|
||||
*/
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
final int hash;
|
||||
int index;
|
||||
if (key == null) {
|
||||
hash = 0;
|
||||
index = indexOfNull();
|
||||
} else {
|
||||
hash = key.hashCode();
|
||||
index = indexOf(key, hash);
|
||||
}
|
||||
if (index >= 0) {
|
||||
index = (index<<1) + 1;
|
||||
final V old = (V)mArray[index];
|
||||
mArray[index] = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
index = ~index;
|
||||
if (mSize >= mHashes.length) {
|
||||
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
|
||||
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
|
||||
|
||||
if (DEBUG) System.out.println("put: grow from " + mHashes.length + " to " + n);
|
||||
|
||||
final int[] ohashes = mHashes;
|
||||
final Object[] oarray = mArray;
|
||||
allocArrays(n);
|
||||
|
||||
if (mHashes.length > 0) {
|
||||
if (DEBUG) System.out.println("put: copy 0-" + mSize + " to 0");
|
||||
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
|
||||
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
|
||||
}
|
||||
|
||||
freeArrays(ohashes, oarray, mSize);
|
||||
}
|
||||
|
||||
if (index < mSize) {
|
||||
if (DEBUG) System.out.println("put: move " + index + "-" + (mSize-index)
|
||||
+ " to " + (index+1));
|
||||
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
|
||||
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
|
||||
}
|
||||
|
||||
mHashes[index] = hash;
|
||||
mArray[index<<1] = key;
|
||||
mArray[(index<<1)+1] = value;
|
||||
mSize++;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special fast path for appending items to the end of the array without validation.
|
||||
* The array must already be large enough to contain the item.
|
||||
* @hide
|
||||
*/
|
||||
public void append(K key, V value) {
|
||||
int index = mSize;
|
||||
final int hash = key == null ? 0 : key.hashCode();
|
||||
if (index >= mHashes.length) {
|
||||
throw new IllegalStateException("Array is full");
|
||||
}
|
||||
if (index > 0 && mHashes[index-1] > hash) {
|
||||
RuntimeException e = new RuntimeException("here");
|
||||
e.fillInStackTrace();
|
||||
System.out.println("New hash " + hash
|
||||
+ " is before end of array hash " + mHashes[index-1]
|
||||
+ " at index " + index + " key " + key);
|
||||
e.printStackTrace();
|
||||
put(key, value);
|
||||
return;
|
||||
}
|
||||
mSize = index+1;
|
||||
mHashes[index] = hash;
|
||||
index <<= 1;
|
||||
mArray[index] = key;
|
||||
mArray[index+1] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The use of the {@link #append} function can result in invalid array maps, in particular
|
||||
* an array map where the same key appears multiple times. This function verifies that
|
||||
* the array map is valid, throwing IllegalArgumentException if a problem is found. The
|
||||
* main use for this method is validating an array map after unpacking from an IPC, to
|
||||
* protect against malicious callers.
|
||||
* @hide
|
||||
*/
|
||||
public void validate() {
|
||||
final int N = mSize;
|
||||
if (N <= 1) {
|
||||
// There can't be dups.
|
||||
return;
|
||||
}
|
||||
int basehash = mHashes[0];
|
||||
int basei = 0;
|
||||
for (int i=1; i<N; i++) {
|
||||
int hash = mHashes[i];
|
||||
if (hash != basehash) {
|
||||
basehash = hash;
|
||||
basei = i;
|
||||
continue;
|
||||
}
|
||||
// We are in a run of entries with the same hash code. Go backwards through
|
||||
// the array to see if any keys are the same.
|
||||
final Object cur = mArray[i<<1];
|
||||
for (int j=i-1; j>=basei; j--) {
|
||||
final Object prev = mArray[j<<1];
|
||||
if (cur == prev) {
|
||||
throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
|
||||
}
|
||||
if (cur != null && prev != null && cur.equals(prev)) {
|
||||
throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
|
||||
* @param array The array whose contents are to be retrieved.
|
||||
*/
|
||||
public void putAll(ArrayMap<? extends K, ? extends V> array) {
|
||||
final int N = array.mSize;
|
||||
ensureCapacity(mSize + N);
|
||||
if (mSize == 0) {
|
||||
if (N > 0) {
|
||||
System.arraycopy(array.mHashes, 0, mHashes, 0, N);
|
||||
System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
|
||||
mSize = N;
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<N; i++) {
|
||||
put(array.keyAt(i), array.valueAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an existing key from the array map.
|
||||
* @param key The key of the mapping to remove.
|
||||
* @return Returns the value that was stored under the key, or null if there
|
||||
* was no such key.
|
||||
*/
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
final int index = indexOfKey(key);
|
||||
if (index >= 0) {
|
||||
return removeAt(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the key/value mapping at the given index.
|
||||
* @param index The desired index, must be between 0 and {@link #size()}-1.
|
||||
* @return Returns the value that was stored at this index.
|
||||
*/
|
||||
public V removeAt(int index) {
|
||||
final Object old = mArray[(index << 1) + 1];
|
||||
if (mSize <= 1) {
|
||||
// Now empty.
|
||||
if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to 0");
|
||||
freeArrays(mHashes, mArray, mSize);
|
||||
mHashes = EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
mSize = 0;
|
||||
} else {
|
||||
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
|
||||
// Shrunk enough to reduce size of arrays. We don't allow it to
|
||||
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
|
||||
// that and BASE_SIZE.
|
||||
final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
|
||||
|
||||
if (DEBUG) System.out.println("remove: shrink from " + mHashes.length + " to " + n);
|
||||
|
||||
final int[] ohashes = mHashes;
|
||||
final Object[] oarray = mArray;
|
||||
allocArrays(n);
|
||||
|
||||
mSize--;
|
||||
if (index > 0) {
|
||||
if (DEBUG) System.out.println("remove: copy from 0-" + index + " to 0");
|
||||
System.arraycopy(ohashes, 0, mHashes, 0, index);
|
||||
System.arraycopy(oarray, 0, mArray, 0, index << 1);
|
||||
}
|
||||
if (index < mSize) {
|
||||
if (DEBUG) System.out.println("remove: copy from " + (index+1) + "-" + mSize
|
||||
+ " to " + index);
|
||||
System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
|
||||
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
|
||||
(mSize - index) << 1);
|
||||
}
|
||||
} else {
|
||||
mSize--;
|
||||
if (index < mSize) {
|
||||
if (DEBUG) System.out.println("remove: move " + (index+1) + "-" + mSize
|
||||
+ " to " + index);
|
||||
System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
|
||||
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
|
||||
(mSize - index) << 1);
|
||||
}
|
||||
mArray[mSize << 1] = null;
|
||||
mArray[(mSize << 1) + 1] = null;
|
||||
}
|
||||
}
|
||||
return (V)old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items in this array map.
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This implementation returns false if the object is not a map, or
|
||||
* if the maps have different sizes. Otherwise, for each key in this map,
|
||||
* values of both maps are compared. If the values for any key are not
|
||||
* equal, the method returns false, otherwise it returns true.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object instanceof Map) {
|
||||
Map<?, ?> map = (Map<?, ?>) object;
|
||||
if (size() != map.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i=0; i<mSize; i++) {
|
||||
K key = keyAt(i);
|
||||
V mine = valueAt(i);
|
||||
Object theirs = map.get(key);
|
||||
if (mine == null) {
|
||||
if (theirs != null || !map.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!mine.equals(theirs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (NullPointerException ignored) {
|
||||
return false;
|
||||
} catch (ClassCastException ignored) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int[] hashes = mHashes;
|
||||
final Object[] array = mArray;
|
||||
int result = 0;
|
||||
for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
|
||||
Object value = array[v];
|
||||
result += hashes[i] ^ (value == null ? 0 : value.hashCode());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This implementation composes a string by iterating over its mappings. If
|
||||
* this map contains itself as a key or a value, the string "(this Map)"
|
||||
* will appear in its place.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isEmpty()) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder(mSize * 28);
|
||||
buffer.append('{');
|
||||
for (int i=0; i<mSize; i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
Object key = keyAt(i);
|
||||
if (key != this) {
|
||||
buffer.append(key);
|
||||
} else {
|
||||
buffer.append("(this Map)");
|
||||
}
|
||||
buffer.append('=');
|
||||
Object value = valueAt(i);
|
||||
if (value != this) {
|
||||
buffer.append(value);
|
||||
} else {
|
||||
buffer.append("(this Map)");
|
||||
}
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Interop with traditional Java containers. Not as efficient as using
|
||||
// specialized collection APIs.
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
private MapCollections<K, V> getCollection() {
|
||||
if (mCollections == null) {
|
||||
mCollections = new MapCollections<K, V>() {
|
||||
@Override
|
||||
protected int colGetSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object colGetEntry(int index, int offset) {
|
||||
return mArray[(index<<1) + offset];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int colIndexOfKey(Object key) {
|
||||
return indexOfKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int colIndexOfValue(Object value) {
|
||||
return indexOfValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<K, V> colGetMap() {
|
||||
return ArrayMap.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void colPut(K key, V value) {
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V colSetValue(int index, V value) {
|
||||
return setValueAt(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void colRemoveAt(int index) {
|
||||
removeAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void colClear() {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
return mCollections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the array map contains all of the keys in the given collection.
|
||||
* @param collection The collection whose contents are to be checked against.
|
||||
* @return Returns true if this array map contains a key for every entry
|
||||
* in <var>collection</var>, else returns false.
|
||||
*/
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return MapCollections.containsAllHelper(this, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
|
||||
* @param map The map whose contents are to be retrieved.
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> map) {
|
||||
ensureCapacity(mSize + map.size());
|
||||
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all keys in the array map that exist in the given collection.
|
||||
* @param collection The collection whose contents are to be used to remove keys.
|
||||
* @return Returns true if any keys were removed from the array map, else false.
|
||||
*/
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
return MapCollections.removeAllHelper(this, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all keys in the array map that do <b>not</b> exist in the given collection.
|
||||
* @param collection The collection whose contents are to be used to determine which
|
||||
* keys to keep.
|
||||
* @return Returns true if any keys were removed from the array map, else false.
|
||||
*/
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
return MapCollections.retainAllHelper(this, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link java.util.Set} for iterating over and interacting with all mappings
|
||||
* in the array map.
|
||||
*
|
||||
* <p><b>Note:</b> this is a very inefficient way to access the array contents, it
|
||||
* requires generating a number of temporary objects and allocates additional state
|
||||
* information associated with the container that will remain for the life of the container.</p>
|
||||
*
|
||||
* <p><b>Note:</b></p> the semantics of this
|
||||
* Set are subtly different than that of a {@link java.util.HashMap}: most important,
|
||||
* the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
|
||||
* object that exists for the entire iterator, so you can <b>not</b> hold on to it
|
||||
* after calling {@link java.util.Iterator#next() Iterator.next}.</p>
|
||||
*/
|
||||
@Override
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
return getCollection().getEntrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link java.util.Set} for iterating over and interacting with all keys
|
||||
* in the array map.
|
||||
*
|
||||
* <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
|
||||
* requires generating a number of temporary objects and allocates additional state
|
||||
* information associated with the container that will remain for the life of the container.</p>
|
||||
*/
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return getCollection().getKeySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link java.util.Collection} for iterating over and interacting with all values
|
||||
* in the array map.
|
||||
*
|
||||
* <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
|
||||
* requires generating a number of temporary objects and allocates additional state
|
||||
* information associated with the container that will remain for the life of the container.</p>
|
||||
*/
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return getCollection().getValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util;
|
||||
|
||||
class ContainerHelpers {
|
||||
|
||||
// This is Arrays.binarySearch(), but doesn't do any argument validation.
|
||||
static int binarySearch(int[] array, int size, int value) {
|
||||
int lo = 0;
|
||||
int hi = size - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = array[mid];
|
||||
|
||||
if (midVal < value) {
|
||||
lo = mid + 1;
|
||||
} else if (midVal > value) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return mid; // value found
|
||||
}
|
||||
}
|
||||
return ~lo; // value not present
|
||||
}
|
||||
|
||||
static int binarySearch(long[] array, int size, long value) {
|
||||
int lo = 0;
|
||||
int hi = size - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final long midVal = array[mid];
|
||||
|
||||
if (midVal < value) {
|
||||
lo = mid + 1;
|
||||
} else if (midVal > value) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return mid; // value found
|
||||
}
|
||||
}
|
||||
return ~lo; // value not present
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.util;
|
||||
|
||||
public final class EmptyArray {
|
||||
private EmptyArray() {}
|
||||
public static final boolean[] BOOLEAN = new boolean[0];
|
||||
public static final byte[] BYTE = new byte[0];
|
||||
public static final char[] CHAR = new char[0];
|
||||
public static final double[] DOUBLE = new double[0];
|
||||
public static final int[] INT = new int[0];
|
||||
public static final Class<?>[] CLASS = new Class[0];
|
||||
public static final Object[] OBJECT = new Object[0];
|
||||
public static final String[] STRING = new String[0];
|
||||
public static final Throwable[] THROWABLE = new Throwable[0];
|
||||
public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper for writing standard Java collection interfaces to a data
|
||||
* structure like {@link ArrayMap}.
|
||||
* @hide
|
||||
*/
|
||||
abstract class MapCollections<K, V> {
|
||||
EntrySet mEntrySet;
|
||||
KeySet mKeySet;
|
||||
ValuesCollection mValues;
|
||||
|
||||
final class ArrayIterator<T> implements Iterator<T> {
|
||||
final int mOffset;
|
||||
int mSize;
|
||||
int mIndex;
|
||||
boolean mCanRemove = false;
|
||||
|
||||
ArrayIterator(int offset) {
|
||||
mOffset = offset;
|
||||
mSize = colGetSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mIndex < mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
Object res = colGetEntry(mIndex, mOffset);
|
||||
mIndex++;
|
||||
mCanRemove = true;
|
||||
return (T)res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (!mCanRemove) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
mIndex--;
|
||||
mSize--;
|
||||
mCanRemove = false;
|
||||
colRemoveAt(mIndex);
|
||||
}
|
||||
}
|
||||
|
||||
final class MapIterator implements Iterator<Map.Entry<K, V>>, Map.Entry<K, V> {
|
||||
int mEnd;
|
||||
int mIndex;
|
||||
boolean mEntryValid = false;
|
||||
|
||||
MapIterator() {
|
||||
mEnd = colGetSize() - 1;
|
||||
mIndex = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mIndex < mEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, V> next() {
|
||||
mIndex++;
|
||||
mEntryValid = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
colRemoveAt(mIndex);
|
||||
mIndex--;
|
||||
mEnd--;
|
||||
mEntryValid = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException(
|
||||
"This container does not support retaining Map.Entry objects");
|
||||
}
|
||||
return (K)colGetEntry(mIndex, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException(
|
||||
"This container does not support retaining Map.Entry objects");
|
||||
}
|
||||
return (V)colGetEntry(mIndex, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V object) {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException(
|
||||
"This container does not support retaining Map.Entry objects");
|
||||
}
|
||||
return colSetValue(mIndex, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException(
|
||||
"This container does not support retaining Map.Entry objects");
|
||||
}
|
||||
if (!(o instanceof Map.Entry)) {
|
||||
return false;
|
||||
}
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return Objects.equal(e.getKey(), colGetEntry(mIndex, 0))
|
||||
&& Objects.equal(e.getValue(), colGetEntry(mIndex, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
if (!mEntryValid) {
|
||||
throw new IllegalStateException(
|
||||
"This container does not support retaining Map.Entry objects");
|
||||
}
|
||||
final Object key = colGetEntry(mIndex, 0);
|
||||
final Object value = colGetEntry(mIndex, 1);
|
||||
return (key == null ? 0 : key.hashCode()) ^
|
||||
(value == null ? 0 : value.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return getKey() + "=" + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
final class EntrySet implements Set<Map.Entry<K, V>> {
|
||||
@Override
|
||||
public boolean add(Map.Entry<K, V> object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Map.Entry<K, V>> collection) {
|
||||
int oldSize = colGetSize();
|
||||
for (Map.Entry<K, V> entry : collection) {
|
||||
colPut(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return oldSize != colGetSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
colClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Map.Entry))
|
||||
return false;
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
int index = colIndexOfKey(e.getKey());
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
Object foundVal = colGetEntry(index, 1);
|
||||
return Objects.equal(foundVal, e.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!contains(it.next())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return colGetSize() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
return new MapIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return colGetSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] array) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return equalsSetHelper(this, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 0;
|
||||
for (int i=colGetSize()-1; i>=0; i--) {
|
||||
final Object key = colGetEntry(i, 0);
|
||||
final Object value = colGetEntry(i, 1);
|
||||
result += ( (key == null ? 0 : key.hashCode()) ^
|
||||
(value == null ? 0 : value.hashCode()) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
final class KeySet implements Set<K> {
|
||||
|
||||
@Override
|
||||
public boolean add(K object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends K> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
colClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object object) {
|
||||
return colIndexOfKey(object) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return containsAllHelper(colGetMap(), collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return colGetSize() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
return new ArrayIterator<K>(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object object) {
|
||||
int index = colIndexOfKey(object);
|
||||
if (index >= 0) {
|
||||
colRemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
return removeAllHelper(colGetMap(), collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
return retainAllHelper(colGetMap(), collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return colGetSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return toArrayHelper(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] array) {
|
||||
return toArrayHelper(array, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return equalsSetHelper(this, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 0;
|
||||
for (int i=colGetSize()-1; i>=0; i--) {
|
||||
Object obj = colGetEntry(i, 0);
|
||||
result += obj == null ? 0 : obj.hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
final class ValuesCollection implements Collection<V> {
|
||||
|
||||
@Override
|
||||
public boolean add(V object) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends V> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
colClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object object) {
|
||||
return colIndexOfValue(object) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!contains(it.next())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return colGetSize() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return new ArrayIterator<V>(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object object) {
|
||||
int index = colIndexOfValue(object);
|
||||
if (index >= 0) {
|
||||
colRemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
int N = colGetSize();
|
||||
boolean changed = false;
|
||||
for (int i=0; i<N; i++) {
|
||||
Object cur = colGetEntry(i, 1);
|
||||
if (collection.contains(cur)) {
|
||||
colRemoveAt(i);
|
||||
i--;
|
||||
N--;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
int N = colGetSize();
|
||||
boolean changed = false;
|
||||
for (int i=0; i<N; i++) {
|
||||
Object cur = colGetEntry(i, 1);
|
||||
if (!collection.contains(cur)) {
|
||||
colRemoveAt(i);
|
||||
i--;
|
||||
N--;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return colGetSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return toArrayHelper(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] array) {
|
||||
return toArrayHelper(array, 1);
|
||||
}
|
||||
};
|
||||
|
||||
public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) {
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!map.containsKey(it.next())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {
|
||||
int oldSize = map.size();
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
map.remove(it.next());
|
||||
}
|
||||
return oldSize != map.size();
|
||||
}
|
||||
|
||||
public static <K, V> boolean retainAllHelper(Map<K, V> map, Collection<?> collection) {
|
||||
int oldSize = map.size();
|
||||
Iterator<K> it = map.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!collection.contains(it.next())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return oldSize != map.size();
|
||||
}
|
||||
|
||||
public Object[] toArrayHelper(int offset) {
|
||||
final int N = colGetSize();
|
||||
Object[] result = new Object[N];
|
||||
for (int i=0; i<N; i++) {
|
||||
result[i] = colGetEntry(i, offset);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public <T> T[] toArrayHelper(T[] array, int offset) {
|
||||
final int N = colGetSize();
|
||||
if (array.length < N) {
|
||||
@SuppressWarnings("unchecked") T[] newArray
|
||||
= (T[]) Array.newInstance(array.getClass().getComponentType(), N);
|
||||
array = newArray;
|
||||
}
|
||||
for (int i=0; i<N; i++) {
|
||||
array[i] = (T)colGetEntry(i, offset);
|
||||
}
|
||||
if (array.length > N) {
|
||||
array[N] = null;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
|
||||
if (set == object) {
|
||||
return true;
|
||||
}
|
||||
if (object instanceof Set) {
|
||||
Set<?> s = (Set<?>) object;
|
||||
|
||||
try {
|
||||
return set.size() == s.size() && set.containsAll(s);
|
||||
} catch (NullPointerException ignored) {
|
||||
return false;
|
||||
} catch (ClassCastException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Set<Map.Entry<K, V>> getEntrySet() {
|
||||
if (mEntrySet == null) {
|
||||
mEntrySet = new EntrySet();
|
||||
}
|
||||
return mEntrySet;
|
||||
}
|
||||
|
||||
public Set<K> getKeySet() {
|
||||
if (mKeySet == null) {
|
||||
mKeySet = new KeySet();
|
||||
}
|
||||
return mKeySet;
|
||||
}
|
||||
|
||||
public Collection<V> getValues() {
|
||||
if (mValues == null) {
|
||||
mValues = new ValuesCollection();
|
||||
}
|
||||
return mValues;
|
||||
}
|
||||
|
||||
protected abstract int colGetSize();
|
||||
protected abstract Object colGetEntry(int index, int offset);
|
||||
protected abstract int colIndexOfKey(Object key);
|
||||
protected abstract int colIndexOfValue(Object key);
|
||||
protected abstract Map<K, V> colGetMap();
|
||||
protected abstract void colPut(K key, V value);
|
||||
protected abstract V colSetValue(int index, V value);
|
||||
protected abstract void colRemoveAt(int index);
|
||||
protected abstract void colClear();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.util;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
public final class Objects {
|
||||
private Objects() {}
|
||||
/**
|
||||
* Returns true if two possibly-null objects are equal.
|
||||
*/
|
||||
public static boolean equal(Object a, Object b) {
|
||||
return a == b || (a != null && a.equals(b));
|
||||
}
|
||||
public static int hashCode(Object o) {
|
||||
return (o == null) ? 0 : o.hashCode();
|
||||
}
|
||||
/**
|
||||
* Returns a string reporting the value of each declared field, via reflection.
|
||||
* Static and transient fields are automatically skipped. Produces output like
|
||||
* "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
|
||||
*/
|
||||
public static String toString(Object o) {
|
||||
Class<?> c = o.getClass();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(c.getSimpleName()).append('[');
|
||||
int i = 0;
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
|
||||
continue;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
Object value = f.get(o);
|
||||
if (i++ > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(f.getName());
|
||||
sb.append('=');
|
||||
if (value.getClass().isArray()) {
|
||||
if (value.getClass() == boolean[].class) {
|
||||
sb.append(Arrays.toString((boolean[]) value));
|
||||
} else if (value.getClass() == byte[].class) {
|
||||
sb.append(Arrays.toString((byte[]) value));
|
||||
} else if (value.getClass() == char[].class) {
|
||||
sb.append(Arrays.toString((char[]) value));
|
||||
} else if (value.getClass() == double[].class) {
|
||||
sb.append(Arrays.toString((double[]) value));
|
||||
} else if (value.getClass() == float[].class) {
|
||||
sb.append(Arrays.toString((float[]) value));
|
||||
} else if (value.getClass() == int[].class) {
|
||||
sb.append(Arrays.toString((int[]) value));
|
||||
} else if (value.getClass() == long[].class) {
|
||||
sb.append(Arrays.toString((long[]) value));
|
||||
} else if (value.getClass() == short[].class) {
|
||||
sb.append(Arrays.toString((short[]) value));
|
||||
} else {
|
||||
sb.append(Arrays.toString((Object[]) value));
|
||||
}
|
||||
} else if (value.getClass() == Character.class) {
|
||||
sb.append('\'').append(value).append('\'');
|
||||
} else if (value.getClass() == String.class) {
|
||||
sb.append('"').append(value).append('"');
|
||||
} else {
|
||||
sb.append(value);
|
||||
}
|
||||
} catch (IllegalAccessException unexpected) {
|
||||
throw new AssertionError(unexpected);
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.oracle.dalvik;
|
||||
|
||||
import android.util.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class VMLauncher {
|
||||
private VMLauncher() {
|
||||
}
|
||||
public static int launchJVM(boolean useJli, List<String> args) {
|
||||
System.out.print("Launch method: ");
|
||||
if (useJli) {
|
||||
System.out.println("JLI_Launch");
|
||||
return launchJVM(args.toArray(new String[0]));
|
||||
} else {
|
||||
System.out.println("JNI_JavaVM");
|
||||
args.remove(0);
|
||||
int cpPath = args.indexOf("-cp");
|
||||
// int jarPath = Arrays.asList(args).indexOf("-jar");
|
||||
if (cpPath != -1) {
|
||||
String classpath = args.remove(cpPath + 1);
|
||||
args.set(cpPath, "-Djava.class.path=" + classpath);
|
||||
|
||||
List<String> vmArgs = new ArrayList<>();
|
||||
List<String> mainArgs = new ArrayList<>();
|
||||
String mainClass = null;
|
||||
int mainClassIndex;
|
||||
for (String arg : args) {
|
||||
if (!arg.startsWith("-") && mainClass == null) {
|
||||
mainClass = arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mainClass == null) {
|
||||
vmArgs.add(arg);
|
||||
} else {
|
||||
mainArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return createLaunchMainJVM(vmArgs.toArray(new String[0]), mainClass, mainArgs.toArray(new String[0]));
|
||||
} else {
|
||||
throw new RuntimeException("Invalid or unsupported classpath type");
|
||||
}
|
||||
}
|
||||
}
|
||||
private static native int launchJVM(String[] args);
|
||||
private static native int createLaunchMainJVM(String[] vmArgs, String mainClass, String[] mainArgs);
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.util.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class AndroidLWJGLKeycode {
|
||||
// Fix double letters on MC 1.9 and above
|
||||
public static boolean isBackspaceAfterChar = true;
|
||||
private static final ArrayMap<Integer, Integer> androidToLwjglMap;
|
||||
private static String[] androidKeyNameArray;
|
||||
static {
|
||||
// Mapping Android Keycodes to LWJGL Keycodes
|
||||
androidToLwjglMap = new ArrayMap<Integer, Integer>();
|
||||
|
||||
// 0-9 keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
|
||||
// A-Z keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_M);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z);
|
||||
|
||||
// Alt keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
|
||||
|
||||
// Escape key
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
|
||||
// Control keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
|
||||
|
||||
// Arrow keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
|
||||
|
||||
// Fn keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12);
|
||||
// FIXME GLFW Function key
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_FUNCTION, LWJGLGLFWKeycode.GLFW_KEY_FUNCTION);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_KANA, LWJGLGLFWKeycode.GLFW_KEY_K);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
|
||||
|
||||
// Num keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
|
||||
|
||||
// Page keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_POWER, LWJGLGLFWKeycode.GLFW_KEY_POWER);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON);
|
||||
|
||||
// Shift keys
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
|
||||
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_SLEEP, LWJGLGLFWKeycode.GLFW_KEY_SLEEP);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_SYSRQ, LWJGLGLFWKeycode.GLFW_KEY_SYSRQ);
|
||||
androidToLwjglMap.put(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_YEN, LWJGLGLFWKeycode.GLFW_KEY_YEN);
|
||||
|
||||
// androidToLwjglMap.put(KeyEvent.KEYCODE_BUTTON_1, LWJGLGLFWKeycode.G
|
||||
}
|
||||
|
||||
public static String[] generateKeyName() {
|
||||
if (androidKeyNameArray == null) {
|
||||
List<String> keyName = new ArrayList<String>();
|
||||
for (Integer perKey : androidToLwjglMap.keySet()) {
|
||||
keyName.add(KeyEvent.keyCodeToString(perKey.intValue()).replace("KEYCODE_", ""));
|
||||
}
|
||||
androidKeyNameArray = keyName.toArray(new String[0]);
|
||||
}
|
||||
return androidKeyNameArray;
|
||||
}
|
||||
|
||||
public static void execKey(KeyEvent keyEvent, int i, boolean isDown) {
|
||||
for (Map.Entry<Integer, Integer> perKey : androidToLwjglMap.entrySet()) {
|
||||
if (i == 1 && (keyEvent.getSource() == InputDevice.SOURCE_MOUSE)) {
|
||||
// Right mouse detection
|
||||
BaseMainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
|
||||
// BaseMainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
|
||||
} else if (perKey.getKey() == i) {
|
||||
BaseMainActivity.sendKeyPress(perKey.getValue(), keyEvent.getModifiers(), isDown);
|
||||
}
|
||||
}
|
||||
|
||||
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
|
||||
CallbackBridge.holdingCapslock = keyEvent.isCapsLockOn();
|
||||
CallbackBridge.holdingCtrl = keyEvent.isCtrlPressed();
|
||||
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
|
||||
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
|
||||
|
||||
try {
|
||||
if (!CallbackBridge.isGrabbing()) {
|
||||
if (keyEvent.isPrintingKey()) {
|
||||
BaseMainActivity.sendKeyPress(androidToLwjglMap.get(keyEvent.getKeyCode()), (char) keyEvent.getUnicodeChar(keyEvent.getMetaState()), keyEvent.getScanCode(), CallbackBridge.getCurrentMods(), isDown);
|
||||
} else if ((int) keyEvent.getDisplayLabel() != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
BaseMainActivity.sendKeyPress(androidToLwjglMap.get(keyEvent.getKeyCode()), (char) keyEvent.getDisplayLabel(), keyEvent.getScanCode(), CallbackBridge.getCurrentMods(), isDown);
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
|
||||
if (isBackspaceAfterChar && (int) keyEvent.getDisplayLabel() != KeyEvent.KEYCODE_UNKNOWN && !CallbackBridge.isGrabbing() && i != KeyEvent.KEYCODE_DEL) {
|
||||
BaseMainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, 0, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
public static void execKeyIndex(BaseMainActivity mainActivity, int index) {
|
||||
mainActivity.sendKeyPress(getKeyByIndex(index));
|
||||
}
|
||||
|
||||
public static int getKeyByIndex(int index) {
|
||||
return androidToLwjglMap.valueAt(index);
|
||||
}
|
||||
|
||||
public static int getIndexByLWJGLKey(int lwjglKey) {
|
||||
for (int i = 0; i < androidToLwjglMap.size(); i++) {
|
||||
int currKey = androidToLwjglMap.valueAt(i);
|
||||
if (currKey == lwjglKey) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
import android.os.*;
|
||||
|
||||
public class InstallerTask extends AsyncTask<String, Void, String>
|
||||
{
|
||||
@Override
|
||||
protected String doInBackground(String[] p1)
|
||||
{
|
||||
try
|
||||
{
|
||||
downloadLibraries(p1[0]);
|
||||
dexMinecraftLibs();
|
||||
downloadMinecraft(p1[0]);
|
||||
dexMinecraftClient(p1[0]);
|
||||
downloadAssets(p1[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e.getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(String result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
|
||||
if(result == null){
|
||||
//No errors
|
||||
}
|
||||
}
|
||||
private void downloadLibraries(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void dexMinecraftLibs() throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void downloadMinecraft(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void dexMinecraftClient(String version) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
private void downloadAssets(String versionName) throws Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class JAssetInfo
|
||||
{
|
||||
public String hash;
|
||||
public int size;
|
||||
//public String objects;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class JAssets {
|
||||
public Map<String, JAssetInfo> objects;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.util.Map;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import java.util.*;
|
||||
|
||||
public class JMinecraftVersionList {
|
||||
public static final String TYPE_OLD_ALPHA = "old_alpha";
|
||||
public static final String TYPE_OLD_BETA = "old_beta";
|
||||
public static final String TYPE_RELEASE = "release";
|
||||
public static final String TYPE_SNAPSHOT = "snapshot";
|
||||
public Map<String, String> latest;
|
||||
public Version[] versions;
|
||||
|
||||
public static class Version {
|
||||
// Since 1.13, so it's one of ways to check
|
||||
public Arguments arguments;
|
||||
public AssetIndex assetIndex;
|
||||
|
||||
public String assets;
|
||||
public Map<String, MinecraftClientInfo> downloads;
|
||||
public String id;
|
||||
public String inheritsFrom;
|
||||
public DependentLibrary[] libraries;
|
||||
public String mainClass;
|
||||
public String minecraftArguments;
|
||||
public int minimumLauncherVersion;
|
||||
public DependentLibrary optifineLib;
|
||||
public String releaseTime;
|
||||
public String time;
|
||||
public String type;
|
||||
public String url;
|
||||
}
|
||||
|
||||
// Since 1.13
|
||||
public static class Arguments {
|
||||
public Object[] game;
|
||||
public Object[] jvm;
|
||||
|
||||
public static class ArgValue {
|
||||
public ArgRules[] rules;
|
||||
public String value;
|
||||
|
||||
// TLauncher styled argument...
|
||||
public String[] values;
|
||||
|
||||
public static class ArgRules {
|
||||
public String action;
|
||||
public String features;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class AssetIndex {
|
||||
public String id, sha1, url;
|
||||
public long size, totalSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class JVersion extends Object
|
||||
{
|
||||
private String version;
|
||||
private JVersion(String version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
public static JVersion from(String version)
|
||||
{
|
||||
return new JVersion(version);
|
||||
}
|
||||
public boolean isVersionCode()
|
||||
{
|
||||
return !version.contains(".");
|
||||
}
|
||||
public JVersion toVersionCode()
|
||||
{
|
||||
if(!isVersionCode()){
|
||||
version = version.replace(".", "");
|
||||
return this;
|
||||
} else throw new RuntimeException("Can't convert version code to itself");
|
||||
}
|
||||
public JVersion toVersionName()
|
||||
{
|
||||
if(isVersionCode()){
|
||||
StringBuilder charList = new StringBuilder();
|
||||
for(int i=0; i<version.length(); i++){
|
||||
charList.append(version.substring(i, i+1));
|
||||
if(i != version.length() - 1){
|
||||
charList.append(".");
|
||||
}
|
||||
}
|
||||
version = charList.toString();
|
||||
return this;
|
||||
} else throw new RuntimeException("Can't convert version name to itself");
|
||||
}
|
||||
public String getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
public class LWJGLGLFWKeycode
|
||||
{
|
||||
/** The unknown key. */
|
||||
public static final int GLFW_KEY_UNKNOWN = 0; // should be -1
|
||||
|
||||
/** Printable keys. */
|
||||
public static final int
|
||||
GLFW_KEY_SPACE = 32,
|
||||
GLFW_KEY_APOSTROPHE = 39,
|
||||
GLFW_KEY_COMMA = 44,
|
||||
GLFW_KEY_MINUS = 45,
|
||||
GLFW_KEY_PERIOD = 46,
|
||||
GLFW_KEY_SLASH = 47,
|
||||
GLFW_KEY_0 = 48,
|
||||
GLFW_KEY_1 = 49,
|
||||
GLFW_KEY_2 = 50,
|
||||
GLFW_KEY_3 = 51,
|
||||
GLFW_KEY_4 = 52,
|
||||
GLFW_KEY_5 = 53,
|
||||
GLFW_KEY_6 = 54,
|
||||
GLFW_KEY_7 = 55,
|
||||
GLFW_KEY_8 = 56,
|
||||
GLFW_KEY_9 = 57,
|
||||
GLFW_KEY_SEMICOLON = 59,
|
||||
GLFW_KEY_EQUAL = 61,
|
||||
GLFW_KEY_A = 65,
|
||||
GLFW_KEY_B = 66,
|
||||
GLFW_KEY_C = 67,
|
||||
GLFW_KEY_D = 68,
|
||||
GLFW_KEY_E = 69,
|
||||
GLFW_KEY_F = 70,
|
||||
GLFW_KEY_G = 71,
|
||||
GLFW_KEY_H = 72,
|
||||
GLFW_KEY_I = 73,
|
||||
GLFW_KEY_J = 74,
|
||||
GLFW_KEY_K = 75,
|
||||
GLFW_KEY_L = 76,
|
||||
GLFW_KEY_M = 77,
|
||||
GLFW_KEY_N = 78,
|
||||
GLFW_KEY_O = 79,
|
||||
GLFW_KEY_P = 80,
|
||||
GLFW_KEY_Q = 81,
|
||||
GLFW_KEY_R = 82,
|
||||
GLFW_KEY_S = 83,
|
||||
GLFW_KEY_T = 84,
|
||||
GLFW_KEY_U = 85,
|
||||
GLFW_KEY_V = 86,
|
||||
GLFW_KEY_W = 87,
|
||||
GLFW_KEY_X = 88,
|
||||
GLFW_KEY_Y = 89,
|
||||
GLFW_KEY_Z = 90,
|
||||
GLFW_KEY_LEFT_BRACKET = 91,
|
||||
GLFW_KEY_BACKSLASH = 92,
|
||||
GLFW_KEY_RIGHT_BRACKET = 93,
|
||||
GLFW_KEY_GRAVE_ACCENT = 96,
|
||||
GLFW_KEY_WORLD_1 = 161,
|
||||
GLFW_KEY_WORLD_2 = 162;
|
||||
|
||||
/** Function keys. */
|
||||
public static final int
|
||||
GLFW_KEY_ESCAPE = 256,
|
||||
GLFW_KEY_ENTER = 257,
|
||||
GLFW_KEY_TAB = 258,
|
||||
GLFW_KEY_BACKSPACE = 259,
|
||||
GLFW_KEY_INSERT = 260,
|
||||
GLFW_KEY_DELETE = 261,
|
||||
GLFW_KEY_RIGHT = 262,
|
||||
GLFW_KEY_LEFT = 263,
|
||||
GLFW_KEY_DOWN = 264,
|
||||
GLFW_KEY_UP = 265,
|
||||
GLFW_KEY_PAGE_UP = 266,
|
||||
GLFW_KEY_PAGE_DOWN = 267,
|
||||
GLFW_KEY_HOME = 268,
|
||||
GLFW_KEY_END = 269,
|
||||
GLFW_KEY_CAPS_LOCK = 280,
|
||||
GLFW_KEY_SCROLL_LOCK = 281,
|
||||
GLFW_KEY_NUM_LOCK = 282,
|
||||
GLFW_KEY_PRINT_SCREEN = 283,
|
||||
GLFW_KEY_PAUSE = 284,
|
||||
GLFW_KEY_F1 = 290,
|
||||
GLFW_KEY_F2 = 291,
|
||||
GLFW_KEY_F3 = 292,
|
||||
GLFW_KEY_F4 = 293,
|
||||
GLFW_KEY_F5 = 294,
|
||||
GLFW_KEY_F6 = 295,
|
||||
GLFW_KEY_F7 = 296,
|
||||
GLFW_KEY_F8 = 297,
|
||||
GLFW_KEY_F9 = 298,
|
||||
GLFW_KEY_F10 = 299,
|
||||
GLFW_KEY_F11 = 300,
|
||||
GLFW_KEY_F12 = 301,
|
||||
GLFW_KEY_F13 = 302,
|
||||
GLFW_KEY_F14 = 303,
|
||||
GLFW_KEY_F15 = 304,
|
||||
GLFW_KEY_F16 = 305,
|
||||
GLFW_KEY_F17 = 306,
|
||||
GLFW_KEY_F18 = 307,
|
||||
GLFW_KEY_F19 = 308,
|
||||
GLFW_KEY_F20 = 309,
|
||||
GLFW_KEY_F21 = 310,
|
||||
GLFW_KEY_F22 = 311,
|
||||
GLFW_KEY_F23 = 312,
|
||||
GLFW_KEY_F24 = 313,
|
||||
GLFW_KEY_F25 = 314,
|
||||
GLFW_KEY_KP_0 = 320,
|
||||
GLFW_KEY_KP_1 = 321,
|
||||
GLFW_KEY_KP_2 = 322,
|
||||
GLFW_KEY_KP_3 = 323,
|
||||
GLFW_KEY_KP_4 = 324,
|
||||
GLFW_KEY_KP_5 = 325,
|
||||
GLFW_KEY_KP_6 = 326,
|
||||
GLFW_KEY_KP_7 = 327,
|
||||
GLFW_KEY_KP_8 = 328,
|
||||
GLFW_KEY_KP_9 = 329,
|
||||
GLFW_KEY_KP_DECIMAL = 330,
|
||||
GLFW_KEY_KP_DIVIDE = 331,
|
||||
GLFW_KEY_KP_MULTIPLY = 332,
|
||||
GLFW_KEY_KP_SUBTRACT = 333,
|
||||
GLFW_KEY_KP_ADD = 334,
|
||||
GLFW_KEY_KP_ENTER = 335,
|
||||
GLFW_KEY_KP_EQUAL = 336,
|
||||
GLFW_KEY_LEFT_SHIFT = 340,
|
||||
GLFW_KEY_LEFT_CONTROL = 341,
|
||||
GLFW_KEY_LEFT_ALT = 342,
|
||||
GLFW_KEY_LEFT_SUPER = 343,
|
||||
GLFW_KEY_RIGHT_SHIFT = 344,
|
||||
GLFW_KEY_RIGHT_CONTROL = 345,
|
||||
GLFW_KEY_RIGHT_ALT = 346,
|
||||
GLFW_KEY_RIGHT_SUPER = 347,
|
||||
GLFW_KEY_MENU = 348,
|
||||
GLFW_KEY_LAST = GLFW_KEY_MENU;
|
||||
|
||||
/** If this bit is set one or more Shift keys were held down. */
|
||||
public static final int GLFW_MOD_SHIFT = 0x1;
|
||||
|
||||
/** If this bit is set one or more Control keys were held down. */
|
||||
public static final int GLFW_MOD_CONTROL = 0x2;
|
||||
|
||||
/** If this bit is set one or more Alt keys were held down. */
|
||||
public static final int GLFW_MOD_ALT = 0x4;
|
||||
|
||||
/** If this bit is set one or more Super keys were held down. */
|
||||
public static final int GLFW_MOD_SUPER = 0x8;
|
||||
|
||||
/** If this bit is set the Caps Lock key is enabled and the {@link #GLFW_LOCK_KEY_MODS LOCK_KEY_MODS} input mode is set. */
|
||||
public static final int GLFW_MOD_CAPS_LOCK = 0x10;
|
||||
|
||||
/** If this bit is set the Num Lock key is enabled and the {@link #GLFW_LOCK_KEY_MODS LOCK_KEY_MODS} input mode is set. */
|
||||
public static final int GLFW_MOD_NUM_LOCK = 0x20;
|
||||
|
||||
|
||||
/** Mouse buttons. See <a target="_blank" href="http://www.glfw.org/docs/latest/input.html#input_mouse_button">mouse button input</a> for how these are used. */
|
||||
public static final int
|
||||
GLFW_MOUSE_BUTTON_1 = 0,
|
||||
GLFW_MOUSE_BUTTON_2 = 1,
|
||||
GLFW_MOUSE_BUTTON_3 = 2,
|
||||
GLFW_MOUSE_BUTTON_4 = 3,
|
||||
GLFW_MOUSE_BUTTON_5 = 4,
|
||||
GLFW_MOUSE_BUTTON_6 = 5,
|
||||
GLFW_MOUSE_BUTTON_7 = 6,
|
||||
GLFW_MOUSE_BUTTON_8 = 7,
|
||||
GLFW_MOUSE_BUTTON_LAST = GLFW_MOUSE_BUTTON_8,
|
||||
GLFW_MOUSE_BUTTON_LEFT = GLFW_MOUSE_BUTTON_1,
|
||||
GLFW_MOUSE_BUTTON_RIGHT = GLFW_MOUSE_BUTTON_2,
|
||||
GLFW_MOUSE_BUTTON_MIDDLE = GLFW_MOUSE_BUTTON_3;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
import org.robovm.apple.foundation.*;
|
||||
import org.robovm.apple.uikit.*;
|
||||
import org.robovm.pods.dialog.*;
|
||||
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
|
||||
public class PLaunchApp extends UIApplicationDelegateAdapter {
|
||||
|
||||
@Override
|
||||
public boolean didFinishLaunching(UIApplication application,
|
||||
UIApplicationLaunchOptions launchOptions) {
|
||||
File f = new File(Tools.DIR_HOME_JRE);
|
||||
if (f.exists()) {
|
||||
Thread launchThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Application.launch(PLaunchJFXApp.class);
|
||||
}
|
||||
};
|
||||
launchThread.setDaemon(true);
|
||||
launchThread.start();
|
||||
} else {
|
||||
WindowAlertController alertController = new WindowAlertController("Error", "OpenJDK is not installed. Please install before enter launcher.", UIAlertControllerStyle.Alert);
|
||||
alertController.addAction(new UIAlertAction("OK",
|
||||
UIAlertActionStyle.Default, (action) -> {
|
||||
alertController.dismissViewController(true, null);
|
||||
}
|
||||
));
|
||||
alertController.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
/*
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, final Throwable ex) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
ex.printStackTrace(pw);
|
||||
pw.flush();
|
||||
System.err.println("UNCAUGHT EXCEPTION: " + sw.toString());
|
||||
|
||||
// Platform.getPlatform().runOnUIThread(() -> {
|
||||
// WindowAlertController alertController = new WindowAlertController("Error", sw.toString(), UIAlertControllerStyle.Alert);
|
||||
// alertController.addAction(new UIAlertAction("OK",
|
||||
// UIAlertActionStyle.Default, (action) -> {
|
||||
// alertController.dismissViewController(true, null);
|
||||
// }
|
||||
// ));
|
||||
// alertController.show();
|
||||
// });
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
PrintStream filePrintStream = new PrintStream(new FileOutputStream(System.getenv("HOME") + "/log_output.txt"));
|
||||
System.setOut(filePrintStream);
|
||||
System.setErr(filePrintStream);
|
||||
|
||||
System.out.println("Starting UI...");
|
||||
} catch (Throwable th) {
|
||||
throw new RuntimeException(th);
|
||||
}
|
||||
*/
|
||||
|
||||
LauncherPreferences.loadPreferences();
|
||||
|
||||
System.setProperty("java.library.path", Tools.DIR_DATA + "/Frameworks");
|
||||
|
||||
System.setProperty("javafx.verbose", "true");
|
||||
System.setProperty("javafx.platform", "ios");
|
||||
System.setProperty("glass.platform", "ios");
|
||||
System.setProperty("jfxmedia.platforms", "IOSPlatform");
|
||||
System.setProperty("com.sun.javafx.isEmbedded", "true");
|
||||
|
||||
System.setProperty("prism.verbose", "true");
|
||||
System.setProperty("prism.allowhidpi", "true");
|
||||
System.setProperty("prism.mintexturesize", "16");
|
||||
System.setProperty("prism.static.libraries", "true");
|
||||
System.setProperty("prism.useNativeIIO", "false");
|
||||
|
||||
System.out.println("Starting UI...");
|
||||
NSAutoreleasePool pool = new NSAutoreleasePool();
|
||||
UIApplication.main(args, null, PLaunchApp.class);
|
||||
pool.drain();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
import org.lwjgl.glfw.CallbackBridge;
|
||||
|
||||
public class PLaunchJFXApp extends Application {
|
||||
public void start(Stage stage) {
|
||||
Rectangle2D bounds = Screen.getPrimary().getBounds();
|
||||
CallbackBridge.windowWidth = (int) bounds.getWidth();
|
||||
CallbackBridge.windowHeight = (int) bounds.getHeight();
|
||||
|
||||
JREUtils.saveGLContext();
|
||||
|
||||
// Start Minecraft there!
|
||||
File file = new File(Tools.DIR_GAME_NEW);
|
||||
file.mkdirs();
|
||||
|
||||
String mcver = "1.13";
|
||||
try {
|
||||
mcver = Tools.read(Tools.DIR_GAME_HOME + "/config_ver.txt");
|
||||
} catch (IOException e) {
|
||||
System.out.println("config_ver.txt not found, defaulting to Minecraft 1.13");
|
||||
}
|
||||
|
||||
MinecraftAccount acc = new MinecraftAccount();
|
||||
acc.selectedVersion = mcver;
|
||||
JMinecraftVersionList.Version version = Tools.getVersionInfo(mcver);
|
||||
|
||||
try {
|
||||
Tools.launchMinecraft(acc, version);
|
||||
} catch (Throwable th) {
|
||||
Tools.showError(th);
|
||||
}
|
||||
|
||||
/*
|
||||
net.minecraft.client.main.Main.main(new String[]{
|
||||
"--username", "test",
|
||||
"--version", "1.7.10",
|
||||
"--gameDir", file.getAbsolutePath(),
|
||||
"--assetsDir", file.getAbsolutePath() + "/assets",
|
||||
"--assetIndex", "1.7.10",
|
||||
"--uuid", "0",
|
||||
"--accessToken", "0",
|
||||
"--userProperties", "{}",
|
||||
"--userType", "mojang",
|
||||
"--versionType", "release"
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.RefreshTokenTask;
|
||||
import net.kdt.pojavlaunch.value.MinecraftAccount;
|
||||
|
||||
public class PojavProfile
|
||||
{
|
||||
private static String PROFILE_PREF = "pojav_profile";
|
||||
private static String PROFILE_PREF_FILE = "file";
|
||||
private static String PROFILE_PREF_TEMP_CONTENT = "tempContent";
|
||||
|
||||
private static SharedPreferences getPrefs(Context ctx) {
|
||||
return ctx.getSharedPreferences(PROFILE_PREF, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static MinecraftAccount getCurrentProfileContent(Context ctx) throws IOException, JsonSyntaxException {
|
||||
MinecraftAccount build = MinecraftAccount.load(getCurrentProfileName(ctx));
|
||||
if (build == null) {
|
||||
return getTempProfileContent(ctx);
|
||||
}
|
||||
return build;
|
||||
}
|
||||
|
||||
public static MinecraftAccount getTempProfileContent(Context ctx) {
|
||||
return MinecraftAccount.parse(getPrefs(ctx).getString(PROFILE_PREF_TEMP_CONTENT, ""));
|
||||
}
|
||||
|
||||
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 boolean setCurrentProfile(Context ctx, Object obj) {
|
||||
SharedPreferences.Editor pref = getPrefs(ctx).edit();
|
||||
|
||||
try {
|
||||
if (obj instanceof MinecraftAccount) {
|
||||
try {
|
||||
MinecraftAccount.saveTempAccount((MinecraftAccount) obj);
|
||||
} catch (IOException e) {
|
||||
Tools.showError(ctx, e);
|
||||
}
|
||||
} else 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 MinecraftAccount.class, String.class or null");
|
||||
}
|
||||
} finally {
|
||||
return pref.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFileType(Context ctx) {
|
||||
return new File(Tools.DIR_ACCOUNT_NEW + "/" + PojavProfile.getCurrentProfileName(ctx) + ".json").exists();
|
||||
}
|
||||
|
||||
|
||||
public static void launch(Activity ctx, Object o) {
|
||||
PojavProfile.setCurrentProfile(ctx, o);
|
||||
|
||||
Intent intent = new Intent(ctx, PojavV2ActivityManager.getLauncherRemakeVer(ctx)); //MCLauncherActivity.class);
|
||||
ctx.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void updateTokens(final Activity ctx, final String name, RefreshListener listen) throws Exception {
|
||||
new RefreshTokenTask(ctx, listen).execute(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.util.*;
|
||||
import com.google.gson.*;
|
||||
import com.oracle.dalvik.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
import org.robovm.apple.foundation.*;
|
||||
import org.robovm.apple.uikit.*;
|
||||
import org.robovm.pods.dialog.*;
|
||||
import org.robovm.pods.*;
|
||||
|
||||
public final class Tools
|
||||
{
|
||||
public static final boolean ENABLE_DEV_FEATURES = true; // BuildConfig.DEBUG;
|
||||
|
||||
public static String APP_NAME = "null";
|
||||
|
||||
public static final Gson GLOBAL_GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static final String URL_HOME = "https://pojavlauncherteam.github.io/PojavLauncher";
|
||||
public static String DIR_DATA = System.getProperty("java.home");
|
||||
public static String CURRENT_ARCHITECTURE;
|
||||
|
||||
// New since 3.3.1
|
||||
public static String DIR_ACCOUNT_NEW;
|
||||
public static String DIR_ACCOUNT_OLD;
|
||||
public static final String DIR_GAME_HOME = System.getProperty("user.home") + "/Documents";
|
||||
public static final String DIR_GAME_NEW = DIR_GAME_HOME + "/minecraft";
|
||||
|
||||
// New since 3.0.0
|
||||
public static String DIR_HOME_JRE = "/usr/lib/jvm/java-16-openjdk";
|
||||
public static String DIRNAME_HOME_JRE = "lib";
|
||||
|
||||
// New since 2.4.2
|
||||
public static final String DIR_HOME_VERSION = DIR_GAME_NEW + "/versions";
|
||||
public static final String DIR_HOME_LIBRARY = DIR_GAME_NEW + "/libraries";
|
||||
|
||||
public static final String DIR_HOME_CRASH = DIR_GAME_NEW + "/crash-reports";
|
||||
|
||||
public static final String ASSETS_PATH = DIR_GAME_NEW + "/assets";
|
||||
public static final String CTRLMAP_PATH = DIR_GAME_NEW + "/controlmap";
|
||||
public static final String CTRLDEF_FILE = DIR_GAME_NEW + "/controlmap/default.json";
|
||||
|
||||
public static final String LIBNAME_OPTIFINE = "optifine:OptiFine";
|
||||
|
||||
public static void launchMinecraft(MinecraftAccount profile, JMinecraftVersionList.Version versionInfo) throws Throwable {
|
||||
String[] launchArgs = getMinecraftArgs(profile, versionInfo);
|
||||
|
||||
// ctx.appendlnToLog("Minecraft Args: " + Arrays.toString(launchArgs));
|
||||
|
||||
String launchClassPath = generateLaunchClassPath(profile.selectedVersion);
|
||||
|
||||
List<String> javaArgList = new ArrayList<String>();
|
||||
|
||||
javaArgList.add("-cp");
|
||||
javaArgList.add(getLWJGL3ClassPath() + ":" + launchClassPath);
|
||||
|
||||
javaArgList.add(versionInfo.mainClass);
|
||||
javaArgList.addAll(Arrays.asList(launchArgs));
|
||||
|
||||
// Debug
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(new File(Tools.DIR_GAME_HOME, "currargs_generated.txt")));
|
||||
for (String s : javaArgList) {
|
||||
bw.write(s, 0, s.length());
|
||||
bw.write(" ", 0, 1);
|
||||
}
|
||||
bw.close();
|
||||
|
||||
JREUtils.launchJavaVM(javaArgList);
|
||||
}
|
||||
|
||||
public static void getJavaArgs(List<String> javaArgList) {
|
||||
List<String> overrideableArgList = new ArrayList<String>();
|
||||
|
||||
overrideableArgList.add("-Djava.home=" + Tools.DIR_HOME_JRE);
|
||||
overrideableArgList.add("-Djava.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
|
||||
|
||||
overrideableArgList.add("-Duser.home=" + Tools.DIR_GAME_HOME);
|
||||
overrideableArgList.add("-Duser.language=" + System.getProperty("user.language"));
|
||||
// overrideableArgList.add("-Duser.timezone=GMT");
|
||||
|
||||
overrideableArgList.add("-Dos.name=Darwin");
|
||||
overrideableArgList.add("-Dos.version=iOS-" + System.getProperty("os.version"));
|
||||
|
||||
overrideableArgList.add("-Dpojav.path.minecraft=" + Tools.DIR_GAME_NEW);
|
||||
overrideableArgList.add("-Dpojav.path.private.account=" + Tools.DIR_ACCOUNT_NEW);
|
||||
|
||||
// javaArgList.add("-Dorg.lwjgl.libname=liblwjgl3.so");
|
||||
// javaArgList.add("-Dorg.lwjgl.system.jemalloc.libname=libjemalloc.so");
|
||||
|
||||
overrideableArgList.add("-Dorg.lwjgl.opengl.libname=libGL.dylib");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.opengl.libname=libgl4es_115.so");
|
||||
|
||||
// javaArgList.add("-Dorg.lwjgl.opengl.libname=libRegal.so");
|
||||
|
||||
// Enable LWJGL3 debug
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.Debug=true");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.DebugFunctions=true");
|
||||
// overrideableArgList.add("-Dorg.lwjgl.util.DebugLoader=true");
|
||||
|
||||
// GLFW Stub width height
|
||||
overrideableArgList.add("-Dglfwstub.windowWidth=" + CallbackBridge.windowWidth);
|
||||
overrideableArgList.add("-Dglfwstub.windowHeight=" + CallbackBridge.windowHeight);
|
||||
overrideableArgList.add("-Dglfwstub.initEgl=false");
|
||||
|
||||
overrideableArgList.add("-Dnet.minecraft.clientmodname=" + Tools.APP_NAME);
|
||||
|
||||
// Disable FML Early Loading Screen to get Forge 1.14+ works
|
||||
overrideableArgList.add("-Dfml.earlyprogresswindow=false");
|
||||
|
||||
// Override args
|
||||
for (String argOverride : LauncherPreferences.PREF_CUSTOM_JAVA_ARGS.split(" ")) {
|
||||
for (int i = 0; i < overrideableArgList.size(); i++) {
|
||||
String arg = overrideableArgList.get(i);
|
||||
if (arg.startsWith("-D") && argOverride.startsWith(arg.substring(0, arg.indexOf('=') + 1))) {
|
||||
overrideableArgList.set(i, argOverride);
|
||||
break;
|
||||
} else if (i+1 == overrideableArgList.size()) {
|
||||
javaArgList.add(argOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
javaArgList.addAll(overrideableArgList);
|
||||
}
|
||||
|
||||
public static String[] getMinecraftArgs(MinecraftAccount profile, JMinecraftVersionList.Version versionInfo) {
|
||||
String username = profile.username;
|
||||
String versionName = versionInfo.id;
|
||||
if (versionInfo.inheritsFrom != null) {
|
||||
versionName = versionInfo.inheritsFrom;
|
||||
}
|
||||
|
||||
String userType = "mojang";
|
||||
|
||||
File gameDir = new File(Tools.DIR_GAME_NEW);
|
||||
gameDir.mkdirs();
|
||||
|
||||
Map<String, String> varArgMap = new ArrayMap<String, String>();
|
||||
varArgMap.put("auth_access_token", profile.accessToken);
|
||||
varArgMap.put("auth_player_name", username);
|
||||
varArgMap.put("auth_uuid", profile.profileId);
|
||||
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<String>();
|
||||
if (versionInfo.arguments != null) {
|
||||
// Support Minecraft 1.13+
|
||||
for (Object arg : versionInfo.arguments.game) {
|
||||
if (arg instanceof String) {
|
||||
minecraftArgs.add((String) arg);
|
||||
} else {
|
||||
/*
|
||||
JMinecraftVersionList.Arguments.ArgValue argv = (JMinecraftVersionList.Arguments.ArgValue) arg;
|
||||
if (argv.values != null) {
|
||||
minecraftArgs.add(argv.values[0]);
|
||||
} else {
|
||||
|
||||
for (JMinecraftVersionList.Arguments.ArgValue.ArgRules rule : arg.rules) {
|
||||
// rule.action = allow
|
||||
// TODO implement this
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String[] argsFromJson = JSONUtils.insertJSONValueList(
|
||||
splitAndFilterEmpty(
|
||||
versionInfo.minecraftArguments == null ?
|
||||
fromStringArray(minecraftArgs.toArray(new String[0])):
|
||||
versionInfo.minecraftArguments
|
||||
), varArgMap
|
||||
);
|
||||
// Tools.dialogOnUiThread(this, "Result args", Arrays.asList(argsFromJson).toString());
|
||||
return argsFromJson;
|
||||
}
|
||||
|
||||
public static String fromStringArray(String[] strArr) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < strArr.length; i++) {
|
||||
if (i > 0) builder.append(" ");
|
||||
builder.append(strArr[i]);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String[] splitAndFilterEmpty(String argStr) {
|
||||
List<String> strList = new ArrayList<String>();
|
||||
for (String arg : argStr.split(" ")) {
|
||||
if (!arg.isEmpty()) {
|
||||
strList.add(arg);
|
||||
}
|
||||
}
|
||||
strList.add("--fullscreen");
|
||||
return strList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String artifactToPath(String group, String artifact, String version) {
|
||||
return group.replaceAll("\\.", "/") + "/" + artifact + "/" + version + "/" + artifact + "-" + version + ".jar";
|
||||
}
|
||||
|
||||
public static String getPatchedFile(String version) {
|
||||
return DIR_HOME_VERSION + "/" + version + "/" + version + ".jar";
|
||||
}
|
||||
|
||||
private static String getLWJGL3ClassPath() {
|
||||
StringBuilder libStr = new StringBuilder();
|
||||
File lwjgl3Folder = new File(Tools.DIR_GAME_NEW, "lwjgl3");
|
||||
if (/* info.arguments != null && */ lwjgl3Folder.exists()) {
|
||||
for (File file: lwjgl3Folder.listFiles()) {
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
libStr.append(file.getAbsolutePath() + ":");
|
||||
}
|
||||
}
|
||||
// Remove the ':' at the end
|
||||
libStr.setLength(libStr.length() - 1);
|
||||
}
|
||||
return libStr.toString();
|
||||
}
|
||||
|
||||
private static boolean isClientFirst = false;
|
||||
public static String generateLaunchClassPath(String version) {
|
||||
StringBuilder libStr = new StringBuilder(); //versnDir + "/" + version + "/" + version + ".jar:";
|
||||
|
||||
JMinecraftVersionList.Version info = getVersionInfo(version);
|
||||
String[] classpath = generateLibClasspath(info);
|
||||
|
||||
// Debug: LWJGL 3 override
|
||||
// File lwjgl2Folder = new File(Tools.MAIN_PATH, "lwjgl2");
|
||||
|
||||
/*
|
||||
File lwjgl3Folder = new File(Tools.MAIN_PATH, "lwjgl3");
|
||||
if (lwjgl3Folder.exists()) {
|
||||
for (File file: lwjgl3Folder.listFiles()) {
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
libStr.append(file.getAbsolutePath() + ":");
|
||||
}
|
||||
}
|
||||
} else if (lwjgl2Folder.exists()) {
|
||||
for (File file: lwjgl2Folder.listFiles()) {
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
libStr.append(file.getAbsolutePath() + ":");
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (isClientFirst) {
|
||||
libStr.append(getPatchedFile(version));
|
||||
}
|
||||
for (String perJar : classpath) {
|
||||
if (!new File(perJar).exists()) {
|
||||
System.out.println("Ignored non-exists file: " + perJar);
|
||||
continue;
|
||||
}
|
||||
libStr.append((isClientFirst ? ":" : "") + perJar + (!isClientFirst ? ":" : ""));
|
||||
}
|
||||
if (!isClientFirst) {
|
||||
libStr.append(getPatchedFile(version));
|
||||
}
|
||||
|
||||
return libStr.toString();
|
||||
}
|
||||
/*
|
||||
public static DisplayMetrics getDisplayMetrics(Activity ctx) {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
ctx.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
return displayMetrics;
|
||||
}
|
||||
|
||||
public static void setFullscreen(Activity act) {
|
||||
final View decorView = act.getWindow().getDecorView();
|
||||
decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
decorView.setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static DisplayMetrics currentDisplayMetrics;
|
||||
public static void updateWindowSize(Activity ctx) {
|
||||
currentDisplayMetrics = getDisplayMetrics(ctx);
|
||||
CallbackBridge.windowWidth = currentDisplayMetrics.widthPixels;
|
||||
CallbackBridge.windowHeight = currentDisplayMetrics.heightPixels;
|
||||
|
||||
if (CallbackBridge.windowWidth < CallbackBridge.windowHeight) {
|
||||
CallbackBridge.windowWidth = currentDisplayMetrics.heightPixels;
|
||||
CallbackBridge.windowHeight = currentDisplayMetrics.widthPixels;
|
||||
}
|
||||
}
|
||||
|
||||
public static float dpToPx(float dp) {
|
||||
// 921600 = 1280 * 720, default scale
|
||||
// TODO better way to scaling
|
||||
float scaledDp = dp; // / DisplayMetrics.DENSITY_XHIGH * currentDisplayMetrics.densityDpi;
|
||||
return (scaledDp * currentDisplayMetrics.density);
|
||||
}
|
||||
|
||||
public static void copyAssetFile(Context ctx, String fileName, String output, boolean overwrite) throws IOException {
|
||||
copyAssetFile(ctx, fileName, output, new File(fileName).getName(), overwrite);
|
||||
}
|
||||
|
||||
public static void copyAssetFile(Context ctx, String fileName, String output, String outputName, boolean overwrite) throws IOException
|
||||
{
|
||||
File file = new File(output);
|
||||
if(!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
File file2 = new File(output, outputName);
|
||||
if(!file2.exists() || overwrite){
|
||||
write(file2.getAbsolutePath(), loadFromAssetToByte(ctx, fileName));
|
||||
}
|
||||
}
|
||||
|
||||
public static void extractAssetFolder(Activity ctx, String path, String output) throws Exception {
|
||||
extractAssetFolder(ctx, path, output, false);
|
||||
}
|
||||
|
||||
public static void extractAssetFolder(Activity ctx, String path, String output, boolean overwrite) throws Exception {
|
||||
AssetManager assetManager = ctx.getAssets();
|
||||
String assets[] = null;
|
||||
try {
|
||||
assets = assetManager.list(path);
|
||||
if (assets.length == 0) {
|
||||
Tools.copyAssetFile(ctx, path, output, overwrite);
|
||||
} else {
|
||||
File dir = new File(output, path);
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
for (String sub : assets) {
|
||||
extractAssetFolder(ctx, path + "/" + sub, output, overwrite);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showError(ctx, e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
public static void showError(Throwable e) {
|
||||
showError(e, false);
|
||||
}
|
||||
|
||||
public static void showError(final Throwable e, final boolean exitIfOk) {
|
||||
showError("Error", e, exitIfOk, false);
|
||||
}
|
||||
|
||||
public static void showError(final String title, final Throwable e, final boolean exitIfOk) {
|
||||
showError(title, e, exitIfOk, false);
|
||||
}
|
||||
|
||||
private static void showError(final String title, final Throwable e, final boolean exitIfOk, final boolean showMore) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
pw.flush();
|
||||
|
||||
System.err.println(sw.toString());
|
||||
|
||||
Platform.getPlatform().runOnUIThread(() -> {
|
||||
WindowAlertController alertController = new WindowAlertController(title, sw.toString(), UIAlertControllerStyle.Alert);
|
||||
alertController.addAction(new UIAlertAction("OK",
|
||||
UIAlertActionStyle.Default, (action) -> {
|
||||
alertController.dismissViewController(true, null);
|
||||
if (exitIfOk) {
|
||||
System.exit(0);
|
||||
}
|
||||
})
|
||||
);
|
||||
alertController.show();
|
||||
});
|
||||
}
|
||||
/*
|
||||
public static void dialogOnUiThread(final Activity ctx, final CharSequence title, final CharSequence message) {
|
||||
ctx.runOnUiThread(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
new AlertDialog.Builder(ctx)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
*/
|
||||
public static void moveInside(String from, String to) {
|
||||
File fromFile = new File(from);
|
||||
for (File fromInside : fromFile.listFiles()) {
|
||||
moveRecursive(fromInside.getAbsolutePath(), to);
|
||||
}
|
||||
fromFile.delete();
|
||||
}
|
||||
|
||||
public static void moveRecursive(String from, String to) {
|
||||
moveRecursive(new File(from), new File(to));
|
||||
}
|
||||
|
||||
public static void moveRecursive(File from, File to) {
|
||||
File toFrom = new File(to, from.getName());
|
||||
try {
|
||||
if (from.isDirectory()) {
|
||||
for (File child : from.listFiles()) {
|
||||
moveRecursive(child, toFrom);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
from.getParentFile().mkdirs();
|
||||
from.renameTo(toFrom);
|
||||
}
|
||||
}
|
||||
/*
|
||||
public static void openURL(Activity act, String url) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
act.startActivity(browserIntent);
|
||||
}
|
||||
*/
|
||||
public static String[] generateLibClasspath(JMinecraftVersionList.Version info) {
|
||||
List<String> libDir = new ArrayList<String>();
|
||||
|
||||
for (DependentLibrary libItem: info.libraries) {
|
||||
String[] libInfos = libItem.name.split(":");
|
||||
libDir.add(Tools.DIR_HOME_LIBRARY + "/" + Tools.artifactToPath(libInfos[0], libInfos[1], libInfos[2]));
|
||||
}
|
||||
return libDir.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static JMinecraftVersionList.Version getVersionInfo(String versionName) {
|
||||
try {
|
||||
JMinecraftVersionList.Version customVer = Tools.GLOBAL_GSON.fromJson(read(DIR_HOME_VERSION + "/" + versionName + "/" + versionName + ".json"), JMinecraftVersionList.Version.class);
|
||||
for (DependentLibrary lib : customVer.libraries) {
|
||||
if (lib.name.startsWith(LIBNAME_OPTIFINE)) {
|
||||
customVer.optifineLib = lib;
|
||||
}
|
||||
}
|
||||
if (customVer.inheritsFrom == null || customVer.inheritsFrom.equals(customVer.id)) {
|
||||
return customVer;
|
||||
} else {
|
||||
JMinecraftVersionList.Version inheritsVer = Tools.GLOBAL_GSON.fromJson(read(DIR_HOME_VERSION + "/" + customVer.inheritsFrom + "/" + customVer.inheritsFrom + ".json"), JMinecraftVersionList.Version.class);
|
||||
inheritsVer.inheritsFrom = inheritsVer.id;
|
||||
|
||||
insertSafety(inheritsVer, customVer,
|
||||
"assetIndex", "assets", "id",
|
||||
"mainClass", "minecraftArguments",
|
||||
"optifineLib", "releaseTime", "time", "type"
|
||||
);
|
||||
|
||||
List<DependentLibrary> libList = new ArrayList<DependentLibrary>(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)) {
|
||||
System.out.println("Library " + libName + ": Replaced version " +
|
||||
libName.substring(libName.lastIndexOf(":") + 1) + " with " +
|
||||
libAddedName.substring(libAddedName.lastIndexOf(":") + 1));
|
||||
libList.set(i, lib);
|
||||
continue loop_1;
|
||||
}
|
||||
}
|
||||
|
||||
libList.add(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();
|
||||
totalArgList.addAll(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) {
|
||||
for (String key : keyArr) {
|
||||
Object value = null;
|
||||
try {
|
||||
Field fieldA = fromVer.getClass().getDeclaredField(key);
|
||||
value = fieldA.get(fromVer);
|
||||
if (((value instanceof String) && !((String) value).isEmpty()) || value != null) {
|
||||
Field fieldB = targetVer.getClass().getDeclaredField(key);
|
||||
fieldB.set(targetVer, value);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
System.err.println("Unable to insert " + key + "=" + value);
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertStream(InputStream inputStream) throws IOException {
|
||||
return convertStream(inputStream, Charset.forName("UTF-8"));
|
||||
}
|
||||
|
||||
public static String convertStream(InputStream inputStream, Charset charset) throws IOException {
|
||||
String out = "";
|
||||
int len;
|
||||
byte[] buf = new byte[512];
|
||||
while((len = inputStream.read(buf))!=-1) {
|
||||
out += new String(buf,0,len,charset);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static File lastFileModified(String dir) {
|
||||
File fl = new File(dir);
|
||||
|
||||
File[] files = fl.listFiles(new FileFilter() {
|
||||
public boolean accept(File file) {
|
||||
return file.isFile();
|
||||
}
|
||||
});
|
||||
|
||||
long lastMod = Long.MIN_VALUE;
|
||||
File choice = null;
|
||||
for (File file : files) {
|
||||
if (file.lastModified() > lastMod) {
|
||||
choice = file;
|
||||
lastMod = file.lastModified();
|
||||
}
|
||||
}
|
||||
|
||||
return choice;
|
||||
}
|
||||
|
||||
|
||||
public static String read(InputStream is) throws IOException {
|
||||
String out = "";
|
||||
int len;
|
||||
byte[] buf = new byte[512];
|
||||
while((len = is.read(buf))!=-1) {
|
||||
out += new String(buf,0,len);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String read(String path) throws IOException {
|
||||
return read(new FileInputStream(path));
|
||||
}
|
||||
|
||||
public static void write(String path, byte[] content) throws IOException
|
||||
{
|
||||
File outPath = new File(path);
|
||||
outPath.getParentFile().mkdirs();
|
||||
outPath.createNewFile();
|
||||
|
||||
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(path));
|
||||
fos.write(content, 0, content.length);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
public static void write(String path, String content) throws IOException {
|
||||
write(path, content.getBytes());
|
||||
}
|
||||
/*
|
||||
public static byte[] loadFromAssetToByte(Context ctx, String inFile) {
|
||||
byte[] buffer = null;
|
||||
|
||||
try {
|
||||
InputStream stream = ctx.getAssets().open(inFile);
|
||||
|
||||
int size = stream.available();
|
||||
buffer = new byte[size];
|
||||
stream.read(buffer);
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
// Handle exceptions here
|
||||
e.printStackTrace();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
*/
|
||||
public static void downloadFile(String urlInput, String nameOutput) throws IOException {
|
||||
File file = new File(nameOutput);
|
||||
DownloadUtils.downloadFile(urlInput, file);
|
||||
}
|
||||
public abstract static class DownloaderFeedback {
|
||||
public abstract void updateProgress(int curr, int max);
|
||||
}
|
||||
|
||||
public static void downloadFileMonitored(String urlInput,String nameOutput, DownloaderFeedback monitor) throws IOException {
|
||||
if(!new File(nameOutput).exists()){
|
||||
new File(nameOutput).getParentFile().mkdirs();
|
||||
}
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(urlInput).openConnection();
|
||||
InputStream readStr = conn.getInputStream();
|
||||
FileOutputStream fos = new FileOutputStream(new File(nameOutput));
|
||||
int cur = 0; int oval=0; int len = conn.getContentLength(); byte[] buf = new byte[65535];
|
||||
while((cur = readStr.read(buf)) != -1) {
|
||||
oval += cur;
|
||||
fos.write(buf,0,cur);
|
||||
monitor.updateProgress(oval,len);
|
||||
}
|
||||
fos.close();
|
||||
conn.disconnect();
|
||||
}
|
||||
public static class ZipTool
|
||||
{
|
||||
private ZipTool(){}
|
||||
public static void zip(List<File> files, File zipFile) throws IOException {
|
||||
final int BUFFER_SIZE = 2048;
|
||||
|
||||
BufferedInputStream origin = null;
|
||||
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
|
||||
|
||||
try {
|
||||
byte data[] = new byte[BUFFER_SIZE];
|
||||
|
||||
for (File file : files) {
|
||||
FileInputStream fileInputStream = new FileInputStream( file );
|
||||
|
||||
origin = new BufferedInputStream(fileInputStream, BUFFER_SIZE);
|
||||
|
||||
try {
|
||||
ZipEntry entry = new ZipEntry(file.getName());
|
||||
|
||||
out.putNextEntry(entry);
|
||||
|
||||
int count;
|
||||
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
|
||||
out.write(data, 0, count);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
origin.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
public static void unzip(File zipFile, File targetDirectory) throws IOException {
|
||||
final int BUFFER_SIZE = 1024;
|
||||
ZipInputStream zis = new ZipInputStream(
|
||||
new BufferedInputStream(new FileInputStream(zipFile)));
|
||||
try {
|
||||
ZipEntry ze;
|
||||
int count;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
File file = new File(targetDirectory, ze.getName());
|
||||
File dir = ze.isDirectory() ? file : file.getParentFile();
|
||||
if (!dir.isDirectory() && !dir.mkdirs())
|
||||
throw new FileNotFoundException("Failed to ensure directory: " +
|
||||
dir.getAbsolutePath());
|
||||
if (ze.isDirectory())
|
||||
continue;
|
||||
FileOutputStream fout = new FileOutputStream(file);
|
||||
try {
|
||||
while ((count = zis.read(buffer)) != -1)
|
||||
fout.write(buffer, 0, count);
|
||||
} finally {
|
||||
fout.close();
|
||||
}
|
||||
/* if time should be restored as well
|
||||
long time = ze.getTime();
|
||||
if (time > 0)
|
||||
file.setLastModified(time);
|
||||
*/
|
||||
}
|
||||
} finally {
|
||||
zis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
package net.kdt.pojavlaunch.authenticator.microsoft;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
|
||||
import java.net.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.*;
|
||||
import net.kdt.pojavlaunch.authenticator.microsoft.*;
|
||||
import org.json.*;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.io.*;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
|
||||
/*
|
||||
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 mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
|
||||
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
|
||||
*/
|
||||
|
||||
//private Gson gson = new Gson();
|
||||
private RefreshListener listener;
|
||||
|
||||
private Context ctx;
|
||||
private ProgressDialog build;
|
||||
|
||||
public MicrosoftAuthTask(Context ctx, RefreshListener listener) {
|
||||
this.ctx = ctx;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreExecute() {
|
||||
build = new ProgressDialog(ctx);
|
||||
build.setMessage(ctx.getString(R.string.global_waiting));
|
||||
build.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
build.setCancelable(false);
|
||||
build.setMax(6);
|
||||
build.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doInBackground(String... args) {
|
||||
try {
|
||||
/*
|
||||
publishProgress();
|
||||
String msaAccessToken = acquireAccessToken(args[0]);
|
||||
|
||||
publishProgress();
|
||||
String xblToken = acquireXBLToken(msaAccessToken);
|
||||
|
||||
publishProgress();
|
||||
String[] xstsData = acquireXsts(xblToken);
|
||||
|
||||
publishProgress();
|
||||
String mcAccessToken = acquireMinecraftToken(xstsData[0], xstsData[1]);
|
||||
|
||||
publishProgress();
|
||||
|
||||
*/
|
||||
Msa msa = new Msa(this, Boolean.parseBoolean(args[0]), args[1]);
|
||||
|
||||
MinecraftAccount acc = new MinecraftAccount();
|
||||
if (msa.doesOwnGame) {
|
||||
acc.clientToken = "0"; /* FIXME */
|
||||
acc.accessToken = msa.mcToken;
|
||||
acc.username = msa.mcName;
|
||||
acc.profileId = msa.mcUuid;
|
||||
acc.isMicrosoft = true;
|
||||
acc.msaRefreshToken = msa.msRefreshToken;
|
||||
}
|
||||
acc.save();
|
||||
|
||||
return acc;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
public void publishProgressPublic() {
|
||||
super.publishProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void[] p1) {
|
||||
super.onProgressUpdate(p1);
|
||||
build.setProgress(build.getProgress() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Object result) {
|
||||
build.dismiss();
|
||||
if (result instanceof MinecraftAccount) {
|
||||
listener.onSuccess((MinecraftAccount) result);
|
||||
} else {
|
||||
listener.onFailed((Throwable) result);
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://github.com/MiniDigger/MiniLauncher/blob/master/launcher/src/main/java/me/minidigger/minecraftlauncher/launcher/gui/MsaFragmentController.java
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Ammar Ahmad
|
||||
Copyright (c) 2018 Martin Benndorf
|
||||
Copyright (c) 2018 Mark Vainomaa
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
/*
|
||||
private final class XSTSXUI {
|
||||
private String uhs;
|
||||
}
|
||||
|
||||
private final class XSTSDisplayClaims {
|
||||
private XSTSXUI[] xui;
|
||||
}
|
||||
|
||||
private final class GlobalToken {
|
||||
// MSA AccessToken
|
||||
private String access_token;
|
||||
|
||||
// XBL, XSTS Token
|
||||
private String Token;
|
||||
|
||||
// XSTS
|
||||
private XSTSDisplayClaims DisplayClaims;
|
||||
|
||||
// Minecraft side
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
private String acquireAccessToken(String authcode) throws IOException, URISyntaxException{
|
||||
URI uri = new URI(authTokenUrl);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("client_id", "00000000402b5328");
|
||||
data.put("code", authcode);
|
||||
data.put("grant_type", "authorization_code");
|
||||
data.put("redirect_url", "https://login.live.com/oauth20_desktop.srf");
|
||||
data.put("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(ofFormData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).access_token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String acquireXBLToken(String accessToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(xblAuthUrl);
|
||||
|
||||
Map<Object, Object> dataProp = new ArrayMap<>();
|
||||
dataProp.put("AuthMethod", "RPS");
|
||||
dataProp.put("SiteName", "user.auth.xboxlive.com");
|
||||
dataProp.put("RpsTicket", accessToken);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("Properties", dataProp);
|
||||
data.put("RelyingParty", "http://auth.xboxlive.com");
|
||||
data.put("TokenType", "JWT");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).Token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String[] acquireXsts(String xblToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(xstsAuthUrl);
|
||||
|
||||
Map<Object, Object> dataProp = new ArrayMap<>();
|
||||
dataProp.put("SandboxId", "RETAIL");
|
||||
dataProp.put("UserTokens", Arrays.asList(xblToken));
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("Properties", dataProp);
|
||||
data.put("RelyingParty", "rp://api.minecraftservices.com/");
|
||||
data.put("TokenType", "JWT");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
|
||||
String xblXsts = token.Token;
|
||||
String uhs = token.DisplayClaims.xui[0].uhs;
|
||||
|
||||
return new String[]{uhs, xblXsts};
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private String acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(mcLoginUrl);
|
||||
|
||||
Map<Object, Object> data = new ArrayMap<>();
|
||||
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(ofJSONData(data)).build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
return Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class).access_token;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
private MinecraftAccount checkMcProfile(String mcAccessToken) throws IOException, URISyntaxException {
|
||||
URI uri = new URI(mcProfileUrl);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Authorization", "Bearer " + mcAccessToken)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.GET().build();
|
||||
|
||||
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = (String) resp.body();
|
||||
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
|
||||
String uuid = token.id;
|
||||
String uuidDashes = uuid.replaceFirst(
|
||||
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
|
||||
);
|
||||
|
||||
MinecraftAccount acc = new MinecraftAccount();
|
||||
acc.isMicrosoft = true;
|
||||
acc.username = token.name;
|
||||
acc.accessToken = mcAccessToken;
|
||||
acc.profileId = uuidDashes;
|
||||
return acc;
|
||||
} else {
|
||||
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpRequest.BodyPublisher ofJSONData(Map<Object, Object> data) {
|
||||
return HttpRequest.BodyPublishers.ofString(Tools.GLOBAL_GSON.toJson(data));
|
||||
}
|
||||
|
||||
public static HttpRequest.BodyPublisher ofFormData(Map<Object, Object> data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<Object, Object> entry : data.entrySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
try {
|
||||
builder.append(URLEncoder.encode(entry.getKey().toString(), "UTF-8"));
|
||||
builder.append("=");
|
||||
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return HttpRequest.BodyPublishers.ofString(builder.toString());
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package net.kdt.pojavlaunch.authenticator.microsoft;
|
||||
|
||||
import android.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.json.*;
|
||||
|
||||
|
||||
public class Msa {
|
||||
/*
|
||||
private static final String loginUrl = "https://login.live.com/oauth20_authorize.srf" +
|
||||
"?client_id=00000000402b5328" +
|
||||
"&response_type=code" +
|
||||
"&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL" +
|
||||
"&redirect_uri=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf";
|
||||
|
||||
private static final String redirectUrlSuffix = "https://login.live.com/oauth20_desktop.srf?code=";
|
||||
*/
|
||||
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 mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
|
||||
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
|
||||
|
||||
private MicrosoftAuthTask task;
|
||||
|
||||
public boolean isRefresh;
|
||||
public String msRefreshToken;
|
||||
public String mcName;
|
||||
public String mcToken;
|
||||
public String mcUuid;
|
||||
public boolean doesOwnGame;
|
||||
|
||||
public Msa(MicrosoftAuthTask task, boolean isRefresh, String authCode) throws IOException, JSONException {
|
||||
this.task = task;
|
||||
acquireAccessToken(isRefresh, authCode);
|
||||
}
|
||||
|
||||
public void acquireAccessToken(boolean isRefresh, String authcode) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(authTokenUrl);
|
||||
Log.i("MicroAuth", "isRefresh=" + isRefresh + ", authCode= "+authcode);
|
||||
Map<Object, Object> data = new HashMap<>();/*Map.of(
|
||||
"client_id", "00000000402b5328",
|
||||
"code", authcode,
|
||||
"grant_type", "authorization_code",
|
||||
"redirect_uri", "https://login.live.com/oauth20_desktop.srf",
|
||||
"scope", "service::user.auth.xboxlive.com::MBI_SSL"
|
||||
);*/
|
||||
data.put("client_id", "00000000402b5328");
|
||||
data.put(isRefresh ? "refresh_token" : "code", authcode);
|
||||
data.put("grant_type", isRefresh ? "refresh_token" : "authorization_code");
|
||||
data.put("redirect_url", "https://login.live.com/oauth20_desktop.srf");
|
||||
data.put("scope", "service::user.auth.xboxlive.com::MBI_SSL");
|
||||
|
||||
//да пошла yf[eq1 она ваша джава 11
|
||||
String req = ofFormData(data);
|
||||
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(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
msRefreshToken = jo.getString("refresh_token");
|
||||
Log.i("MicroAuth","Acess Token = "+jo.getString("access_token"));
|
||||
acquireXBLToken(jo.getString("access_token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void acquireXBLToken(String accessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(xblAuthUrl);
|
||||
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
Map<Object, Object> properties = new HashMap<>();
|
||||
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");
|
||||
/*Map.of(
|
||||
|
||||
"Properties", Map.of(
|
||||
"AuthMethod", "RPS",
|
||||
"SiteName", "user.auth.xboxlive.com",
|
||||
"RpsTicket", accessToken
|
||||
),
|
||||
"RelyingParty", "http://auth.xboxlive.com",
|
||||
"TokenType", "JWT"
|
||||
);*/
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
Log.i("MicroAuth","Xbl Token = "+jo.getString("Token"));
|
||||
acquireXsts(jo.getString("Token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void acquireXsts(String xblToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(xstsAuthUrl);
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
Map<Object, Object> properties = new HashMap<>();
|
||||
properties.put("SandboxId", "RETAIL");
|
||||
properties.put("UserTokens",Collections.singleton(xblToken));
|
||||
data.put("Properties",properties);
|
||||
data.put("RelyingParty", "rp://api.minecraftservices.com/");
|
||||
data.put("TokenType", "JWT");
|
||||
/*Map<Object, Object> data = Map.of(
|
||||
"Properties", Map.of(
|
||||
"SandboxId", "RETAIL",
|
||||
"UserTokens", List.of(xblToken)
|
||||
),
|
||||
"RelyingParty", "rp://api.minecraftservices.com/",
|
||||
"TokenType", "JWT"
|
||||
);
|
||||
*/
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("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");
|
||||
Log.i("MicroAuth","Xbl Xsts = "+jo.getString("Token")+"; Uhs = " + uhs);
|
||||
acquireMinecraftToken(uhs,jo.getString("Token"));
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(mcLoginUrl);
|
||||
|
||||
Map<Object, Object> data = new HashMap<>();
|
||||
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
|
||||
|
||||
String req = ofJSONData(data);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(req.getBytes("UTF-8").length));
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setUseCaches(false);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.connect();
|
||||
try(OutputStream wr = conn.getOutputStream()) {
|
||||
wr.write(req.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
Log.i("MicroAuth","MC token: "+jo.getString("access_token"));
|
||||
mcToken = jo.getString("access_token");
|
||||
checkMcProfile(jo.getString("access_token"));
|
||||
checkMcStore(jo.getString("access_token"));
|
||||
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
private void checkMcStore(String mcAccessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
URL url = new URL(mcStoreUrl);
|
||||
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
|
||||
conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
if(conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
|
||||
JSONObject jo = new JSONObject(Tools.read(conn.getInputStream()));
|
||||
JSONArray ja = jo.getJSONArray("items");
|
||||
Log.i("MicroAuth","Store Len = " + ja.length());
|
||||
for(int i = 0; i < ja.length(); i++) {
|
||||
String prod = ja.getJSONObject(i).getString("name");
|
||||
Log.i("MicroAuth","Product " + i +": " +prod);
|
||||
}
|
||||
}else{
|
||||
throwResponseError(conn);
|
||||
}
|
||||
/*
|
||||
HttpRequest request = HttpRequest.newBuilder(uri)
|
||||
.header("Authorization", "Bearer " + mcAccessToken)
|
||||
.GET().build();
|
||||
|
||||
HttpClient.newBuilder().build().sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenAccept(resp -> {
|
||||
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
|
||||
String body = resp.body();
|
||||
Log.i("MicroAuth","store: " + body);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private void checkMcProfile(String mcAccessToken) throws IOException, JSONException {
|
||||
task.publishProgressPublic();
|
||||
|
||||
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());
|
||||
Log.i("MicroAuth","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("MicroAuth","UserName = " + name);
|
||||
Log.i("MicroAuth","Uuid Minecraft = " + uuidDashes);
|
||||
mcName=name;
|
||||
mcUuid=uuidDashes;
|
||||
}else{
|
||||
Log.i("MicroAuth","It seems that this Microsoft Account does not own the game.");
|
||||
doesOwnGame = false;
|
||||
throwResponseError(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public static String ofJSONData(Map<Object, Object> data) {
|
||||
return new JSONObject(data).toString();
|
||||
}
|
||||
|
||||
public static String ofFormData(Map<Object, Object> data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<Object, Object> entry : data.entrySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append("&");
|
||||
}
|
||||
try {
|
||||
builder.append(URLEncoder.encode(entry.getKey().toString(), "UTF-8"));
|
||||
builder.append("=");
|
||||
builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Should not happen
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static void throwResponseError(HttpURLConnection conn) throws IOException {
|
||||
String errStr = Tools.read(conn.getErrorStream());
|
||||
Log.i("MicroAuth","Error code: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + "\n" + errStr);
|
||||
throw new RuntimeException("MSA Error: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + ", error stream:\n" + errStr);
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class InvalidateTokenTask extends AsyncTask<String, Void, Throwable> {
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private Gson gson = new Gson();
|
||||
private MinecraftAccount profilePath;
|
||||
|
||||
private Context ctx;
|
||||
private String path;
|
||||
|
||||
public InvalidateTokenTask(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable doInBackground(String... args) {
|
||||
path = args[0];
|
||||
try {
|
||||
this.profilePath = MinecraftAccount.load(args[0]);
|
||||
this.authenticator.invalidate(profilePath.accessToken,
|
||||
UUID.fromString(profilePath.isMicrosoft ? profilePath.profileId : profilePath.clientToken /* should be? */));
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Throwable result) {
|
||||
if (result != null) {
|
||||
Tools.showError(ctx, result);
|
||||
}
|
||||
new File(path).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
public interface LoginListener
|
||||
{
|
||||
public void onBeforeLogin();
|
||||
public void onLoginDone(String[] result);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.os.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class LoginTask extends AsyncTask<String, Void, String[]>
|
||||
{
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private String TAG = "MojangAuth-login";
|
||||
private LoginListener listener;
|
||||
|
||||
public LoginTask setLoginListener(LoginListener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
private UUID getRandomUUID() {
|
||||
return UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
listener.onBeforeLogin();
|
||||
|
||||
super.onPreExecute();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(String[] args) {
|
||||
ArrayList<String> str = new ArrayList<String>();
|
||||
str.add("ERROR");
|
||||
try{
|
||||
try{
|
||||
AuthenticateResponse response = authenticator.authenticate(args[0], args[1], getRandomUUID());
|
||||
if (response.selectedProfile == null) {
|
||||
str.add("Can't login a demo account!\n");
|
||||
} else {
|
||||
if (new File(Tools.DIR_ACCOUNT_NEW + "/" + response.selectedProfile.name + ".json").exists()) {
|
||||
str.add("This account already exist!\n");
|
||||
} else {
|
||||
str.add(response.accessToken); // Access token
|
||||
str.add(response.clientToken.toString()); // Client token
|
||||
str.add(response.selectedProfile.id); // Profile ID
|
||||
str.add(response.selectedProfile.name); // Username
|
||||
str.set(0, "NORMAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
//MainActivity.updateStatus(804);
|
||||
catch(Throwable e){
|
||||
str.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
str.add(e.getMessage());
|
||||
}
|
||||
return str.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String[] result) {
|
||||
listener.onLoginDone(result);
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public interface RefreshListener
|
||||
{
|
||||
public void onFailed(Throwable e);
|
||||
public void onSuccess(MinecraftAccount profile);
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang;
|
||||
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import com.google.gson.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.*;
|
||||
import android.app.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
|
||||
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
|
||||
//private Gson gson = new Gson();
|
||||
private RefreshListener listener;
|
||||
private MinecraftAccount profilePath;
|
||||
|
||||
private Context ctx;
|
||||
private ProgressDialog build;
|
||||
|
||||
public RefreshTokenTask(Context ctx, RefreshListener listener) {
|
||||
this.ctx = ctx;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreExecute() {
|
||||
build = new ProgressDialog(ctx);
|
||||
build.setMessage(ctx.getString(R.string.global_waiting));
|
||||
build.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
build.setCancelable(false);
|
||||
build.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable doInBackground(String... args) {
|
||||
try {
|
||||
this.profilePath = MinecraftAccount.load(args[0]);
|
||||
int responseCode = 400;
|
||||
responseCode = this.authenticator.validate(profilePath.accessToken).statusCode;
|
||||
if (responseCode >= 200 && responseCode < 300) {
|
||||
RefreshResponse response = this.authenticator.refresh(profilePath.accessToken, UUID.fromString(profilePath.clientToken));
|
||||
// if (response == null) {
|
||||
// throw new NullPointerException("Response is null?");
|
||||
// }
|
||||
if (response == null) {
|
||||
// Refresh when offline?
|
||||
return null;
|
||||
} else if (response.selectedProfile == null) {
|
||||
throw new IllegalArgumentException("Can't refresh a demo account!");
|
||||
}
|
||||
|
||||
profilePath.clientToken = response.clientToken.toString();
|
||||
profilePath.accessToken = response.accessToken;
|
||||
profilePath.username = response.selectedProfile.name;
|
||||
profilePath.profileId = response.selectedProfile.id;
|
||||
profilePath.save();
|
||||
}
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Throwable result) {
|
||||
build.dismiss();
|
||||
if (result == null) {
|
||||
listener.onSuccess(null);
|
||||
} else {
|
||||
listener.onFailed(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthenticateRequest {
|
||||
public AgentInfo agent = new AgentInfo();
|
||||
public UUID clientToken;
|
||||
public String password;
|
||||
public String username;
|
||||
|
||||
public static class AgentInfo {
|
||||
public String name;
|
||||
public int version;
|
||||
}
|
||||
|
||||
public AuthenticateRequest(String username, String password, UUID clientToken, String clientName, int clientVersion) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.clientToken = clientToken;
|
||||
this.agent.name = clientName;
|
||||
this.agent.version = clientVersion;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthenticateResponse {
|
||||
public String accessToken;
|
||||
public Profile[] availableProfiles;
|
||||
public UUID clientToken;
|
||||
public Profile selectedProfile;
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
public class ErrorResponse {
|
||||
public String cause;
|
||||
public String error;
|
||||
public String errorMessage;
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class NetworkResponse
|
||||
{
|
||||
public final int statusCode;
|
||||
public final Object response;
|
||||
public NetworkResponse(int statusCode, Object response) {
|
||||
this.statusCode = statusCode;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public void throwExceptionIfNeed(String msg) {
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void throwExceptionIfNeed() {
|
||||
throwExceptionIfNeed("Respone error, code: " + statusCode + ", message: " + response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
public class Profile {
|
||||
public String id;
|
||||
public boolean legacy;
|
||||
public String name;
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RefreshRequest {
|
||||
public String accessToken;
|
||||
public UUID clientToken;
|
||||
|
||||
public RefreshRequest(String accessToken, UUID clientToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.clientToken = clientToken;
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RefreshResponse {
|
||||
public String accessToken;
|
||||
public UUID clientToken;
|
||||
public Profile selectedProfile;
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
public class YggdrasilAuthenticator {
|
||||
private static final String API_URL = "https://authserver.mojang.com/";
|
||||
private String clientName = "Minecraft";
|
||||
private int clientVersion = 1;
|
||||
|
||||
private NetworkResponse makeRequest(String endpoint, Object inputObject, Class<?> responseClass) throws IOException, Throwable {
|
||||
Throwable th;
|
||||
InputStream is = null;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
String requestJson = Tools.GLOBAL_GSON.toJson(inputObject);
|
||||
try {
|
||||
URL url = new URL(API_URL + endpoint);
|
||||
OutputStream os;
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestProperty("User-Agent", "Minecraft");
|
||||
conn.setDoInput(true);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.connect();
|
||||
os = null;
|
||||
os = conn.getOutputStream();
|
||||
os.write(requestJson.getBytes(Charset.forName("UTF-8")));
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
|
||||
int statusCode = conn.getResponseCode();
|
||||
if (statusCode != 200) {
|
||||
is = conn.getErrorStream();
|
||||
} else {
|
||||
is = conn.getInputStream();
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
IOUtils.copy(is, bos);
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
String outString = new String(bos.toByteArray(), Charset.forName("UTF-8"));
|
||||
if (statusCode == 200 || statusCode == 204){
|
||||
System.out.println("Task " + endpoint + " successful");
|
||||
|
||||
if (responseClass != null) {
|
||||
return new NetworkResponse(statusCode, Tools.GLOBAL_GSON.fromJson(outString, responseClass));
|
||||
}
|
||||
} else {
|
||||
System.out.println("Task " + endpoint + " failure");
|
||||
}
|
||||
|
||||
return new NetworkResponse(statusCode, outString);
|
||||
} catch (UnknownHostException e) {
|
||||
if (endpoint.equals("refresh")) {
|
||||
return null;
|
||||
} else {
|
||||
throw new RuntimeException("Can't connect to the server", e);
|
||||
}
|
||||
} catch (Throwable th2) {
|
||||
th = th2;
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception e2) {
|
||||
e2.addSuppressed(th2);
|
||||
throw e2;
|
||||
}
|
||||
}
|
||||
throw th;
|
||||
}
|
||||
} catch (Throwable th3) {
|
||||
th = th3;
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticateResponse authenticate(String username, String password, UUID clientId) throws IOException, Throwable {
|
||||
NetworkResponse obj = makeRequest("authenticate", new AuthenticateRequest(username, password, clientId, this.clientName, this.clientVersion), AuthenticateResponse.class);
|
||||
/*
|
||||
if (obj.statusCode != 200) {
|
||||
throw new RuntimeException("Invalid username or password, status code: " + obj.statusCode);
|
||||
}
|
||||
*/
|
||||
obj.throwExceptionIfNeed();
|
||||
return (AuthenticateResponse) obj.response;
|
||||
}
|
||||
|
||||
public RefreshResponse refresh(String authToken, UUID clientId) throws IOException, Throwable {
|
||||
NetworkResponse obj = makeRequest("refresh", new RefreshRequest(authToken, clientId), RefreshResponse.class);
|
||||
if (obj == null) {
|
||||
return null;
|
||||
} else {
|
||||
obj.throwExceptionIfNeed(); // "Invalid username or password, status code: " + obj.statusCode);
|
||||
return (RefreshResponse) obj.response;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkResponse validate(String authToken) throws Throwable {
|
||||
return makeRequest("validate", new RefreshRequest(authToken, null), null);
|
||||
}
|
||||
|
||||
public NetworkResponse invalidate(String authToken, UUID clientId) throws Throwable {
|
||||
return makeRequest("invalidate", new RefreshRequest(authToken, clientId), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class BaseInstaller {
|
||||
protected File mFile;
|
||||
protected ZipFile mJarFile;
|
||||
|
||||
public void setInput(File file) throws IOException {
|
||||
mFile = file;
|
||||
mJarFile = new ZipFile(file);
|
||||
}
|
||||
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void from(BaseInstaller base) {
|
||||
mFile = base.mFile;
|
||||
mJarFile = base.mJarFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import android.content.*;
|
||||
import java.io.*;
|
||||
import java.util.jar.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.nio.charset.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.apache.commons.io.*;
|
||||
import com.google.gson.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
public class FabricInstaller extends BaseInstaller {
|
||||
public FabricInstaller(BaseInstaller i) {
|
||||
from(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
// Unused ZipFile
|
||||
mJarFile.close();
|
||||
|
||||
String mcversion = ctx.dialogInput("Fabric installer", R.string.main_version);
|
||||
|
||||
ctx.appendlnToLog("Launching JVM");
|
||||
return ctx.launchJavaRuntime(null,
|
||||
"-jar " + mFile.getAbsolutePath() + " client -dir . -mcversion " + mcversion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import java.io.*;
|
||||
import com.google.gson.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
|
||||
public class InstallerDetector
|
||||
{
|
||||
public static boolean isFabric(BaseInstaller installer) {
|
||||
return installer.mJarFile.getEntry("net/fabricmc/installer/Main.class") != null;
|
||||
}
|
||||
|
||||
// Forge Legacy: for 1.12.1 and below
|
||||
public static boolean isForgeLegacy(BaseInstaller installer) throws IOException, JsonSyntaxException {
|
||||
ForgeInstallProfile profile = LegacyForgeInstaller.readInstallProfile(installer);
|
||||
return profile != null && profile.versionInfo != null;
|
||||
}
|
||||
|
||||
// Forge for 1.12.2 only
|
||||
public static boolean isForge1p12p2(BaseInstaller installer) throws IOException, JsonSyntaxException {
|
||||
ForgeInstallProfile profile = LegacyForgeInstaller.readInstallProfile(installer);
|
||||
// Forge 1.12.2 install_profile.json has same format as Forge 1.13+
|
||||
return isForgeNew(installer) && profile.minecraft.equals("1.12.2");
|
||||
}
|
||||
|
||||
// Forge New: for 1.13 and above
|
||||
public static boolean isForgeNew(BaseInstaller installer) throws IOException, JsonSyntaxException {
|
||||
ForgeInstallProfile profile = LegacyForgeInstaller.readInstallProfile(installer);
|
||||
return profile != null && profile.version != null;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import android.content.*;
|
||||
import java.io.*;
|
||||
import java.util.jar.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.nio.charset.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.apache.commons.io.*;
|
||||
import com.google.gson.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
public class Legacy1p12p2ForgeInstaller extends BaseInstaller {
|
||||
public Legacy1p12p2ForgeInstaller(BaseInstaller i) {
|
||||
from(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
String target;
|
||||
|
||||
ctx.appendlnToLog("Reading install_profile.json");
|
||||
ForgeInstallProfile profile = readInstallProfile(this);
|
||||
|
||||
// Write the json file
|
||||
File versionFile = new File(Tools.DIR_HOME_VERSION, profile.version);
|
||||
versionFile.mkdir();
|
||||
target = versionFile.getAbsolutePath() + "/" + profile.version + ".json";
|
||||
ctx.appendlnToLog("Writing " + target + " from " + profile.json);
|
||||
ZipEntry versionJson = mJarFile.getEntry(profile.json==null ? "version.json" : profile.json.substring(profile.json.indexOf("/")+1,profile.json.length()));
|
||||
Tools.write(
|
||||
target,
|
||||
Tools.convertStream(mJarFile.getInputStream(versionJson))
|
||||
);
|
||||
|
||||
// Forge 1.12.2+ installer does not include universal, so download
|
||||
// Users are already go throught Forge ads to download installer, so not again.
|
||||
String[] libInfos = profile.path.split(":");
|
||||
File libraryFile = new File(Tools.DIR_HOME_LIBRARY, Tools.artifactToPath(libInfos[0], libInfos[1], libInfos[2]));
|
||||
libraryFile.getParentFile().mkdirs();
|
||||
target = libraryFile.getAbsolutePath();
|
||||
String downloadPath = "https://files.minecraftforge.net/maven/" + profile.path.replace(":", "/").replace("net.minecraftforge","net/minecraftforge") + "/forge-" + libInfos[2] + "-universal.jar";
|
||||
ctx.appendlnToLog("Downloading " + target);
|
||||
Tools.downloadFile(downloadPath, target);
|
||||
|
||||
mJarFile.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static ForgeInstallProfile readInstallProfile(BaseInstaller base) throws IOException, JsonSyntaxException {
|
||||
ZipEntry entry = base.mJarFile.getEntry("install_profile.json");
|
||||
return entry == null ? null : Tools.GLOBAL_GSON.fromJson(
|
||||
Tools.convertStream(
|
||||
base.mJarFile.getInputStream(entry)
|
||||
),
|
||||
ForgeInstallProfile.class
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
import android.content.*;
|
||||
import java.io.*;
|
||||
import java.util.jar.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.nio.charset.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.apache.commons.io.*;
|
||||
import com.google.gson.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
public class LegacyForgeInstaller extends BaseInstaller {
|
||||
public LegacyForgeInstaller(BaseInstaller i) {
|
||||
from(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
String target;
|
||||
|
||||
ctx.appendlnToLog("Reading install_profile.json");
|
||||
ForgeInstallProfile profile = readInstallProfile(this);
|
||||
|
||||
// Write the json file
|
||||
File versionFile = new File(Tools.DIR_HOME_VERSION, profile.install.target);
|
||||
versionFile.mkdir();
|
||||
target = versionFile.getAbsolutePath() + "/" + profile.install.target + ".json";
|
||||
ctx.appendlnToLog("Writing " + target);
|
||||
Tools.write(
|
||||
target,
|
||||
Tools.GLOBAL_GSON.toJson(profile.versionInfo)
|
||||
);
|
||||
|
||||
// Extract Forge universal
|
||||
String[] libInfos = profile.install.path.split(":");
|
||||
File libraryFile = new File(Tools.DIR_HOME_LIBRARY, Tools.artifactToPath(libInfos[0], libInfos[1], libInfos[2]));
|
||||
libraryFile.getParentFile().mkdirs();
|
||||
target = libraryFile.getAbsolutePath().replace("-universal", "");
|
||||
ctx.appendlnToLog("Writing " + target);
|
||||
InputStream in = mJarFile.getInputStream(mJarFile.getEntry(profile.install.filePath));
|
||||
FileOutputStream out = new FileOutputStream(target);
|
||||
IOUtils.copy(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
|
||||
mJarFile.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static ForgeInstallProfile readInstallProfile(BaseInstaller base) throws IOException, JsonSyntaxException {
|
||||
ZipEntry entry = base.mJarFile.getEntry("install_profile.json");
|
||||
return entry == null ? null : Tools.GLOBAL_GSON.fromJson(
|
||||
Tools.convertStream(
|
||||
base.mJarFile.getInputStream(entry),
|
||||
Charset.forName("UTF-8")
|
||||
),
|
||||
ForgeInstallProfile.class
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import android.content.*;
|
||||
import java.io.*;
|
||||
import java.util.jar.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.nio.charset.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.apache.commons.io.*;
|
||||
import com.google.gson.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
public class LegacyOptifineInstaller extends BaseInstaller {
|
||||
public LegacyOptifineInstaller(BaseInstaller i) {
|
||||
from(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.kdt.pojavlaunch.installers;
|
||||
|
||||
import java.io.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class NewForgeInstaller extends BaseInstaller
|
||||
{
|
||||
public NewForgeInstaller(BaseInstaller i) {
|
||||
from(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int install(JavaGUILauncherActivity ctx) throws IOException {
|
||||
// Unused ZipFile
|
||||
mJarFile.close();
|
||||
|
||||
ctx.appendlnToLog("Launching JVM");
|
||||
return ctx.launchJavaRuntime(null,
|
||||
"-cp " + Tools.DIR_GAME_NEW + "/config/forge-installer-headless.jar:" + mFile.getAbsolutePath() + " me.xfl03.HeadlessInstaller --installClient .");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package net.kdt.pojavlaunch.prefs;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class LauncherPreferences
|
||||
{
|
||||
// public static SharedPreferences DEFAULT_PREF;
|
||||
public static boolean PREF_VERTYPE_RELEASE = true;
|
||||
public static boolean PREF_VERTYPE_SNAPSHOT = false;
|
||||
public static boolean PREF_VERTYPE_OLDALPHA = false;
|
||||
public static boolean PREF_VERTYPE_OLDBETA = false;
|
||||
public static boolean PREF_FREEFORM = false;
|
||||
public static float PREF_BUTTONSIZE = 100f;
|
||||
public static float PREF_MOUSESCALE = 100f;
|
||||
public static int PREF_LONGPRESS_TRIGGER = 500;
|
||||
public static String PREF_DEFAULTCTRL_PATH = Tools.CTRLDEF_FILE;
|
||||
public static String PREF_CUSTOM_JAVA_ARGS;
|
||||
public static String PREF_CUSTOM_OPENGL_LIBNAME = "libGL.dylib";
|
||||
public static String PREF_LANGUAGE = "default";
|
||||
|
||||
public static void loadPreferences() {
|
||||
/*
|
||||
PREF_BUTTONSIZE = DEFAULT_PREF.getInt("buttonscale", 100);
|
||||
PREF_MOUSESCALE = DEFAULT_PREF.getInt("mousescale", 100);
|
||||
PREF_FREEFORM = DEFAULT_PREF.getBoolean("freeform", false);
|
||||
PREF_VERTYPE_RELEASE = DEFAULT_PREF.getBoolean("vertype_release", true);
|
||||
PREF_VERTYPE_SNAPSHOT = DEFAULT_PREF.getBoolean("vertype_snapshot", false);
|
||||
PREF_VERTYPE_OLDALPHA = DEFAULT_PREF.getBoolean("vertype_oldalpha", false);
|
||||
PREF_VERTYPE_OLDBETA = DEFAULT_PREF.getBoolean("vertype_oldbeta", false);
|
||||
PREF_LONGPRESS_TRIGGER = DEFAULT_PREF.getInt("timeLongPressTrigger", 500);
|
||||
PREF_DEFAULTCTRL_PATH = DEFAULT_PREF.getString("defaultCtrl", Tools.CTRLDEF_FILE);
|
||||
PREF_LANGUAGE = DEFAULT_PREF.getString("language", "default");
|
||||
|
||||
// Get double of max Android heap to set default heap size
|
||||
int androidHeap = (int) (Runtime.getRuntime().maxMemory() / 1024l / 512l);
|
||||
int doubleAndroidHeap = androidHeap * 2;
|
||||
// PREF_CUSTOM_JAVA_ARGS = DEFAULT_PREF.getString("javaArgs", "");
|
||||
if (PREF_CUSTOM_JAVA_ARGS.isEmpty()) {
|
||||
String DEFAULT_JAVA_ARGS =
|
||||
"-Xms" + (androidHeap > 800 ? 800 : androidHeap) + "m " +
|
||||
// (32bit) More than 800mb may make JVM not allocateable and crash
|
||||
"-Xmx" + (doubleAndroidHeap > 800 ? 800 : doubleAndroidHeap) + "m " +
|
||||
"-XX:+UseG1GC " +
|
||||
"-XX:+ParallelRefProcEnabled " +
|
||||
"-XX:MaxGCPauseMillis=200 " +
|
||||
"-XX:+UnlockExperimentalVMOptions " +
|
||||
"-XX:+AlwaysPreTouch " +
|
||||
"-XX:G1NewSizePercent=30 " +
|
||||
"-XX:G1MaxNewSizePercent=40 " +
|
||||
"-XX:G1HeapRegionSize=8M " +
|
||||
"-XX:G1ReservePercent=20 " +
|
||||
"-XX:G1HeapWastePercent=5 " +
|
||||
"-XX:G1MixedGCCountTarget=4 " +
|
||||
"-XX:InitiatingHeapOccupancyPercent=15 " +
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90 " +
|
||||
"-XX:G1RSetUpdatingPauseTimePercent=5 " +
|
||||
"-XX:SurvivorRatio=32 " +
|
||||
"-XX:+PerfDisableSharedMem " +
|
||||
"-XX:MaxTenuringThreshold=1";
|
||||
|
||||
PREF_CUSTOM_JAVA_ARGS = DEFAULT_JAVA_ARGS;
|
||||
DEFAULT_PREF.edit().putString("javaArgs", DEFAULT_JAVA_ARGS).commit();
|
||||
}
|
||||
|
||||
String argLwjglLibname = "-Dorg.lwjgl.opengl.libname=";
|
||||
for (String arg : PREF_CUSTOM_JAVA_ARGS.split(" ")) {
|
||||
if (arg.startsWith(argLwjglLibname)) {
|
||||
PREF_CUSTOM_OPENGL_LIBNAME = arg.substring(argLwjglLibname.length());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
File argsFile = new File(Tools.DIR_GAME_HOME, "overrideargs.txt");
|
||||
try {
|
||||
if (!argsFile.exists()) {
|
||||
argsFile.createNewFile();
|
||||
}
|
||||
PREF_CUSTOM_JAVA_ARGS = Tools.read(argsFile.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.graphics.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
import com.google.gson.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
import net.kdt.pojavlaunch.value.*;
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
public class MinecraftDownloaderTask extends AsyncTask<String, String, Throwable>
|
||||
{
|
||||
private BaseLauncherActivity mActivity;
|
||||
private boolean launchWithError = false;
|
||||
MinecraftDownloaderTask thiz = this;
|
||||
public MinecraftDownloaderTask(BaseLauncherActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mActivity.mLaunchProgress.setMax(1);
|
||||
mActivity.statusIsLaunching(true);
|
||||
}
|
||||
|
||||
private JMinecraftVersionList.Version verInfo;
|
||||
@Override
|
||||
protected Throwable doInBackground(final String[] p1) {
|
||||
Throwable throwable = null;
|
||||
try {
|
||||
final String downVName = "/" + p1[0] + "/" + p1[0];
|
||||
//Downloading libraries
|
||||
String minecraftMainJar = Tools.DIR_HOME_VERSION + downVName + ".jar";
|
||||
JAssets assets = null;
|
||||
try {
|
||||
String verJsonDir = Tools.DIR_HOME_VERSION + downVName + ".json";
|
||||
|
||||
verInfo = findVersion(p1[0]);
|
||||
|
||||
if (verInfo.url != null && !new File(verJsonDir).exists()) {
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".json"));
|
||||
|
||||
Tools.downloadFileMonitored(
|
||||
verInfo.url,
|
||||
verJsonDir,
|
||||
new Tools.DownloaderFeedback(){
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(p1[0] + ".json", curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
verInfo = Tools.getVersionInfo(p1[0]);
|
||||
assets = downloadIndex(verInfo.assets, new File(Tools.ASSETS_PATH, "indexes/" + verInfo.assets + ".json"));
|
||||
|
||||
File outLib;
|
||||
String libPathURL;
|
||||
|
||||
setMax(verInfo.libraries.length);
|
||||
zeroProgress();
|
||||
for (final DependentLibrary libItem : verInfo.libraries) {
|
||||
|
||||
if (
|
||||
libItem.name.startsWith("net.java.jinput") ||
|
||||
libItem.name.startsWith("org.lwjgl")
|
||||
) { // Black list
|
||||
publishProgress("1", "Ignored " + libItem.name);
|
||||
//Thread.sleep(100);
|
||||
} else {
|
||||
String[] libInfo = libItem.name.split(":");
|
||||
String libArtifact = Tools.artifactToPath(libInfo[0], libInfo[1], libInfo[2]);
|
||||
outLib = new File(Tools.DIR_HOME_LIBRARY + "/" + libArtifact);
|
||||
outLib.getParentFile().mkdirs();
|
||||
|
||||
if (!outLib.exists()) {
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, libItem.name));
|
||||
|
||||
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) + libArtifact;
|
||||
libItem.downloads = new DependentLibrary.LibraryDownloads(artifact);
|
||||
|
||||
skipIfFailed = true;
|
||||
}
|
||||
try {
|
||||
libPathURL = libItem.downloads.artifact.url;
|
||||
Tools.downloadFileMonitored(
|
||||
libPathURL,
|
||||
outLib.getAbsolutePath(),
|
||||
new Tools.DownloaderFeedback() {
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(libItem.name, curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable th) {
|
||||
if (!skipIfFailed) {
|
||||
throw th;
|
||||
} else {
|
||||
th.printStackTrace();
|
||||
publishProgress("0", th.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setMax(3);
|
||||
zeroProgress();
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, p1[0] + ".jar"));
|
||||
File minecraftMainFile = new File(minecraftMainJar);
|
||||
if (!minecraftMainFile.exists() || minecraftMainFile.length() == 0l) {
|
||||
try {
|
||||
Tools.downloadFileMonitored(
|
||||
verInfo.downloads.values().toArray(new MinecraftClientInfo[0])[0].url,
|
||||
minecraftMainJar,
|
||||
new Tools.DownloaderFeedback() {
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
publishDownloadProgress(p1[0] + ".jar", curr, max);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable th) {
|
||||
if (verInfo.inheritsFrom != null) {
|
||||
minecraftMainFile.delete();
|
||||
FileInputStream is = new FileInputStream(new File(Tools.DIR_HOME_VERSION, verInfo.inheritsFrom + "/" + verInfo.inheritsFrom + ".jar"));
|
||||
FileOutputStream os = new FileOutputStream(minecraftMainFile);
|
||||
IOUtils.copy(is, os);
|
||||
is.close();
|
||||
os.close();
|
||||
} else {
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
launchWithError = true;
|
||||
throw e;
|
||||
}
|
||||
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_cleancache));
|
||||
// new File(inputPath).delete();
|
||||
|
||||
for (File f : new File(Tools.DIR_HOME_VERSION).listFiles()) {
|
||||
if(f.getName().endsWith(".part")) {
|
||||
System.out.println("Cleaning cache: " + f);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
mActivity.mIsAssetsProcessing = true;
|
||||
mActivity.mPlayButton.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mActivity.mPlayButton.setText("Skip");
|
||||
mActivity.mPlayButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_download_assets));
|
||||
setMax(assets.objects.size());
|
||||
zeroProgress();
|
||||
try {
|
||||
downloadAssets(assets, verInfo.assets, new File(Tools.ASSETS_PATH));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// Ignore it
|
||||
launchWithError = false;
|
||||
} finally {
|
||||
mActivity.mIsAssetsProcessing = false;
|
||||
}
|
||||
} catch (Throwable th){
|
||||
throwable = th;
|
||||
} finally {
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
private int addProgress = 0;
|
||||
|
||||
public void zeroProgress() {
|
||||
addProgress = 0;
|
||||
}
|
||||
|
||||
public void setMax(final int value)
|
||||
{
|
||||
mActivity.mLaunchProgress.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mActivity.mLaunchProgress.setMax(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void publishDownloadProgress(String target, int curr, int max) {
|
||||
// array length > 2 ignores append log on dev console
|
||||
publishProgress("0", mActivity.getString(R.string.mcl_launch_downloading_progress, target,
|
||||
curr / 1024d / 1024d, max / 1024d / 1024d), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... p1)
|
||||
{
|
||||
int addedProg = Integer.parseInt(p1[0]);
|
||||
if (addedProg != -1) {
|
||||
addProgress = addProgress + addedProg;
|
||||
mActivity.mLaunchProgress.setProgress(addProgress);
|
||||
|
||||
mActivity.mLaunchTextStatus.setText(p1[1]);
|
||||
}
|
||||
|
||||
if (p1.length < 3) {
|
||||
mActivity.mConsoleView.putLog(p1[1] + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Throwable p1)
|
||||
{
|
||||
mActivity.mPlayButton.setText("Play");
|
||||
mActivity.mPlayButton.setEnabled(true);
|
||||
mActivity.mLaunchProgress.setMax(100);
|
||||
mActivity.mLaunchProgress.setProgress(0);
|
||||
mActivity.statusIsLaunching(false);
|
||||
if(p1 != null) {
|
||||
p1.printStackTrace();
|
||||
Tools.showError(mActivity, p1);
|
||||
}
|
||||
if(!launchWithError) {
|
||||
mActivity.mCrashView.setLastCrash("");
|
||||
|
||||
try {
|
||||
/*
|
||||
List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||
jvmArgs.add("-Xms128M");
|
||||
jvmArgs.add("-Xmx1G");
|
||||
*/
|
||||
Intent mainIntent = new Intent(mActivity, MainActivity.class /* MainActivity.class */);
|
||||
// mainIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
mainIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
if (LauncherPreferences.PREF_FREEFORM) {
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||
|
||||
ActivityOptions options = (ActivityOptions) ActivityOptions.class.getMethod("makeBasic").invoke(null);
|
||||
Rect freeformRect = new Rect(0, 0, dm.widthPixels / 2, dm.heightPixels / 2);
|
||||
options.getClass().getDeclaredMethod("setLaunchBounds", Rect.class).invoke(options, freeformRect);
|
||||
mActivity.startActivity(mainIntent, options.toBundle());
|
||||
} else {
|
||||
mActivity.startActivity(mainIntent);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Tools.showError(mActivity, e);
|
||||
}
|
||||
|
||||
/*
|
||||
FloatingIntent maini = new FloatingIntent(PojavLauncherActivity.this, MainActivity.class);
|
||||
maini.startFloatingActivity();
|
||||
*/
|
||||
}
|
||||
|
||||
mActivity.mTask = null;
|
||||
}
|
||||
|
||||
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
||||
|
||||
public JAssets downloadIndex(String versionName, File output) throws Throwable {
|
||||
if (!output.exists()) {
|
||||
output.getParentFile().mkdirs();
|
||||
DownloadUtils.downloadFile(verInfo.assetIndex != null ? verInfo.assetIndex.url : "http://s3.amazonaws.com/Minecraft.Download/indexes/" + versionName + ".json", output);
|
||||
}
|
||||
|
||||
return Tools.GLOBAL_GSON.fromJson(Tools.read(output.getAbsolutePath()), JAssets.class);
|
||||
}
|
||||
|
||||
public void downloadAsset(JAssetInfo asset, File objectsDir) throws IOException, Throwable {
|
||||
String assetPath = asset.hash.substring(0, 2) + "/" + asset.hash;
|
||||
File outFile = new File(objectsDir, assetPath);
|
||||
if (!outFile.exists()) {
|
||||
DownloadUtils.downloadFile(MINECRAFT_RES + assetPath, outFile);
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadAssets(JAssets assets, String assetsVersion, File outputDir) throws IOException, Throwable {
|
||||
File hasDownloadedFile = new File(outputDir, "downloaded/" + assetsVersion + ".downloaded");
|
||||
if (!hasDownloadedFile.exists()) {
|
||||
System.out.println("Assets begin time: " + System.currentTimeMillis());
|
||||
Map<String, JAssetInfo> assetsObjects = assets.objects;
|
||||
mActivity.mLaunchProgress.setMax(assetsObjects.size());
|
||||
zeroProgress();
|
||||
File objectsDir = new File(outputDir, "objects");
|
||||
int downloadedSs = 0;
|
||||
for (JAssetInfo asset : assetsObjects.values()) {
|
||||
if (!mActivity.mIsAssetsProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadAsset(asset, objectsDir);
|
||||
publishProgress("1", mActivity.getString(R.string.mcl_launch_downloading, assetsObjects.keySet().toArray(new String[0])[downloadedSs]));
|
||||
downloadedSs++;
|
||||
}
|
||||
hasDownloadedFile.getParentFile().mkdirs();
|
||||
hasDownloadedFile.createNewFile();
|
||||
System.out.println("Assets end time: " + System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private JMinecraftVersionList.Version findVersion(String version) {
|
||||
if (mActivity.mVersionList != null) {
|
||||
for (JMinecraftVersionList.Version valueVer: mActivity.mVersionList.versions) {
|
||||
if (valueVer.id.equals(version)) {
|
||||
return valueVer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom version, inherits from base.
|
||||
return Tools.getVersionInfo(version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import android.os.*;
|
||||
import androidx.appcompat.widget.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.widget.AdapterView.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
|
||||
public class RefreshVersionListTask extends AsyncTask<Void, Void, ArrayList<String>>
|
||||
{
|
||||
private BaseLauncherActivity mActivity;
|
||||
|
||||
public RefreshVersionListTask(BaseLauncherActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void[] p1)
|
||||
{
|
||||
try {
|
||||
mActivity.mVersionList = Tools.GLOBAL_GSON.fromJson(DownloadUtils.downloadString("https://launchermeta.mojang.com/mc/game/version_manifest.json"), JMinecraftVersionList.class);
|
||||
ArrayList<String> versionStringList = filter(mActivity.mVersionList.versions, new File(Tools.DIR_HOME_VERSION).listFiles());
|
||||
|
||||
return versionStringList;
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
|
||||
final PopupMenu popup = new PopupMenu(mActivity, mActivity.mVersionSelector);
|
||||
popup.getMenuInflater().inflate(R.menu.menu_versionopt, popup.getMenu());
|
||||
|
||||
if(result != null && result.size() > 0) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_spinner_item, result);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
|
||||
mActivity.mVersionSelector.setAdapter(adapter);
|
||||
mActivity.mVersionSelector.setSelection(selectAt(result.toArray(new String[0]), mActivity.mProfile.selectedVersion));
|
||||
} else {
|
||||
mActivity.mVersionSelector.setSelection(selectAt(mActivity.mAvailableVersions, mActivity.mProfile.selectedVersion));
|
||||
}
|
||||
mActivity.mVersionSelector.setOnItemSelectedListener(new OnItemSelectedListener(){
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> p1, View p2, int p3, long p4)
|
||||
{
|
||||
String version = p1.getItemAtPosition(p3).toString();
|
||||
mActivity.mProfile.selectedVersion = version;
|
||||
|
||||
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile);
|
||||
if (PojavProfile.isFileType(mActivity)) {
|
||||
try {
|
||||
PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile.save());
|
||||
} catch (IOException e) {
|
||||
Tools.showError(mActivity, e);
|
||||
}
|
||||
}
|
||||
|
||||
mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> p1)
|
||||
{
|
||||
// TODO: Implement this method
|
||||
}
|
||||
});
|
||||
mActivity.mVersionSelector.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> p1, View p2, int p3, long p4)
|
||||
{
|
||||
// Implement copy, remove, reinstall,...
|
||||
popup.show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg) + mActivity.mVersionSelector.getSelectedItem());
|
||||
}
|
||||
|
||||
private ArrayList<String> filter(JMinecraftVersionList.Version[] list1, File[] list2) {
|
||||
ArrayList<String> output = new ArrayList<String>();
|
||||
|
||||
for (JMinecraftVersionList.Version value1: list1) {
|
||||
if ((value1.type.equals("release") && LauncherPreferences.PREF_VERTYPE_RELEASE) ||
|
||||
(value1.type.equals("snapshot") && LauncherPreferences.PREF_VERTYPE_SNAPSHOT) ||
|
||||
(value1.type.equals("old_alpha") && LauncherPreferences.PREF_VERTYPE_OLDALPHA) ||
|
||||
(value1.type.equals("old_beta") && LauncherPreferences.PREF_VERTYPE_OLDBETA)) {
|
||||
output.add(value1.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (File value2: list2) {
|
||||
if (!output.contains(value2.getName())) {
|
||||
output.add(value2.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private int selectAt(String[] strArr, String select) {
|
||||
int count = 0;
|
||||
for(String str : strArr){
|
||||
if (str.equals(select)) {
|
||||
return count;
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
import org.apache.commons.io.*;
|
||||
|
||||
public class DownloadUtils {
|
||||
public static final String USER_AGENT = Tools.APP_NAME;
|
||||
public static final Charset utf8 = Charset.forName("UTF-8");
|
||||
|
||||
public static void download(String url, OutputStream os) throws IOException {
|
||||
download(new URL(url), os);
|
||||
}
|
||||
|
||||
public static void download(URL url, OutputStream os) throws IOException {
|
||||
InputStream is = null;
|
||||
try {
|
||||
// System.out.println("Connecting: " + url.toString());
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestProperty("User-Agent", USER_AGENT);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setDoInput(true);
|
||||
conn.connect();
|
||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IOException("Server returned HTTP " + conn.getResponseCode()
|
||||
+ ": " + conn.getResponseMessage());
|
||||
}
|
||||
is = conn.getInputStream();
|
||||
IOUtils.copy(is, os);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Unable to download from " + url.toString(), e);
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String downloadString(String url) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
download(url, bos);
|
||||
bos.close();
|
||||
return new String(bos.toByteArray(), utf8);
|
||||
}
|
||||
|
||||
public static void downloadFile(String url, File out) throws IOException {
|
||||
out.getParentFile().mkdirs();
|
||||
File tempOut = File.createTempFile(out.getName(), ".part", out.getParentFile());
|
||||
BufferedOutputStream bos = null;
|
||||
try {
|
||||
OutputStream bos2 = new BufferedOutputStream(new FileOutputStream(tempOut));
|
||||
try {
|
||||
download(url, bos2);
|
||||
tempOut.renameTo(out);
|
||||
if (bos2 != null) {
|
||||
bos2.close();
|
||||
}
|
||||
if (tempOut.exists()) {
|
||||
tempOut.delete();
|
||||
}
|
||||
} catch (IOException th2) {
|
||||
if (bos != null) {
|
||||
bos.close();
|
||||
}
|
||||
if (tempOut.exists()) {
|
||||
tempOut.delete();
|
||||
}
|
||||
throw th2;
|
||||
}
|
||||
} catch (IOException th3) {
|
||||
|
||||
if (bos != null) {
|
||||
bos.close();
|
||||
}
|
||||
if (tempOut.exists()) {
|
||||
tempOut.delete();
|
||||
}
|
||||
throw th3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import android.util.*;
|
||||
|
||||
import com.oracle.dalvik.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import libcore.io.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import net.kdt.pojavlaunch.prefs.*;
|
||||
|
||||
import org.lwjgl.glfw.*;
|
||||
|
||||
public class JREUtils
|
||||
{
|
||||
private JREUtils() {}
|
||||
|
||||
public static String LD_LIBRARY_PATH;
|
||||
private static String nativeLibDir;
|
||||
|
||||
public static String findInLdLibPath(String libName) {
|
||||
for (String libPath : Libcore.os.getenv("LD_LIBRARY_PATH").split(":")) {
|
||||
File f = new File(libPath, libName);
|
||||
if (f.exists() && f.isFile()) {
|
||||
return f.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
return libName;
|
||||
}
|
||||
|
||||
public static void initJavaRuntime() {
|
||||
dlopen(findInLdLibPath("libjli.dylib"));
|
||||
dlopen(findInLdLibPath("libjvm.dylib"));
|
||||
dlopen(findInLdLibPath("libverify.dylib"));
|
||||
dlopen(findInLdLibPath("libjava.dylib"));
|
||||
// dlopen(findInLdLibPath("libjsig.so"));
|
||||
dlopen(findInLdLibPath("libnet.dylib"));
|
||||
dlopen(findInLdLibPath("libnio.dylib"));
|
||||
dlopen(findInLdLibPath("libawt.dylib"));
|
||||
dlopen(findInLdLibPath("libawt_headless.dylib"));
|
||||
dlopen(findInLdLibPath("libfreetype.dylib"));
|
||||
dlopen(findInLdLibPath("libfontmanager.dylib"));
|
||||
|
||||
dlopen(nativeLibDir + "/libopenal.dylib");
|
||||
/*
|
||||
if (LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME.equals("libgl04es.so")) {
|
||||
LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME = nativeLibDir + "/libgl04es.so";
|
||||
}
|
||||
if (!dlopen(LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME) && !dlopen(findInLdLibPath(LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME))) {
|
||||
System.err.println("Failed to load custom OpenGL library " + LauncherPreferences.PREF_CUSTOM_OPENGL_LIBNAME + ". Fallbacking to GL4ES.");
|
||||
dlopen(nativeLibDir + "/libgl04es.so");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public static Map<String, String> readJREReleaseProperties() throws IOException {
|
||||
Map<String, String> jreReleaseMap = new ArrayMap<>();
|
||||
BufferedReader jreReleaseReader = new BufferedReader(new FileReader(Tools.DIR_HOME_JRE + "/release"));
|
||||
String currLine;
|
||||
while ((currLine = jreReleaseReader.readLine()) != null) {
|
||||
if (!currLine.isEmpty() || currLine.contains("=")) {
|
||||
String[] keyValue = currLine.split("=");
|
||||
jreReleaseMap.put(keyValue[0], keyValue[1].replace("\"", ""));
|
||||
}
|
||||
}
|
||||
jreReleaseReader.close();
|
||||
return jreReleaseMap;
|
||||
}
|
||||
|
||||
private static boolean checkAccessTokenLeak = true;
|
||||
public static void redirectAndPrintJRELog(final String accessToken) {
|
||||
}
|
||||
|
||||
public static void relocateLibPath() throws Exception {
|
||||
StringBuilder ldLibraryPath = new StringBuilder();
|
||||
ldLibraryPath.append(
|
||||
// To make libjli.so ignore re-execute
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/server:" +
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/jli:" +
|
||||
Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + ":" +
|
||||
Tools.DIR_DATA + "/Frameworks:"+
|
||||
System.getenv("DYLD_LIBRARY_PATH")
|
||||
);
|
||||
|
||||
LD_LIBRARY_PATH = ldLibraryPath.toString();
|
||||
}
|
||||
|
||||
public static void setJavaEnvironment() throws Throwable {
|
||||
Map<String, String> envMap = new ArrayMap<>();
|
||||
envMap.put("JAVA_HOME", Tools.DIR_HOME_JRE);
|
||||
envMap.put("HOME", Tools.DIR_GAME_NEW);
|
||||
envMap.put("TMPDIR", System.getProperty("java.io.tmpdir"));
|
||||
envMap.put("LIBGL_MIPMAP", "3");
|
||||
|
||||
// Fix white color on banner and sheep, since GL4ES 1.1.5
|
||||
envMap.put("LIBGL_NORMALIZE", "1");
|
||||
|
||||
envMap.put("MESA_GLSL_CACHE_DIR", System.getProperty("java.io.tmpdir"));
|
||||
envMap.put("DYLD_LIBRARY_PATH", LD_LIBRARY_PATH);
|
||||
// envMap.put("PATH", Tools.DIR_HOME_JRE + "/bin:" + Os.getenv("PATH"));
|
||||
|
||||
envMap.put("AWTSTUB_WIDTH", Integer.toString(CallbackBridge.windowWidth));
|
||||
envMap.put("AWTSTUB_HEIGHT", Integer.toString(CallbackBridge.windowHeight));
|
||||
|
||||
File customEnvFile = new File(Tools.DIR_GAME_NEW, "custom_env.txt");
|
||||
if (customEnvFile.exists() && customEnvFile.isFile()) {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(customEnvFile));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// Not use split() as only split first one
|
||||
int index = line.indexOf("=");
|
||||
envMap.put(line.substring(0, index), line.substring(index + 1));
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> env : envMap.entrySet()) {
|
||||
Libcore.os.setenv(env.getKey(), env.getValue(), true);
|
||||
}
|
||||
|
||||
setLdLibraryPath(LD_LIBRARY_PATH);
|
||||
|
||||
// return ldLibraryPath;
|
||||
}
|
||||
|
||||
public static int launchJavaVM(final List<String> args) throws Throwable {
|
||||
JREUtils.relocateLibPath();
|
||||
// ctx.appendlnToLog("LD_LIBRARY_PATH = " + JREUtils.LD_LIBRARY_PATH);
|
||||
|
||||
List<String> javaArgList = new ArrayList<String>();
|
||||
javaArgList.add(Tools.DIR_HOME_JRE + "/bin/java");
|
||||
Tools.getJavaArgs(javaArgList);
|
||||
|
||||
javaArgList.addAll(args);
|
||||
|
||||
// For debugging only!
|
||||
/*
|
||||
StringBuilder sbJavaArgs = new StringBuilder();
|
||||
for (String s : javaArgList) {
|
||||
sbJavaArgs.append(s + " ");
|
||||
}
|
||||
ctx.appendlnToLog("Executing JVM: \"" + sbJavaArgs.toString() + "\"");
|
||||
*/
|
||||
|
||||
redirectLogcat(Tools.DIR_GAME_HOME + "/latestlog.txt");
|
||||
|
||||
setJavaEnvironment();
|
||||
initJavaRuntime();
|
||||
chdir(Tools.DIR_GAME_NEW);
|
||||
|
||||
final int exitCode = VMLauncher.launchJVM(true /* JLI_Launch */, javaArgList);
|
||||
System.out.println("Java Exit code: " + exitCode);
|
||||
if (exitCode != 0) {
|
||||
Tools.showError(new Exception("Java exited with code " + exitCode));
|
||||
/*
|
||||
ctx.runOnUiThread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
|
||||
dialog.setMessage(ctx.getString(R.string.mcn_exit_title, exitCode));
|
||||
dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface p1, int p2){
|
||||
BaseMainActivity.fullyExit();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public static native int chdir(String path);
|
||||
public static native boolean dlopen(String libPath);
|
||||
public static native void redirectLogcat(String path);
|
||||
public static native void setLdLibraryPath(String ldLibraryPath);
|
||||
public static native void saveGLContext();
|
||||
|
||||
// Obtain AWT screen pixels to render on Android SurfaceView
|
||||
public static native int[] renderAWTScreenFrame(/* Object canvas, int width, int height */);
|
||||
|
||||
static {
|
||||
System.loadLibrary("pojavexec");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class JSONUtils {
|
||||
public static String[] insertJSONValueList(String[] args, Map<String, String> keyValueMap) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = insertSingleJSONValue(args[i], keyValueMap);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public static String insertSingleJSONValue(String value, Map<String, String> keyValueMap) {
|
||||
String valueInserted = value;
|
||||
for (Map.Entry<String, String> keyValue : keyValueMap.entrySet()) {
|
||||
valueInserted = valueInserted.replace("${" + keyValue.getKey() + "}", keyValue.getValue());
|
||||
}
|
||||
return valueInserted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class MCOptionUtils
|
||||
{
|
||||
private static List<String> mLineList;
|
||||
|
||||
public static void load() {
|
||||
if (mLineList == null) {
|
||||
mLineList = new ArrayList<String>();
|
||||
} else {
|
||||
mLineList.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt"));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
mLineList.add(line);
|
||||
}
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not load options.txt");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void set(String key, String value) {
|
||||
for (int i = 0; i < mLineList.size(); i++) {
|
||||
String line = mLineList.get(i);
|
||||
if (line.startsWith(key + ":")) {
|
||||
mLineList.set(i, key + ":" + value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mLineList.add(key + ":" + value);
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < mLineList.size(); i++) {
|
||||
result.append(mLineList.get(i));
|
||||
if (i + 1 < mLineList.size()) {
|
||||
result.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Tools.write(Tools.DIR_GAME_NEW + "/options.txt", result.toString());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not save options.txt");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
public class DependentLibrary {
|
||||
public String name;
|
||||
public LibraryDownloads downloads;
|
||||
public String url;
|
||||
|
||||
public static class LibraryDownloads
|
||||
{
|
||||
public MinecraftLibraryArtifact artifact;
|
||||
public LibraryDownloads(MinecraftLibraryArtifact artifact) {
|
||||
this.artifact = artifact;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class ForgeInstallProfile {
|
||||
// ----- < 1.12.2 Forge Install Profile -----
|
||||
public ForgeInstallProperties install;
|
||||
public JMinecraftVersionList.Version versionInfo;
|
||||
|
||||
public static class ForgeInstallProperties {
|
||||
public String profileName;
|
||||
public String target;
|
||||
public String path;
|
||||
public String version;
|
||||
public String filePath; // universal file .jar
|
||||
public String minecraft; // target Minecraft version
|
||||
}
|
||||
|
||||
// ----- 1.12.2+ Forge Install Profile -----
|
||||
public String version;
|
||||
public String json;
|
||||
public String path;
|
||||
public String minecraft; // target Minecraft version
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.kdt.pojavlaunch.value;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.io.*;
|
||||
import com.google.gson.*;
|
||||
|
||||
public class MinecraftAccount
|
||||
{
|
||||
public String accessToken = "0"; // access token
|
||||
public String clientToken = "0"; // clientID: refresh and invalidate
|
||||
public String profileId = "0"; // authenticate UUID
|
||||
public String username = "Steve";
|
||||
public String selectedVersion = "1.7.10";
|
||||
public boolean isMicrosoft = false;
|
||||
public String msaRefreshToken = "0";
|
||||
|
||||
public String save(String outPath) throws IOException {
|
||||
Tools.write(outPath, Tools.GLOBAL_GSON.toJson(this));
|
||||
return outPath;
|
||||
}
|
||||
|
||||
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) throws IOException, JsonSyntaxException {
|
||||
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 = "0";
|
||||
} if (acc.username == null) {
|
||||
acc.username = "0";
|
||||
} if (acc.selectedVersion == null) {
|
||||
acc.selectedVersion = "1.7.10";
|
||||
} if (acc.msaRefreshToken == null) {
|
||||
acc.msaRefreshToken = "0";
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
public static void clearTempAccount() {
|
||||
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
|
||||
tempAccFile.delete();
|
||||
}
|
||||
|
||||
public static void saveTempAccount(MinecraftAccount acc) throws IOException {
|
||||
File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json");
|
||||
tempAccFile.delete();
|
||||
acc.save(tempAccFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
public class MinecraftClientInfo
|
||||
{
|
||||
public String sha1;
|
||||
public int size;
|
||||
public String url;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.kdt.pojavlaunch.value;
|
||||
|
||||
public class MinecraftLibraryArtifact extends MinecraftClientInfo
|
||||
{
|
||||
public String path;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
import com.google.gson.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
import java.io.*;
|
||||
|
||||
public class LauncherProfiles
|
||||
{
|
||||
public static MinecraftLauncherProfiles mainProfileJson;
|
||||
public static 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);
|
||||
} else {
|
||||
mainProfileJson = new MinecraftLauncherProfiles();
|
||||
}
|
||||
} else {
|
||||
Tools.write(launcherProfilesFile.getAbsolutePath(), mainProfileJson.toJson());
|
||||
}
|
||||
|
||||
// insertMissing();
|
||||
return mainProfileJson;
|
||||
} catch (Throwable th) {
|
||||
throw new RuntimeException(th);
|
||||
}
|
||||
}
|
||||
/*
|
||||
public static String insert;
|
||||
|
||||
private static void insertMissing() {
|
||||
if (mainProfileJson.authenticationDatabase == null) {
|
||||
MinecraftAuthenticationDatabase mad = new MinecraftAuthenticationDatabase();
|
||||
mainProfileJson.authenticationDatabase = mad;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftAuthenticationDatabase
|
||||
{
|
||||
public String accessToken;
|
||||
public String displayName;
|
||||
public String username;
|
||||
public String uuid;
|
||||
// public MinecraftProfile[] profiles;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class MinecraftLauncherProfiles
|
||||
{
|
||||
public Map<String, MinecraftProfile> profiles;
|
||||
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);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
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;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
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 MinecraftResolution[] resolution;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftResolution
|
||||
{
|
||||
public int width;
|
||||
public int height;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package net.kdt.pojavlaunch.value.launcherprofiles;
|
||||
|
||||
public class MinecraftSelectedUser
|
||||
{
|
||||
public String account;
|
||||
public String profile;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.lwjgl.glfw;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.kdt.pojavlaunch.*;
|
||||
|
||||
public class CallbackBridge {
|
||||
public static final int ANDROID_TYPE_GRAB_STATE = 0;
|
||||
|
||||
public static final int CLIPBOARD_COPY = 2000;
|
||||
public static final int CLIPBOARD_PASTE = 2001;
|
||||
|
||||
public static volatile int windowWidth, windowHeight;
|
||||
public static int mouseX, mouseY;
|
||||
public static boolean mouseLeft;
|
||||
public static StringBuilder DEBUG_STRING = new StringBuilder();
|
||||
|
||||
// volatile private static boolean isGrabbing = false;
|
||||
|
||||
public static void putMouseEventWithCoords(int button, int x, int y /* , int dz, long nanos */) {
|
||||
putMouseEventWithCoords(button, 1, x, y);
|
||||
putMouseEventWithCoords(button, 0, x, y);
|
||||
}
|
||||
|
||||
public static void putMouseEventWithCoords(int button, int state, int x, int y /* , int dz, long nanos */) {
|
||||
sendCursorPos(x, y);
|
||||
sendMouseKeycode(button, CallbackBridge.getCurrentMods(), state == 1);
|
||||
}
|
||||
|
||||
private static boolean threadAttached;
|
||||
public static void sendCursorPos(int x, int y) {
|
||||
if (!threadAttached) {
|
||||
threadAttached = CallbackBridge.nativeAttachThreadToOther(true, true /* TODO BaseMainActivity.isInputStackCall */);
|
||||
}
|
||||
|
||||
DEBUG_STRING.append("CursorPos=" + x + ", " + y + "\n");
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
nativeSendCursorPos(x, y);
|
||||
}
|
||||
|
||||
public static void sendPrepareGrabInitialPos() {
|
||||
DEBUG_STRING.append("Prepare set grab initial posititon");
|
||||
sendMouseKeycode(-1, CallbackBridge.getCurrentMods(), false);
|
||||
}
|
||||
|
||||
public static void sendKeycode(int keycode, char keychar, int scancode, int modifiers, boolean isDown) {
|
||||
DEBUG_STRING.append("KeyCode=" + keycode + ", Char=" + keychar);
|
||||
// TODO CHECK: This may cause input issue, not receive input!
|
||||
/*
|
||||
if (!nativeSendCharMods((int) keychar, modifiers) || !nativeSendChar(keychar)) {
|
||||
nativeSendKey(keycode, 0, isDown ? 1 : 0, modifiers);
|
||||
}
|
||||
*/
|
||||
|
||||
nativeSendKeycode(keycode, keychar, scancode, isDown ? 1 : 0, modifiers);
|
||||
|
||||
// sendData(JRE_TYPE_KEYCODE_CONTROL, keycode, Character.toString(keychar), Boolean.toString(isDown), modifiers);
|
||||
}
|
||||
|
||||
public static void sendMouseKeycode(int button, int modifiers, boolean isDown) {
|
||||
DEBUG_STRING.append("MouseKey=" + button + ", down=" + isDown + "\n");
|
||||
// if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n");
|
||||
nativeSendMouseButton(button, isDown ? 1 : 0, modifiers);
|
||||
}
|
||||
|
||||
public static void sendMouseKeycode(int keycode) {
|
||||
sendMouseKeycode(keycode, CallbackBridge.getCurrentMods(), true);
|
||||
sendMouseKeycode(keycode, CallbackBridge.getCurrentMods(), false);
|
||||
}
|
||||
|
||||
public static void sendScroll(double xoffset, double yoffset) {
|
||||
DEBUG_STRING.append("ScrollX=" + xoffset + ",ScrollY=" + yoffset);
|
||||
nativeSendScroll(xoffset, yoffset);
|
||||
}
|
||||
|
||||
public static void sendUpdateWindowSize(int w, int h) {
|
||||
nativeSendScreenSize(w, h);
|
||||
}
|
||||
|
||||
public static boolean isGrabbing() {
|
||||
// return isGrabbing;
|
||||
return nativeIsGrabbing();
|
||||
}
|
||||
|
||||
// Called from JRE side
|
||||
public static String accessAndroidClipboard(int type, String copy) {
|
||||
switch (type) {
|
||||
case CLIPBOARD_COPY:
|
||||
// BaseMainActivity.GLOBAL_CLIPBOARD.setPrimaryClip(ClipData.newPlainText("Copy", copy));
|
||||
return null;
|
||||
|
||||
case CLIPBOARD_PASTE:
|
||||
/*
|
||||
if (BaseMainActivity.GLOBAL_CLIPBOARD.hasPrimaryClip() && BaseMainActivity.GLOBAL_CLIPBOARD.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||
return BaseMainActivity.GLOBAL_CLIPBOARD.getPrimaryClip().getItemAt(0).getText().toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
*/
|
||||
return "";
|
||||
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
public static void receiveCallback(int type, String data) {
|
||||
switch (type) {
|
||||
case ANDROID_TYPE_GRAB_STATE:
|
||||
// isGrabbing = Boolean.parseBoolean(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
private static String currData;
|
||||
public static void sendData(int type, Object... dataArr) {
|
||||
currData = "";
|
||||
for (int i = 0; i < dataArr.length; i++) {
|
||||
if (dataArr[i] instanceof Integer) {
|
||||
currData += Integer.toString((int) dataArr[i]);
|
||||
} else if (dataArr[i] instanceof String) {
|
||||
currData += (String) dataArr[i];
|
||||
} else {
|
||||
currData += dataArr[i].toString();
|
||||
}
|
||||
currData += (i + 1 < dataArr.length ? ":" : "");
|
||||
}
|
||||
nativeSendData(true, type, currData);
|
||||
}
|
||||
private static native void nativeSendData(boolean isAndroid, int type, String data);
|
||||
*/
|
||||
|
||||
public static boolean holdingAlt, holdingCapslock, holdingCtrl,
|
||||
holdingNumlock, holdingShift;
|
||||
public static int getCurrentMods() {
|
||||
int currMods = 0;
|
||||
if (holdingAlt) {
|
||||
currMods &= LWJGLGLFWKeycode.GLFW_MOD_ALT;
|
||||
} if (holdingCapslock) {
|
||||
currMods &= LWJGLGLFWKeycode.GLFW_MOD_CAPS_LOCK;
|
||||
} if (holdingCtrl) {
|
||||
currMods &= LWJGLGLFWKeycode.GLFW_MOD_CONTROL;
|
||||
} if (holdingNumlock) {
|
||||
currMods &= LWJGLGLFWKeycode.GLFW_MOD_NUM_LOCK;
|
||||
} if (holdingShift) {
|
||||
currMods &= LWJGLGLFWKeycode.GLFW_MOD_SHIFT;
|
||||
}
|
||||
return currMods;
|
||||
}
|
||||
|
||||
public static native boolean nativeAttachThreadToOther(boolean isAndroid, boolean isUsePushPoll);
|
||||
/*
|
||||
private static native boolean nativeSendChar(char codepoint);
|
||||
// GLFW: GLFWCharModsCallback deprecated, but is Minecraft still use?
|
||||
private static native boolean nativeSendCharMods(char codepoint, int mods);
|
||||
*/
|
||||
// private static native void nativeSendCursorEnter(int entered);
|
||||
private static native void nativeSendCursorPos(int x, int y);
|
||||
private static native void nativeSendKeycode(int keycode, char keychar, int scancode, int action, int mods);
|
||||
private static native void nativeSendMouseButton(int button, int action, int mods);
|
||||
private static native void nativeSendScroll(double xoffset, double yoffset);
|
||||
private static native void nativeSendScreenSize(int width, int height);
|
||||
|
||||
public static native boolean nativeIsGrabbing();
|
||||
|
||||
static {
|
||||
System.loadLibrary("pojavexec");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user