[Change project structure to remove RoboVM depends)

This commit is contained in:
khanhduytran0 committed 2020-12-31 15:03:15 +07:00
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();
}
}
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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;
}
@@ -0,0 +1,8 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
public class ErrorResponse {
public String cause;
public String error;
public String errorMessage;
}
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
@@ -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;
}
}
@@ -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
);
}
}
@@ -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;
}
@@ -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;
}
}
*/
}
@@ -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;
}
@@ -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);
}
}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,7 @@
package net.kdt.pojavlaunch.value.launcherprofiles;
public class MinecraftResolution
{
public int width;
public int height;
}
@@ -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");
}
}