diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 80f7fea3..c09612aa 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -25,11 +25,33 @@ jobs: - name: Checkout repository submodules run: git submodule update --init --recursive + - name: Get JRE8 + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + path: depends + workflow_conclusion: success + repo: PojavLauncherTeam/android-openjdk-build-multiarch + branch: jre8-ios-jitjailed + name: jre8-ios-aarch64 + + - name: Get JRE17 + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + path: depends + workflow_conclusion: success + repo: PojavLauncherTeam/android-openjdk-build-multiarch + branch: buildjre17 + name: jre17-ios-aarch64 + - name: Build packages env: password: ${{ secrets.ELLIE_MINI_SECRET }} run: | - export NOSTDIN=1 SUDOPASS=$password + export NOSTDIN=1 SUDOPASS=$password RUNNER=1 export JAVA_HOME=$(/usr/libexec/java_home -v 1.8) gmake -j$(sysctl -n hw.ncpu) ipa diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff9a7226..e59796b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,12 +25,34 @@ jobs: - name: Checkout repository submodules run: git submodule update --init --recursive + - name: Get JRE8 + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + path: depends + workflow_conclusion: success + repo: PojavLauncherTeam/android-openjdk-build-multiarch + branch: jre8-ios-jitjailed + name: jre8-ios-aarch64 + + - name: Get JRE17 + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build.yml + path: depends + workflow_conclusion: success + repo: PojavLauncherTeam/android-openjdk-build-multiarch + branch: buildjre17 + name: jre17-ios-aarch64 + - name: Build packages env: password: ${{ secrets.ELLIE_MINI_SECRET }} run: | export RELEASE=1 - export NOSTDIN=1 SUDOPASS=$password + export NOSTDIN=1 SUDOPASS=$password RUNNER=1 export JAVA_HOME=$(/usr/libexec/java_home -v 1.8) gmake -j$(sysctl -n hw.ncpu) ipa diff --git a/JavaApp/libs_caciocavallo17/cacio-shared-1.18-SNAPSHOT.jar b/JavaApp/libs_caciocavallo17/cacio-shared-1.18-SNAPSHOT.jar new file mode 100644 index 00000000..0d4859d2 Binary files /dev/null and b/JavaApp/libs_caciocavallo17/cacio-shared-1.18-SNAPSHOT.jar differ diff --git a/JavaApp/libs_caciocavallo17/cacio-tta-1.18-SNAPSHOT.jar b/JavaApp/libs_caciocavallo17/cacio-tta-1.18-SNAPSHOT.jar new file mode 100644 index 00000000..aba8d955 Binary files /dev/null and b/JavaApp/libs_caciocavallo17/cacio-tta-1.18-SNAPSHOT.jar differ diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/PojavLauncher.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/PojavLauncher.java index b3457649..1e7fd4c5 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/PojavLauncher.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/PojavLauncher.java @@ -43,6 +43,10 @@ public class PojavLauncher { }); System.setProperty("cacio.managed.screensize", args[2]); + try { + // Try to initialize Caciocavallo17 + Class.forName("com.github.caciocavallosilano.cacio.ctc.CTCPreloadClassLoader"); + } catch (ClassNotFoundException e) {} if (args[0].equals(".LaunchJAR")) { UIKit.callback_JavaGUIViewController_launchJarFile(args[1]); diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/UIKit.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/UIKit.java index 7997e812..e0426508 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/UIKit.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/UIKit.java @@ -3,7 +3,6 @@ package net.kdt.pojavlaunch.uikit; import java.io.*; import java.lang.reflect.*; import java.util.jar.*; -import net.java.openjdk.cacio.ctc.CTCScreen; import net.kdt.pojavlaunch.utils.MCOptionUtils; import net.kdt.pojavlaunch.*; import org.lwjgl.glfw.*; @@ -32,27 +31,39 @@ public class UIKit { public static void callback_JavaGUIViewController_launchJarFile(final String filepath) throws Throwable { // Thread for refreshing the AWT buffer new Thread(() -> { + Method getCurrentScreenRGB; try { - long lastTime = System.currentTimeMillis(); - while (true) { - int[] pixelsArray = null; - try{ - pixelsArray = CTCScreen.getCurrentScreenRGB(); - } catch (NullPointerException e) { - Thread.sleep(500); - } - if (pixelsArray != null) { - //System.out.println(java.util.Arrays.toString(pixelsArray)); - refreshAWTBuffer(pixelsArray); - } - long currentTime = System.currentTimeMillis(); - if (currentTime - lastTime < 16) { - Thread.sleep(16 - (currentTime - lastTime)); - } - lastTime = currentTime; + try { + getCurrentScreenRGB = Class.forName("net.java.openjdk.cacio.ctc.CTCScreen").getMethod("getCurrentScreenRGB"); + } catch (ClassNotFoundException e) { + getCurrentScreenRGB = Class.forName("com.github.caciocavallosilano.cacio.ctc.CTCScreen").getMethod("getCurrentScreenRGB"); } - } catch (InterruptedException e) { - e.printStackTrace(); + } catch (Throwable th) { + System.err.println("Failed to find class CTCScreen"); + th.printStackTrace(); + System.exit(1); + return; + } + + long lastTime = System.currentTimeMillis(); + while (true) { + int[] pixelsArray = null; + try{ + pixelsArray = (int[])getCurrentScreenRGB.invoke(null); + } catch (Throwable e) {} + if (pixelsArray != null) { + //System.out.println(java.util.Arrays.toString(pixelsArray)); + refreshAWTBuffer(pixelsArray); + } + long currentTime = System.currentTimeMillis(); + if (currentTime - lastTime < 16) { + try { + Thread.sleep(16 - (currentTime - lastTime)); + } catch (InterruptedException e) { + break; + } + } + lastTime = currentTime; } }, "AWTFBRefreshThread").start(); diff --git a/JavaApp/src/main/java/org/lwjgl/MemoryUtil.java b/JavaApp/src/main/java/org/lwjgl/MemoryUtil.java new file mode 100644 index 00000000..d41aa767 --- /dev/null +++ b/JavaApp/src/main/java/org/lwjgl/MemoryUtil.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2002-2011 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl; + +import java.lang.reflect.Field; +import java.nio.*; +import java.nio.charset.*; + +/** + * [INTERNAL USE ONLY] + *
+ * This class provides utility methods for passing buffers to JNI API calls. + * + * @author Spasi + */ +public final class MemoryUtil { + + private static final Charset ascii; + private static final Charset utf8; + + static { + ascii = Charset.forName("ISO-8859-1"); + utf8 = Charset.forName("UTF-8"); + } + + private MemoryUtil() { + } + + /** + * Returns the memory address of the specified buffer. [INTERNAL USE ONLY] + * + * @param buffer the buffer + * + * @return the memory address + */ + public static long getAddress0(Buffer buffer) { return org.lwjgl.system.MemoryUtil.memAddress0(buffer); } + + public static long getAddress0Safe(Buffer buffer) { return buffer == null ? 0L : getAddress0(buffer); } + + public static long getAddress0(PointerBuffer buffer) { return getAddress0(buffer.getBuffer()); } + + public static long getAddress0Safe(PointerBuffer buffer) { return buffer == null ? 0L : getAddress0(buffer.getBuffer()); } + + // --- [ API utilities ] --- + + public static long getAddress(ByteBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(ByteBuffer buffer, int position) { return getAddress0(buffer) + position; } + + public static long getAddress(ShortBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(ShortBuffer buffer, int position) { return getAddress0(buffer) + (position << 1); } + + public static long getAddress(CharBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(CharBuffer buffer, int position) { return getAddress0(buffer) + (position << 1); } + + public static long getAddress(IntBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(IntBuffer buffer, int position) { return getAddress0(buffer) + (position << 2); } + + public static long getAddress(FloatBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(FloatBuffer buffer, int position) { return getAddress0(buffer) + (position << 2); } + + public static long getAddress(LongBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(LongBuffer buffer, int position) { return getAddress0(buffer) + (position << 3); } + + public static long getAddress(DoubleBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(DoubleBuffer buffer, int position) { return getAddress0(buffer) + (position << 3); } + + public static long getAddress(PointerBuffer buffer) { return getAddress(buffer, buffer.position()); } + + public static long getAddress(PointerBuffer buffer, int position) { return getAddress0(buffer) + (position * PointerBuffer.getPointerSize()); } + + // --- [ API utilities - Safe ] --- + + public static long getAddressSafe(ByteBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(ByteBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(ShortBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(ShortBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(CharBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(CharBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(IntBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(IntBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(FloatBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(FloatBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(LongBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(LongBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(DoubleBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(DoubleBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + public static long getAddressSafe(PointerBuffer buffer) { return buffer == null ? 0L : getAddress(buffer); } + + public static long getAddressSafe(PointerBuffer buffer, int position) { return buffer == null ? 0L : getAddress(buffer, position); } + + // --- [ String utilities ] --- + + /** + * Returns a ByteBuffer containing the specified text ASCII encoded and null-terminated. + * If text is null, null is returned. + * + * @param text the text to encode + * + * @return the encoded text or null + * + * @see String#getBytes() + */ + public static ByteBuffer encodeASCII(final CharSequence text) { + return encode(text, ascii); + } + + /** + * Returns a ByteBuffer containing the specified text UTF-8 encoded and null-terminated. + * If text is null, null is returned. + * + * @param text the text to encode + * + * @return the encoded text or null + * + * @see String#getBytes() + */ + public static ByteBuffer encodeUTF8(final CharSequence text) { + return encode(text, utf8); + } + + /** + * Returns a ByteBuffer containing the specified text UTF-16LE encoded and null-terminated. + * If text is null, null is returned. + * + * @param text the text to encode + * + * @return the encoded text + */ + public static ByteBuffer encodeUTF16(final CharSequence text) { + return org.lwjgl.system.MemoryUtil.memUTF16(text); + } + + /** + * Wraps the specified text in a null-terminated CharBuffer and encodes it using the specified Charset. + * + * @param text the text to encode + * @param charset the charset to use for encoding + * + * @return the encoded text + */ + private static ByteBuffer encode(final CharSequence text, final Charset charset) { + if ( text == null ) + return null; + + return encode(CharBuffer.wrap(new CharSequenceNT(text)), charset); + } + + /** + * A {@link CharsetEncoder#encode(java.nio.CharBuffer)} implementation that uses {@link BufferUtils#createByteBuffer(int)} + * instead of {@link ByteBuffer#allocate(int)}. + * + * @see CharsetEncoder#encode(java.nio.CharBuffer) + */ + private static ByteBuffer encode(final CharBuffer in, final Charset charset) { + final CharsetEncoder encoder = charset.newEncoder(); // encoders are not thread-safe, create a new one on every call + + int n = (int)(in.remaining() * encoder.averageBytesPerChar()); + ByteBuffer out = BufferUtils.createByteBuffer(n); + + if ( n == 0 && in.remaining() == 0 ) + return out; + + encoder.reset(); + while ( true ) { + CoderResult cr = in.hasRemaining() ? encoder.encode(in, out, true) : CoderResult.UNDERFLOW; + if ( cr.isUnderflow() ) + cr = encoder.flush(out); + + if ( cr.isUnderflow() ) + break; + + if ( cr.isOverflow() ) { + n = 2 * n + 1; // Ensure progress; n might be 0! + ByteBuffer o = BufferUtils.createByteBuffer(n); + out.flip(); + o.put(out); + out = o; + continue; + } + + try { + cr.throwException(); + } catch (CharacterCodingException e) { + throw new RuntimeException(e); + } + } + out.flip(); + return out; + } + + public static String decodeASCII(final ByteBuffer buffer) { + return decode(buffer, ascii); + } + + public static String decodeUTF8(final ByteBuffer buffer) { + return decode(buffer, utf8); + } + + public static String decodeUTF16(final ByteBuffer buffer) { + return org.lwjgl.system.MemoryUtil.memUTF16(buffer); + } + + private static String decode(final ByteBuffer buffer, final Charset charset) { + if ( buffer == null ) + return null; + + return decodeImpl(buffer, charset); + } + + private static String decodeImpl(final ByteBuffer in, final Charset charset) { + final CharsetDecoder decoder = charset.newDecoder(); // decoders are not thread-safe, create a new one on every call + + int n = (int)(in.remaining() * decoder.averageCharsPerByte()); + CharBuffer out = BufferUtils.createCharBuffer(n); + + if ( (n == 0) && (in.remaining() == 0) ) + return ""; + + decoder.reset(); + for (; ; ) { + CoderResult cr = in.hasRemaining() ? decoder.decode(in, out, true) : CoderResult.UNDERFLOW; + if ( cr.isUnderflow() ) + cr = decoder.flush(out); + + if ( cr.isUnderflow() ) + break; + if ( cr.isOverflow() ) { + n = 2 * n + 1; // Ensure progress; n might be 0! + CharBuffer o = BufferUtils.createCharBuffer(n); + out.flip(); + o.put(out); + out = o; + continue; + } + try { + cr.throwException(); + } catch (CharacterCodingException e) { + throw new RuntimeException(e); + } + } + out.flip(); + return out.toString(); + } + + /** A null-terminated CharSequence. */ + private static class CharSequenceNT implements CharSequence { + + final CharSequence source; + + CharSequenceNT(CharSequence source) { + this.source = source; + } + + public int length() { + return source.length() + 1; + + } + + public char charAt(final int index) { + return index == source.length() ? '\0' : source.charAt(index); + + } + + public CharSequence subSequence(final int start, final int end) { + return new CharSequenceNT(source.subSequence(start, Math.min(end, source.length()))); + } + + } + + interface Accessor { + + long getAddress(Buffer buffer); + + } + + private static Accessor loadAccessor(final String className) throws Exception { + return (Accessor)Class.forName(className).newInstance(); + } + + /** Default implementation. */ + private static class AccessorJNI implements Accessor { + + public long getAddress(final Buffer buffer) { + return BufferUtils.getBufferAddress(buffer); + } + + } + + /** Implementation using reflection on ByteBuffer. */ + private static class AccessorReflect implements Accessor { + + private final Field address; + + AccessorReflect() { + try { + address = getAddressField(); + } catch (NoSuchFieldException e) { + throw new UnsupportedOperationException(e); + } + address.setAccessible(true); + } + + public long getAddress(final Buffer buffer) { + try { + return address.getLong(buffer); + } catch (IllegalAccessException e) { + // cannot happen + return 0L; + } + } + + } + + static Field getAddressField() throws NoSuchFieldException { + return getDeclaredFieldRecursive(ByteBuffer.class, "address"); + } + + private static Field getDeclaredFieldRecursive(final Class> root, final String fieldName) throws NoSuchFieldException { + Class> type = root; + + do { + try { + return type.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + type = type.getSuperclass(); + } + } while ( type != null ); + + throw new NoSuchFieldException(fieldName + " does not exist in " + root.getSimpleName() + " or any of its superclasses."); + } + +} diff --git a/JavaApp/src/main/java/org/lwjgl/openal/AL.java b/JavaApp/src/main/java/org/lwjgl/openal/AL.java index 8c2a288f..8b25f2d0 100644 --- a/JavaApp/src/main/java/org/lwjgl/openal/AL.java +++ b/JavaApp/src/main/java/org/lwjgl/openal/AL.java @@ -4,7 +4,8 @@ */ package org.lwjgl.openal; -import org.lwjgl.*; +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; import org.lwjgl.system.*; import javax.annotation.*; diff --git a/JavaApp/src/main/java/org/lwjgl/opengl/ARBShaderObjects.java b/JavaApp/src/main/java/org/lwjgl/opengl/ARBShaderObjects.java index c5ee3318..634279db 100644 --- a/JavaApp/src/main/java/org/lwjgl/opengl/ARBShaderObjects.java +++ b/JavaApp/src/main/java/org/lwjgl/opengl/ARBShaderObjects.java @@ -9,8 +9,7 @@ import javax.annotation.*; import java.nio.*; -import org.lwjgl.*; - +import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; import static org.lwjgl.system.Checks.*; @@ -127,25 +126,49 @@ public class ARBShaderObjects { glUniform4fvARB(location, value); } - public static void glUniform1ARB(@NativeType("GLint") int location, @NativeType("GLfloat const *") IntBuffer value) { + public static void glUniform1ARB(@NativeType("GLint") int location, @NativeType("GLint const *") IntBuffer value) { glUniform1ivARB(location, value); } - public static void glUniform2ARB(@NativeType("GLint") int location, @NativeType("GLfloat const *") IntBuffer value) { + public static void glUniform2ARB(@NativeType("GLint") int location, @NativeType("GLint const *") IntBuffer value) { glUniform2ivARB(location, value); } - public static void glUniform3ARB(@NativeType("GLint") int location, @NativeType("GLfloat const *") IntBuffer value) { + public static void glUniform3ARB(@NativeType("GLint") int location, @NativeType("GLint const *") IntBuffer value) { glUniform3ivARB(location, value); } - public static void glUniform4ARB(@NativeType("GLint") int location, @NativeType("GLfloat const *") IntBuffer value) { + public static void glUniform4ARB(@NativeType("GLint") int location, @NativeType("GLint const *") IntBuffer value) { glUniform4ivARB(location, value); } + + public static void glUniformMatrix2ARB(@NativeType("GLint") int location, @NativeType("GLboolean") boolean transpose, @NativeType("GLfloat const *") FloatBuffer value) { + glUniformMatrix2fvARB(location, transpose, value); + } + + public static void glUniformMatrix3ARB(@NativeType("GLint") int location, @NativeType("GLboolean") boolean transpose, @NativeType("GLfloat const *") FloatBuffer value) { + glUniformMatrix3fvARB(location, transpose, value); + } + + public static void glUniformMatrix4ARB(@NativeType("GLint") int location, @NativeType("GLboolean") boolean transpose, @NativeType("GLfloat const *") FloatBuffer value) { + glUniformMatrix4fvARB(location, transpose, value); + } + + public static void glGetObjectParameterARB(@NativeType("GLhandleARB") int obj, @NativeType("GLenum") int pname, @NativeType("GLfloat *") FloatBuffer params) { + glGetObjectParameterfvARB(obj, pname, params); + } public static void glGetObjectParameterARB(@NativeType("GLhandleARB") int obj, @NativeType("GLenum") int pname, @NativeType("GLint *") IntBuffer params) { glGetObjectParameterivARB(obj, pname, params); } + + public static void glGetUniformARB(@NativeType("GLhandleARB") int programObj, @NativeType("GLint") int location, @NativeType("GLfloat *") FloatBuffer params) { + glGetUniformfvARB(programObj, location, params); + } + + public static void glGetUniformARB(@NativeType("GLhandleARB") int programObj, @NativeType("GLint") int location, @NativeType("GLint *") IntBuffer params) { + glGetUniformivARB(programObj, location, params); + } // -- End LWJGL2 part -- // --- [ glDeleteObjectARB ] --- diff --git a/JavaApp/src/main/java/org/lwjgl/opengl/GL11.java b/JavaApp/src/main/java/org/lwjgl/opengl/GL11.java index 32835e5e..687d98aa 100644 --- a/JavaApp/src/main/java/org/lwjgl/opengl/GL11.java +++ b/JavaApp/src/main/java/org/lwjgl/opengl/GL11.java @@ -9,7 +9,7 @@ import javax.annotation.*; import java.nio.*; -import org.lwjgl.*; +import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/JavaApp/src/main/java/org/lwjgl/opengl/GL15.java b/JavaApp/src/main/java/org/lwjgl/opengl/GL15.java index 373c620e..cac36f1f 100644 --- a/JavaApp/src/main/java/org/lwjgl/opengl/GL15.java +++ b/JavaApp/src/main/java/org/lwjgl/opengl/GL15.java @@ -9,7 +9,7 @@ import javax.annotation.*; import java.nio.*; -import org.lwjgl.*; +import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/JavaApp/src/main/java/org/lwjgl/opengl/GL20.java b/JavaApp/src/main/java/org/lwjgl/opengl/GL20.java index 92151666..b4e164e4 100644 --- a/JavaApp/src/main/java/org/lwjgl/opengl/GL20.java +++ b/JavaApp/src/main/java/org/lwjgl/opengl/GL20.java @@ -9,7 +9,8 @@ import javax.annotation.*; import java.nio.*; -import org.lwjgl.*; +import org.lwjgl.BufferUtils; +import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/Makefile b/Makefile index 49367bbc..d47363ae 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ IOS15PREF := private/preboot/procursus # Release vs Debug RELEASE ?= 0 +# Check if running on github runner +RUNNER ?= 0 + ifeq (1,$(RELEASE)) CMAKE_BUILD_TYPE := Release else @@ -149,7 +152,6 @@ DEPLOY = \ mv /var/tmp/PojavLauncher $(1)/Applications/PojavLauncher.app/PojavLauncher && \ mv /var/tmp/*.jar $(1)/Applications/PojavLauncher.app/libs/ && \ cd $(1)/Applications/PojavLauncher.app/Frameworks && \ - ln -sf libawt_xawt.dylib libawt_headless.dylib && \ chown -R 501:501 $(1)/Applications/PojavLauncher.app/*"; \ else \ sudo rm -rf $(1)/Applications/PojavLauncher.app/Frameworks/libOSMesaOverride.dylib.framework; \ @@ -158,7 +160,6 @@ DEPLOY = \ sudo mv $(WORKINGDIR)/PojavLauncher.app/PojavLauncher $(1)/Applications/PojavLauncher.app/PojavLauncher; \ sudo mv $(SOURCEDIR)/JavaApp/local_out/*.jar $(1)/Applications/PojavLauncher.app/libs/; \ cd $(1)/Applications/PojavLauncher.app/Frameworks; \ - sudo ln -sf libawt_xawt.dylib libawt_headless.dylib; \ sudo chown -R 501:501 $(1)/Applications/PojavLauncher.app/*; \ fi @@ -297,13 +298,13 @@ deb: native java extras fi $(call DIRCHECK,$(WORKINGDIR)/PojavLauncher.app/libs) $(call DIRCHECK,$(WORKINGDIR)/PojavLauncher.app/libs_caciocavallo) + $(call DIRCHECK,$(WORKINGDIR)/PojavLauncher.app/libs_caciocavallo17) @cp -R $(SOURCEDIR)/Natives/resources/* $(WORKINGDIR)/PojavLauncher.app/ || exit 1 @cp $(WORKINGDIR)/*.dylib $(WORKINGDIR)/PojavLauncher.app/Frameworks/ || exit 1 - @( cd $(WORKINGDIR)/PojavLauncher.app/Frameworks; ln -sf libawt_xawt.dylib libawt_headless.dylib ) || exit 1 @cp -R $(WORKINGDIR)/*.framework $(WORKINGDIR)/PojavLauncher.app/Frameworks/ || exit 1 @cp -R $(SOURCEDIR)/JavaApp/libs/* $(WORKINGDIR)/PojavLauncher.app/libs/ || exit 1 @cp $(SOURCEDIR)/JavaApp/local_out/*.jar $(WORKINGDIR)/PojavLauncher.app/libs/ || exit 1 - @cp -R $(SOURCEDIR)/JavaApp/libs_caciocavallo/* $(WORKINGDIR)/PojavLauncher.app/libs_caciocavallo/ || exit 1 + @cp -R $(SOURCEDIR)/JavaApp/libs_caciocavallo* $(WORKINGDIR)/PojavLauncher.app/ || exit 1 @cp -R $(SOURCEDIR)/Natives/*.lproj $(WORKINGDIR)/PojavLauncher.app/ || exit 1 $(call DIRCHECK,$(OUTPUTDIR)) @cp -R $(WORKINGDIR)/PojavLauncher.app $(OUTPUTDIR) @@ -325,18 +326,22 @@ ipa: dsym echo 'Building PojavLauncher $(VERSION) - IPA - Start' mkdir -p $(SOURCEDIR)/depends; \ cd $(SOURCEDIR)/depends; \ - if [ ! -d "java-8-openjdk" ]; then \ + if [ ! -f "java-8-openjdk/bin/java" ] && [ ! -f "$(ls ../jre8-*.tar.xz)" ]; then \ + if [ "$(RUNNER)" != "1" ]; then \ + wget 'https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/releases/download/jre8-40df388/jre8-arm64-20220811-release.tar.xz'; \ + fi; \ mkdir java-8-openjdk && cd java-8-openjdk; \ - wget 'https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/releases/download/jre8-40df388/jre8-arm64-20220811-release.tar.xz'; \ - tar xvf *.tar.xz; \ - rm *.tar.xz; \ + tar xvf ../jre8-*.tar.xz; \ + rm ../jre8-*.tar.xz; \ fi; \ - cd ..; \ - if [ ! -d "java-17-openjdk" ]; then \ + cd ..; \ + if [ ! -f "java-17-openjdk/bin/java" ] && [ ! -f "$(ls ../jre17-*.tar.xz)" ]; then \ + if [ "$(RUNNER)" != "1" ]; then \ + wget 'https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/releases/download/jre17-ca01427/jre17-arm64-20220817-release.tar.xz'; \ + fi; \ mkdir java-17-openjdk && cd java-17-openjdk; \ - wget 'https://github.com/PojavLauncherTeam/android-openjdk-build-multiarch/releases/download/jre17-ca01427/jre17-arm64-20220817-release.tar.xz'; \ - tar xvf *.tar.xz; \ - rm *.tar.xz; \ + tar xvf ../jre17-*.tar.xz; \ + rm ../jre17-*.tar.xz; \ fi; \ mkdir -p $(OUTPUTDIR); \ cd $(OUTPUTDIR); \ @@ -346,6 +351,9 @@ ipa: dsym cp -R $(POJAV_JRE8_DIR) $(OUTPUTDIR)/Payload/PojavLauncher.app/jvm/; \ cp -R $(POJAV_JRE17_DIR) $(OUTPUTDIR)/Payload/PojavLauncher.app/jvm/; \ rm -rf $(OUTPUTDIR)/Payload/PojavLauncher.app/jvm/*/{bin,include,jre,lib/{ct.sym,libjsig.dylib,src.zip,tools.jar}}; \ + cp $(OUTPUTDIR)/Payload/PojavLauncher.app/Frameworks/libawt_xawt.dylib $(OUTPUTDIR)/Payload/PojavLauncher.app/jvm/java-17-openjdk/lib/; \ + cp $(OUTPUTDIR)/Payload/PojavLauncher.app/Frameworks/libawt_xawt.dylib $(OUTPUTDIR)/Payload/PojavLauncher.app/jvm/java-8-openjdk/lib/; \ + rm $(OUTPUTDIR)/Payload/PojavLauncher.app/Frameworks/libawt_*.dylib; \ ldid -S$(SOURCEDIR)/entitlements_ipa.xml $(OUTPUTDIR)/Payload/PojavLauncher.app/PojavLauncher; \ rm -f $(OUTPUTDIR)/*.ipa; \ cd $(OUTPUTDIR); \ diff --git a/Natives/CMakeLists.txt b/Natives/CMakeLists.txt index a8937bcd..6cdc08bd 100644 --- a/Natives/CMakeLists.txt +++ b/Natives/CMakeLists.txt @@ -21,7 +21,7 @@ include_directories( ) # For cacio support library -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -rpath @executable_path/jvm/java-8-openjdk/lib -rpath /usr/lib/jvm/java-8-openjdk/lib") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -rpath @loader_path -rpath @executable_path/jvm/java-8-openjdk/lib -rpath /usr/lib/jvm/java-8-openjdk/lib") add_definitions(-DCONFIG_COMMIT="${CONFIG_COMMIT}") add_definitions(-DCONFIG_RELEASE=${CONFIG_RELEASE}) @@ -103,6 +103,9 @@ add_executable(PojavLauncher external/DBNumberedSlider/Classes/DBNumberedSlider.m external/TOInsetGroupedTableView/TOInsetGroupedTableView/TOInsetGroupedTableView.m + input/ControllerInput.m + input/KeyboardInput.m + AboutLauncherViewController.m AccountListViewController.m AppDelegate.m @@ -110,7 +113,6 @@ add_executable(PojavLauncher FileListViewController.m GameSurfaceView.m JavaGUIViewController.m - KeyboardInput.m LauncherFAQViewController.m LauncherMenuViewController.m LauncherPreferences.m diff --git a/Natives/JavaGUIViewController.m b/Natives/JavaGUIViewController.m index 4a1690cc..13f72b9a 100644 --- a/Natives/JavaGUIViewController.m +++ b/Natives/JavaGUIViewController.m @@ -111,8 +111,16 @@ const void * _CGDataProviderGetBytePointerCallbackAWT(void *info) { } - (void)surfaceOnClick:(UITapGestureRecognizer *)sender { + if (runtimeJNIEnvPtr == NULL) { + return; + } + if (method_ReceiveInput == NULL) { class_CTCAndroidInput = (*runtimeJNIEnvPtr)->FindClass(runtimeJNIEnvPtr, "net/java/openjdk/cacio/ctc/CTCAndroidInput"); + if ((*runtimeJNIEnvPtr)->ExceptionCheck(runtimeJNIEnvPtr) == JNI_TRUE) { + (*runtimeJNIEnvPtr)->ExceptionClear(runtimeJNIEnvPtr); + class_CTCAndroidInput = (*runtimeJNIEnvPtr)->FindClass(runtimeJNIEnvPtr, "com/github/caciocavallosilano/cacio/ctc/CTCAndroidInput"); + } assert(class_CTCAndroidInput != NULL); method_ReceiveInput = (*runtimeJNIEnvPtr)->GetStaticMethodID(runtimeJNIEnvPtr, class_CTCAndroidInput, "receiveData", "(IIIII)V"); assert(method_ReceiveInput != NULL); diff --git a/Natives/JavaLauncher.m b/Natives/JavaLauncher.m index 525d2d5b..fb455aab 100644 --- a/Natives/JavaLauncher.m +++ b/Natives/JavaLauncher.m @@ -41,7 +41,9 @@ void init_loadCustomEnv() { /* Define default env */ // I accidentally patched a bit wrong, the value should be a path containing libawt_xawt.dylib, but here is libawt.dylib path (no need to exist) - setenv("JAVA_AWT_PATH", [NSString stringWithFormat:@"%s/Frameworks/libawt.dylib", getenv("BUNDLE_PATH")].UTF8String, 1); + if (getenv("POJAV_DETECTEDJB")) { + setenv("JAVA_AWT_PATH", [NSString stringWithFormat:@"%s/Frameworks/libawt.dylib", getenv("BUNDLE_PATH")].UTF8String, 1); + } // Disable overloaded functions hack for Minecraft 1.17+ setenv("LIBGL_NOINTOVLHACK", "1", 1); @@ -104,12 +106,7 @@ NSString* environmentFailsafes(int minVersion) { NSString *jre16Path = [NSString stringWithFormat:@"%@/java-16-openjdk", jvmPath]; NSString *jre17Path = [NSString stringWithFormat:@"%@/java-17-openjdk", jvmPath]; - BOOL foundJava8 = [fm fileExistsAtPath:jre8Path]; - if (!foundJava8) { - regLog("[JavaLauncher] Java 8 wasn't found on your device. Install Java 8 for more compatibility and the mod installer."); - } - - if (foundJava8 && minVersion <= 8) { + if ([fm fileExistsAtPath:jre8Path] && minVersion <= 8) { javaHome = jre8Path; } else if ([fm fileExistsAtPath:jre16Path] && minVersion <= 16) { javaHome = jre16Path; @@ -134,7 +131,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi NSString *javaHome_pre = getPreference(@"java_home"); // We handle unset JAVA_HOME right there - if (username == nil || getSelectedJavaVersion() < minVersion) { + if (getSelectedJavaVersion() < minVersion) { NSLog(@"[JavaLauncher] Attempting to change to Java %d (actual might be higher)", minVersion); javaHome_pre = environmentFailsafes(minVersion); NSLog(@"[JavaLauncher] JAVA_HOME is now set to %@", javaHome_pre); @@ -171,30 +168,9 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi allocmem_pre = [getPreference(@"allocated_memory") stringValue]; allocmem = [allocmem_pre cStringUsingEncoding:NSUTF8StringEncoding]; - - char classpath[10000]; // "/Applications/PojavLauncher.app/libs/launcher.jar:/Applications/PojavLauncher.app/libs/ExagearApacheCommons.jar:/Applications/PojavLauncher.app/libs/gson-2.8.6.jar:/Applications/PojavLauncher.app/libs/jsr305.jar:/Applications/PojavLauncher.app/libs/lwjgl3-minecraft.jar"; - // Generate classpath - DIR *d; - struct dirent *dir; - d = opendir(java_libs_path); - int cplen = -2; - if (d) { - // cplen += sprintf(classpath + cplen, "-Xbootclasspath/a:"); - while ((dir = readdir(d)) != NULL) { - if (cplen < 0) { - ++cplen; - continue; - } - cplen += sprintf(classpath + cplen, "%s/%s:", java_libs_path, dir->d_name); - } - classpath[cplen-1] = '\0'; - closedir(d); - } - debugLog("[JavaLauncher] Classpath generated: %s", classpath); - // Check if JVM restarts char *frameworkPath, *javaPath, *jnaLibPath, *userDir, *userHome, *memMin, *memMax, *arcDNS; asprintf(&frameworkPath, "-Djava.library.path=%1$s/Frameworks:%1$s/Frameworks/libOSMesaOverride.dylib.framework:%1$s/Frameworks/libMoltenVK.dylib.framework", getenv("BUNDLE_PATH")); @@ -202,7 +178,6 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi asprintf(&jnaLibPath, "-Djna.boot.library.path=%s/Frameworks/libjnidispatch.dylib.framework", getenv("BUNDLE_PATH")); asprintf(&userDir, "-Duser.dir=%s", getenv("POJAV_GAME_DIR")); asprintf(&userHome, "-Duser.home=%s", getenv("POJAV_HOME")); - asprintf(&memMin, "-Xms%sM", allocmem); asprintf(&memMax, "-Xmx%sM", allocmem); asprintf(&arcDNS, "-javaagent:%s/arc_dns_injector.jar=23.95.137.176", java_libs_path); NSLog(@"[JavaLauncher] Java executable path: %s", javaPath); @@ -211,7 +186,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi margv[++margc] = javaPath; margv[++margc] = "-XstartOnFirstThread"; margv[++margc] = "-Djava.system.class.loader=net.kdt.pojavlaunch.PojavClassLoader"; - margv[++margc] = memMin; + margv[++margc] = "-Xms128M"; margv[++margc] = memMax; margv[++margc] = frameworkPath; margv[++margc] = jnaLibPath; @@ -228,6 +203,11 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi margv[++margc] = (char *) [NSString stringWithFormat:@"-Dpojav.selectedAccount=%@", selectedAccount].UTF8String; } + // Setup Caciocavallo + margv[++margc] = "-Djava.awt.headless=false"; + margv[++margc] = "-Dcacio.font.fontmanager=sun.awt.X11FontManager"; + margv[++margc] = "-Dcacio.font.fontscaler=sun.font.FreetypeFontScaler"; + // Load java char libjlipath8[2048]; // java 8 char libjlipath16[2048]; // java 16+ (?) @@ -247,38 +227,69 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi } // Setup Caciocavallo - margv[++margc] = "-Djava.awt.headless=false"; - margv[++margc] = "-Dcacio.font.fontmanager=sun.awt.X11FontManager"; - margv[++margc] = "-Dcacio.font.fontscaler=sun.font.FreetypeFontScaler"; margv[++margc] = "-Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel"; margv[++margc] = "-Dawt.toolkit=net.java.openjdk.cacio.ctc.CTCToolkit"; margv[++margc] = "-Djava.awt.graphicsenv=net.java.openjdk.cacio.ctc.CTCGraphicsEnvironment"; - - // Generate Caciocavallo bootclasspath - char cacio_libs_path[2048]; - char cacio_classpath[8192]; - sprintf((char*) cacio_libs_path, "%s/libs_caciocavallo", getenv("BUNDLE_PATH")); - cplen = sprintf(cacio_classpath, "-Xbootclasspath/p"); - d = opendir(cacio_libs_path); - int skip = 2; - if (d) { - while ((dir = readdir(d)) != NULL) { - if (skip > 0) { - --skip; - continue; - } - cplen += sprintf(cacio_classpath + cplen, ":%s/%s", cacio_libs_path, dir->d_name); - } - closedir(d); - } - margv[++margc] = cacio_classpath; } else { + // Required by Cosmetica to inject DNS margv[++margc] = "--add-opens=java.base/java.net=ALL-UNNAMED"; + // Setup Caciocavallo + margv[++margc] = "-Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel"; + margv[++margc] = "-Dawt.toolkit=com.github.caciocavallosilano.cacio.ctc.CTCToolkit"; + margv[++margc] = "-Djava.awt.graphicsenv=com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment"; + // Required by Caciocavallo17 to access internal API + margv[++margc] = "--add-exports=java.desktop/java.awt=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.java2d=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/java.awt.dnd.peer=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.awt=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.awt.event=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.awt.datatransfer=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.desktop/sun.font=ALL-UNNAMED"; + margv[++margc] = "--add-exports=java.base/sun.security.action=ALL-UNNAMED"; + margv[++margc] = "--add-opens=java.base/java.util=ALL-UNNAMED"; + margv[++margc] = "--add-opens=java.desktop/java.awt=ALL-UNNAMED"; + margv[++margc] = "--add-opens=java.desktop/sun.font=ALL-UNNAMED"; + margv[++margc] = "--add-opens=java.desktop/sun.java2d=ALL-UNNAMED"; + margv[++margc] = "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"; + // TODO: workaround, will be removed once the startup part works without PLaunchApp margv[++margc] = "--add-exports=cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED"; } + // Add Caciocavallo bootclasspath + NSString *cacio_classpath = [NSString stringWithFormat:@"-Xbootclasspath/%s", isJava8 ? "p" : "a"]; + NSString *cacio_libs_path = [NSString stringWithFormat:@"%s/libs_caciocavallo%s", getenv("BUNDLE_PATH"), isJava8 ? "" : "17"]; + NSArray *files = [fm contentsOfDirectoryAtPath:cacio_libs_path error:nil]; + for(NSString *file in files) { + if ([file hasSuffix:@".jar"]) { + cacio_classpath = [NSString stringWithFormat:@"%@:%@/%@", cacio_classpath, cacio_libs_path, file]; + } + } + margv[++margc] = (char *)cacio_classpath.UTF8String; + +/* + char cacio_libs_path[2048]; + char cacio_classpath[8192]; + sprintf((char*) cacio_libs_path, "%s/libs_caciocavallo", getenv("BUNDLE_PATH")); + cplen = sprintf(cacio_classpath, "-Xbootclasspath/p"); + d = opendir(cacio_libs_path); + int skip = 2; + if (d) { + while ((dir = readdir(d)) != NULL) { + if (skip > 0) { + --skip; + continue; + } + cplen += sprintf(cacio_classpath + cplen, ":%s/%s", cacio_libs_path, dir->d_name); + } + closedir(d); + } + margv[++margc] = cacio_classpath; +*/ + if (!getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) { // In jailed environment, where extended virtual addressing entitlement isn't // present (for free dev account), the maximum metaspace size is 896M. @@ -301,7 +312,7 @@ int launchJVM(NSString *username, id launchTarget, int width, int height, int mi regLog("[Init] Found JLI lib"); margv[++margc] = "-cp"; - margv[++margc] = classpath; + margv[++margc] = (char *)[NSString stringWithFormat:@"%s/*", java_libs_path].UTF8String; margv[++margc] = "net.kdt.pojavlaunch.PojavLauncher"; if (username == nil) { diff --git a/Natives/LauncherViewController.m b/Natives/LauncherViewController.m index 4338db19..a7eaae2a 100644 --- a/Natives/LauncherViewController.m +++ b/Natives/LauncherViewController.m @@ -253,24 +253,11 @@ int versionSelectedAt = 0; } - (void)enterModInstaller:(UIBarButtonItem*)sender { - NSString *javaVer = getPreference(@"java_home"); - NSFileManager *fileManager = [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:JRE8_HOME_JB] && getenv("POJAV_DETECTEDJB")) { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Cannot use the mod installer" message:@"In order to use the mod installer, you need to install Java 8." preferredStyle:UIAlertControllerStyleActionSheet]; - if (alert.popoverPresentationController != nil) { - alert.popoverPresentationController.sourceView = self.view; - alert.popoverPresentationController.sourceRect = CGRectMake(self.view.frame.size.width-10.0, 0, 10, 10); - } - UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]; - [self presentViewController:alert animated:YES completion:nil]; - [alert addAction:ok]; - } else { - UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.sun.java-archive"] - inMode:UIDocumentPickerModeImport]; - documentPicker.delegate = self; - documentPicker.modalPresentationStyle = UIModalPresentationFormSheet; - [self presentViewController:documentPicker animated:YES completion:nil]; - } + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.sun.java-archive"] + inMode:UIDocumentPickerModeImport]; + documentPicker.delegate = self; + documentPicker.modalPresentationStyle = UIModalPresentationFormSheet; + [self presentViewController:documentPicker animated:YES completion:nil]; } - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { diff --git a/Natives/LoginViewController.m b/Natives/LoginViewController.m index 445b7a59..c88cf80b 100644 --- a/Natives/LoginViewController.m +++ b/Natives/LoginViewController.m @@ -1,6 +1,7 @@ #include