diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java index 846485c4..c397bc80 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/PLaunchApp.java @@ -11,10 +11,7 @@ import net.kdt.pojavlaunch.utils.*; import net.kdt.pojavlaunch.value.*; public class PLaunchApp { - private static float currProgress, maxProgress; - - public static volatile JMinecraftVersionList.Version mVersion; - public static boolean mIsAssetsProcessing = false; + private static float currProgress, maxProgress; public static void main(String[] args) throws Throwable { try { @@ -40,10 +37,10 @@ public class PLaunchApp { UIKit.launchUI(); } - public static String getSelectedVersion() { + public static String getStringFromPref(String pref) { try { String plistContent = Tools.read(Tools.DIR_APP_DATA + "/launcher_preferences.plist"); - plistContent = plistContent.substring(plistContent.indexOf("selected_version") + 27); + plistContent = plistContent.substring(plistContent.indexOf("" + pref + "") + pref.length() + 11); return plistContent.substring( plistContent.indexOf("") + 8, plistContent.indexOf("")); @@ -56,23 +53,21 @@ public class PLaunchApp { // Called from SurfaceViewController public static void launchMinecraft() { try { - if (AccountJNI.CURRENT_ACCOUNT == null) { - AccountJNI.CURRENT_ACCOUNT = MinecraftAccount.load(System.getProperty("pojav.selectedAccount")); - } - mVersion = Tools.getVersionInfo(getSelectedVersion()); - System.out.println("Launching Minecraft " + mVersion.id); + MinecraftAccount account = MinecraftAccount.load(getStringFromPref("internal_selected_account")); + JMinecraftVersionList.Version version = Tools.getVersionInfo(getStringFromPref("selected_version")); + System.out.println("Launching Minecraft " + version.id); String configPath; - if (mVersion.logging != null) { - if (mVersion.logging.client.file.id.equals("client-1.12.xml")) { + if (version.logging != null) { + if (version.logging.client.file.id.equals("client-1.12.xml")) { configPath = Tools.DIR_BUNDLE + "/log4j-rce-patch-1.12.xml"; - } else if (mVersion.logging.client.file.id.equals("client-1.7.xml")) { + } else if (version.logging.client.file.id.equals("client-1.7.xml")) { configPath = Tools.DIR_BUNDLE + "/log4j-rce-patch-1.7.xml"; } else { - configPath = Tools.DIR_GAME_NEW + "/" + mVersion.logging.client.file.id; + configPath = Tools.DIR_GAME_NEW + "/" + version.logging.client.file.id; } System.setProperty("log4j.configurationFile", configPath); } - Tools.launchMinecraft(AccountJNI.CURRENT_ACCOUNT, mVersion); + Tools.launchMinecraft(account, version); } catch (Throwable th) { Tools.showError(th); } diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java index cbc868f0..2656ec5f 100644 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/JavaApp/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -155,7 +155,7 @@ public final class Tools } public static String[] getMinecraftArgs(MinecraftAccount profile, JMinecraftVersionList.Version versionInfo) { - String username = profile.username; + String username = profile.username.replace("Demo.", ""); String versionName = versionInfo.id; if (versionInfo.inheritsFrom != null) { versionName = versionInfo.inheritsFrom; @@ -210,7 +210,6 @@ public final class Tools minecraftArgs.add(Integer.toString(GLFW.mGLFWWindowWidth)); minecraftArgs.add("--fullscreenHeight"); minecraftArgs.add(Integer.toString(GLFW.mGLFWWindowHeight)); - String[] argsFromJson = JSONUtils.insertJSONValueList( splitAndFilterEmpty( versionInfo.minecraftArguments == null ? @@ -235,8 +234,8 @@ public final class Tools private static String[] splitAndFilterEmpty(String argStr, MinecraftAccount profile) { List strList = new ArrayList(); - if(profile.username.equals("demo_user") && profile.accessToken.equals("-1") && profile.profileId.equals("25e00594-cf57-3f87-b3af-3f06591be252")) { - strList.add("--demo"); // if statement is this complex to prevent tampering with the .json + if(profile.username.startsWith("Demo.")) { + strList.add("--demo"); } for (String arg : argStr.split(" ")) { if (!arg.isEmpty()) { @@ -409,6 +408,8 @@ public final class Tools } else if (libItem.name.startsWith("net.java.dev.jna:jna:")) { // Special handling for LabyMod 1.8.9 and Forge 1.12.2(?) // we have libjnidispatch 5.8.0 in Frameworks directory + int[] version = Arrays.stream(libItem.name.split(":")[2].split("\\.")).mapToInt(Integer::parseInt).toArray(); + if (version[0] >= 5 && version[1] >= 8) return; System.out.println("Library " + libItem.name + " has been changed to version 5.8.0"); libItem.name = "net.java.dev.jna:jna:5.8.0"; libItem.downloads.artifact.path = "net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar"; diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java deleted file mode 100644 index d924701b..00000000 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.kdt.pojavlaunch.authenticator.microsoft; - -import android.util.*; - -import java.io.*; -import java.net.*; -import java.text.*; -import java.util.*; - -import net.kdt.pojavlaunch.*; -import net.kdt.pojavlaunch.value.launcherprofiles.*; -import net.kdt.pojavlaunch.value.*; - -import org.json.*; - -public class MicrosoftAuthTask { - - //private Gson gson = new Gson(); - - public MinecraftAccount run(String... args) throws Throwable { - /* - 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.msaRefreshToken = msa.msRefreshToken; - } else if(msa.doesDemo) { - acc.clientToken = "0"; /* FIXME */ - acc.accessToken = "-1"; // Differentiate from local accounts - acc.username = "demo_user"; - acc.profileId = "25e00594-cf57-3f87-b3af-3f06591be252"; - acc.msaRefreshToken = "0"; - } - acc.save(); - - return acc; - } -} - diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/Msa.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/Msa.java deleted file mode 100644 index c2349a7d..00000000 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/Msa.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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 boolean doesDemo; - - 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 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"); - // System.out.println("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 data = new HashMap<>(); - Map 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 data = new HashMap<>(); - Map 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 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 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 if(conn.getResponseCode() == 404) { - // Demo mode stuff - doesOwnGame = false; - doesDemo = true; - } else { - // Log.i("MicroAuth","It seems that this Microsoft Account does not own the game."); - doesOwnGame = false; - doesDemo = false; - throwResponseError(conn); - - } - } - - public static String ofJSONData(Map data) { - return new JSONObject(data).toString(); - } - - public static String ofFormData(Map data) { - StringBuilder builder = new StringBuilder(); - for (Map.Entry 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 otherErrStr = ""; - String errStr = Tools.read(conn.getErrorStream()); - // Log.i("MicroAuth","Error code: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + "\n" + errStr); - -// it seems already handled by the demo mode.. -/* - if (errStr.contains("NOT_FOUND") && - errStr.contains("The server has not found anything matching the request URI")) - { - // TODO localize this - otherErrStr = "It seems that this Microsoft Account does not own the game. Make sure that you have bought/migrated to your Microsoft account."; - } else { -*/ - try { - JSONObject jo = new JSONObject(errStr); - switch ((int)(jo.getLong("XErr") - 2148916230l)) { - case 8: // AddChildToFamily - otherErrStr = "This account is under 18 and needs to be added to a Family by an adult before proceed."; - break; - case 3: // CreateAccount - otherErrStr = "The account isn't created yet."; - break; - } - } catch (Throwable th) { // not a JSON? - } - //} - - throw new RuntimeException(otherErrStr + "\n\nMSA Error: " + conn.getResponseCode() + ": " + conn.getResponseMessage() + ", error stream:\n" + errStr); - } -} - diff --git a/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/AccountJNI.java b/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/AccountJNI.java deleted file mode 100644 index 15986b96..00000000 --- a/JavaApp/src/main/java/net/kdt/pojavlaunch/uikit/AccountJNI.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.kdt.pojavlaunch.uikit; - -import java.net.UnknownHostException; -import net.kdt.pojavlaunch.Tools; -import net.kdt.pojavlaunch.authenticator.microsoft.MicrosoftAuthTask; -import net.kdt.pojavlaunch.value.MinecraftAccount; - -public class AccountJNI { - public static final int TYPE_SELECTACC = 0; - public static final int TYPE_MICROSOFT = 1; - public static final int TYPE_OFFLINE = 2; - - public static volatile MinecraftAccount CURRENT_ACCOUNT; - - // Call back about account credentials for login - public static String loginAccount(int type, - String data // One of: - // Local username - // Microsoft token - ) { - try { - switch (type) { - case TYPE_SELECTACC: - CURRENT_ACCOUNT = MinecraftAccount.load(data); - try { - if (CURRENT_ACCOUNT.accessToken.length() > 5) { - CURRENT_ACCOUNT = new MicrosoftAuthTask().run("true", CURRENT_ACCOUNT.msaRefreshToken); - } - CURRENT_ACCOUNT = MinecraftAccount.load(data); - } catch (UnknownHostException e) { - e.printStackTrace(); - // ignore, since it's likely that the user is offline - } - return (CURRENT_ACCOUNT.accessToken.equals("0") ? "-" : "$") + CURRENT_ACCOUNT.username; - - case TYPE_MICROSOFT: - CURRENT_ACCOUNT = new MicrosoftAuthTask().run("false", data); - return "$" + CURRENT_ACCOUNT.username; - - case TYPE_OFFLINE: - CURRENT_ACCOUNT = new MinecraftAccount(); - CURRENT_ACCOUNT.username = data; - CURRENT_ACCOUNT.save(); - return "-" + CURRENT_ACCOUNT.username; - } - } catch (Throwable th) { - Tools.showError(th); - } - - return null; - } - - static { - // System.loadLibrary("pojavexec"); - } -} diff --git a/Makefile b/Makefile index 4880f335..ee0b68a8 100644 --- a/Makefile +++ b/Makefile @@ -285,6 +285,7 @@ deb: native java extras @cp $(SOURCEDIR)/JavaApp/local_out/launcher.jar $(WORKINGDIR)/PojavLauncher.app/libs/launcher.jar || exit 1 @cp -R $(SOURCEDIR)/JavaApp/libs/* $(WORKINGDIR)/PojavLauncher.app/libs/ || exit 1 @cp -R $(SOURCEDIR)/JavaApp/libs_caciocavallo/* $(WORKINGDIR)/PojavLauncher.app/libs_caciocavallo/ || exit 1 + @cp -R $(SOURCEDIR)/Natives/*.lproj $(WORKINGDIR)/PojavLauncher.app/ || exit 1 $(call DIRCHECK,$(OUTPUTDIR)) @cp -R $(WORKINGDIR)/PojavLauncher.app $(OUTPUTDIR) @if [ '$(RELEASE)' = '1' ]; then \ diff --git a/Natives/AboutLauncherViewController.m b/Natives/AboutLauncherViewController.m index 811a0ae9..3a45b4d4 100644 --- a/Natives/AboutLauncherViewController.m +++ b/Natives/AboutLauncherViewController.m @@ -16,8 +16,7 @@ #endif -@interface AboutLauncherViewController () { -} +@interface AboutLauncherViewController() @property (nonatomic, strong) UIActivityViewController *activityViewController; @end diff --git a/Natives/AccountListViewController.h b/Natives/AccountListViewController.h new file mode 100644 index 00000000..7ab063c2 --- /dev/null +++ b/Natives/AccountListViewController.h @@ -0,0 +1,8 @@ +#import + +@interface AccountListViewController : UITableViewController + +@property (nonatomic, copy) void (^whenDelete)(NSString* name); +@property(nonatomic, copy) void (^whenItemSelected)(NSString* name); + +@end diff --git a/Natives/AccountListViewController.m b/Natives/AccountListViewController.m new file mode 100644 index 00000000..bebb8ebe --- /dev/null +++ b/Natives/AccountListViewController.m @@ -0,0 +1,100 @@ +#import "AccountListViewController.h" +#import "AFNetworking.h" +#import "utils.h" + + +@interface ATableViewCell : UITableViewCell +@end + +@implementation ATableViewCell + +@end + + +@interface AccountListViewController() + +@property(nonatomic, strong) NSMutableArray *accountList; + +@end + +@implementation AccountListViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + if (self.accountList == nil) { + self.accountList = [NSMutableArray array]; + } else { + [self.accountList removeAllObjects]; + } + + // List accounts + NSString *listPath = [NSString stringWithFormat:@"%s/accounts", getenv("POJAV_HOME")]; + NSFileManager *fm = [NSFileManager defaultManager]; + NSArray *files = [fm contentsOfDirectoryAtPath:listPath error:nil]; + for(NSString *file in files) { + NSString *path = [listPath stringByAppendingPathComponent:file]; + BOOL isDir = NO; + [fm fileExistsAtPath:path isDirectory:(&isDir)]; + if(!isDir && [file hasSuffix:@".json"]) { + [self.accountList addObject:parseJSONFromFile(path)]; + } + } + + [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.accountList.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; + } + + NSDictionary *selected = self.accountList[indexPath.row]; + cell.textLabel.text = selected[@"username"]; + if ([selected[@"username"] hasPrefix:@"Demo."]) { + cell.textLabel.text = [selected[@"username"] substringFromIndex:5]; + cell.detailTextLabel.text = NSLocalizedString(@"login.option.demo", nil); + } else if (selected[@"xboxGamertag"] == nil) { + cell.detailTextLabel.text = NSLocalizedString(@"login.option.local", nil); + } else { + // Display the Xbox gamertag for online accounts + cell.detailTextLabel.text = selected[@"xboxGamertag"]; + } + + cell.imageView.contentMode = UIViewContentModeCenter; + [cell.imageView setImageWithURL:[NSURL URLWithString:[selected[@"profilePicURL"] stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]] placeholderImage:[UIImage imageNamed:@"default_account_pfp"]]; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self dismissViewControllerAnimated:YES completion:nil]; + + self.whenItemSelected(self.accountList[indexPath.row][@"username"]); +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { + if (editingStyle == UITableViewCellEditingStyleDelete) { + // TODO: invalidate token + + NSString *str = [self.accountList objectAtIndex:indexPath.row][@"username"]; + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *path = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), str]; + if (self.whenDelete != nil) { + self.whenDelete(path); + } + [fm removeItemAtPath:path error:nil]; + [self.accountList removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; + } +} + +@end diff --git a/Natives/CMakeLists.txt b/Natives/CMakeLists.txt index d198bdf8..be708ccd 100644 --- a/Natives/CMakeLists.txt +++ b/Natives/CMakeLists.txt @@ -54,7 +54,11 @@ add_executable(PojavLauncher JavaLauncher.m external/fishhook/fishhook.c + authenticator/BaseAuthenticator.m + authenticator/LocalAuthenticator.m + authenticator/MicrosoftAuthenticator.m AboutLauncherViewController.m + AccountListViewController.m AppDelegate.m CustomControlsViewController.m FileListViewController.m diff --git a/Natives/CustomControlsViewController.m b/Natives/CustomControlsViewController.m index 4f0e8d08..b97bb3b4 100644 --- a/Natives/CustomControlsViewController.m +++ b/Natives/CustomControlsViewController.m @@ -139,21 +139,8 @@ NSMutableArray *keyCodeMap, *keyValueMap; } } - NSError *cc_error; - NSString *cc_data = [NSString stringWithContentsOfFile:controlFilePath encoding:NSUTF8StringEncoding error:&cc_error]; - - if (cc_error) { - NSLog(@"Error: could not read %@: %@", controlFilePath, cc_error.localizedDescription); - showDialog(self, @"Error", [NSString stringWithFormat:@"Could not read %@: %@", controlFilePath, cc_error.localizedDescription]); - return; - } - - NSData* cc_objc_data = [cc_data dataUsingEncoding:NSUTF8StringEncoding]; - self.cc_dictionary = [NSJSONSerialization JSONObjectWithData:cc_objc_data options:NSJSONReadingMutableContainers error:&cc_error]; - if (cc_error) { - showDialog(self, @"Error parsing JSON", cc_error.localizedDescription); - return; - } + self.cc_dictionary = parseJSONFromFile(controlFilePath); + if (self.cc_dictionary == nil) return; loadControlObject(self.offsetView, self.cc_dictionary, ^void(ControlButton* button) { [button addGestureRecognizer:[[UITapGestureRecognizer alloc] diff --git a/Natives/FileListViewController.m b/Natives/FileListViewController.m index 4fb074cd..b39e22bc 100644 --- a/Natives/FileListViewController.m +++ b/Natives/FileListViewController.m @@ -18,7 +18,7 @@ [self.fileList removeAllObjects]; } - // List accounts + // List files NSFileManager *fm = [NSFileManager defaultManager]; NSArray *files = [fm contentsOfDirectoryAtPath:self.listPath error:nil]; for(NSString *file in files) { @@ -30,28 +30,22 @@ } } - CGRect screenBounds = [[UIScreen mainScreen] bounds]; - - int width = (int) roundf(screenBounds.size.width); - int height = (int) roundf(screenBounds.size.height) - self.navigationController.navigationBar.frame.size.height; - [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [self.fileList count]; + return self.fileList.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - NSString *simpleTableIdentifier = @"cell"; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier]; - + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier]; + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } - + cell.textLabel.text = [self.fileList objectAtIndex:indexPath.row]; return cell; } @@ -59,14 +53,11 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self dismissViewControllerAnimated:YES completion:nil]; - NSString *str = [self.fileList objectAtIndex:indexPath.row]; - self.whenItemSelected(str); + self.whenItemSelected(self.fileList [indexPath.row]); } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { - // TODO: invalidate token - NSString *str = [self.fileList objectAtIndex:indexPath.row]; NSFileManager *fm = [NSFileManager defaultManager]; NSString *path = [NSString stringWithFormat:@"%@/%@.json", self.listPath, str]; diff --git a/Natives/LauncherPreferences.m b/Natives/LauncherPreferences.m index 4075b536..cbd27000 100644 --- a/Natives/LauncherPreferences.m +++ b/Natives/LauncherPreferences.m @@ -117,7 +117,6 @@ void loadPreferences() { setDefaultValueForPref(warnPrefDict, @"jb_warn", @YES); setDefaultValueForPref(warnPrefDict, @"customctrl_warn", @YES); setDefaultValueForPref(prefDict, @"a7_allow", @NO); - setDefaultValueForPref(envPrefDict, @"disable_gl4es_shaderconv", @NO); setDefaultValueForPref(prefDict, @"slideable_hotbar", @NO); setDefaultValueForPref(prefDict, @"virtmouse_enable", @NO); setDefaultValueForPref(prefDict, @"check_sha", @YES); diff --git a/Natives/LauncherPreferencesViewController.m b/Natives/LauncherPreferencesViewController.m index 4ee70e15..cebb692c 100644 --- a/Natives/LauncherPreferencesViewController.m +++ b/Natives/LauncherPreferencesViewController.m @@ -12,7 +12,7 @@ #define REND 4 #define JHOME 5 #define GDIRECTORY 6 -#define NOSHADERCONV 7 + #define RESETWARN 8 #define TYPESEL 9 #define DEBUGLOG 10 @@ -44,7 +44,7 @@ #define TAG_PICKER_GDIR 144 #define TAG_DONE_GDIR 154 -#define TAG_NOSHADERCONV 105 + #define TAG_RESETWARN 106 #define TAG_SWITCH_VRELEASE 107 @@ -88,13 +88,11 @@ NSMutableArray* jhomeList; UIPickerView* rendPickerView; UIPickerView* gdirPickerView; UIPickerView* jhomePickerView; -UISwitch *noshaderconvSwitch, *slideHotbarSwitch; +UISwitch *slideHotbarSwitch; UIBlurEffect *blur; UIVisualEffectView *blurView; UIScrollView *scrollView; -NSString *gl4es114 = @"gl4es 1.1.4 - exports OpenGL 2.1"; -NSString *gl4es115 = @"gl4es 1.1.5 (1.16+) - exports OpenGL 2.1"; NSDictionary *rendererDict; NSString *java8jben = @"Java 8"; @@ -130,8 +128,7 @@ int tempIndex; }; */ rendererDict = @{ - gl4es114: @"libgl4es_114.dylib", - gl4es115: @"libgl4es_115.dylib", + @"gl4es 1.1.4 - exports OpenGL 2.1": @"libgl4es_114.dylib", @"tinygl4angle (1.17+) - exports OpenGL 3.2 (Core Profile, limited)": @"libtinygl4angle.dylib", @"Zink (Mesa 21.0) - exports OpenGL 4.1": @"libOSMesaOverride.dylib" }; @@ -379,19 +376,6 @@ int tempIndex; setPreference(@"option_warn", @NO); } - UILabel *noshaderconvTextView = [[UILabel alloc] initWithFrame:CGRectMake(16.0, currY+=44.0, 0.0, 0.0)]; - noshaderconvTextView.text = @"Disable gl4es 1.1.5 shaderconv"; - noshaderconvTextView.numberOfLines = 0; - [noshaderconvTextView sizeToFit]; - [tableView addSubview:noshaderconvTextView]; - - noshaderconvSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(width - 62.0, currY - 5.0, 50.0, 30)]; - noshaderconvSwitch.tag = TAG_NOSHADERCONV; - [noshaderconvSwitch setOn:[getPreference(@"disable_gl4es_shaderconv") boolValue] animated:NO]; - [noshaderconvSwitch addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged]; - [tableView addSubview:noshaderconvSwitch]; - [noshaderconvSwitch setEnabled:[getPreference(@"renderer") isEqualToString:gl4es115]]; - UILabel *slideHotbarTextView = [[UILabel alloc] initWithFrame:CGRectMake(16.0, currY+=44.0, 0.0, 0.0)]; slideHotbarTextView.text = @"Slideable hotbar"; slideHotbarTextView.numberOfLines = 0; @@ -578,8 +562,6 @@ int tempIndex; handler:^(__kindof UIAction * _Nonnull action) {[self helpAlertOpt:JHOME];}], [UIAction actionWithTitle:@"Game directory" image:[[UIImage systemImageNamed:@"folder"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self helpAlertOpt:GDIRECTORY];}], - [UIAction actionWithTitle:@"Disable shaderconv" image:[[UIImage systemImageNamed:@"circle.lefthalf.fill"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil - handler:^(__kindof UIAction * _Nonnull action) {[self helpAlertOpt:NOSHADERCONV];}], [UIAction actionWithTitle:@"Slideable hotbar" image:[[UIImage systemImageNamed:@"slider.horizontal.below.rectangle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self helpAlertOpt:SLIDEHOTBAR];}], [UIAction actionWithTitle:@"Safe area" image:[[UIImage systemImageNamed:@"crop"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil @@ -659,16 +641,6 @@ int tempIndex; } else if (textField.tag == TAG_REND) { setPreference(@"renderer", rendererDict[textField.text]); setenv("POJAV_RENDERER", [getPreference(@"renderer") UTF8String], 1); - - if (![textField.text isEqualToString:gl4es115] && [getPreference(@"disable_gl4es_shaderconv") boolValue] == YES) { - setPreference(@"disable_gl4es_shaderconv", @NO); - [noshaderconvSwitch setOn:[getPreference(@"disable_gl4es_shaderconv") boolValue] animated:YES]; - [noshaderconvSwitch setEnabled:NO]; - } else if (![textField.text isEqualToString:gl4es114] && [getPreference(@"disable_gl4es_shaderconv") boolValue] == NO){ - setPreference(@"disable_gl4es_shaderconv", @YES); - [noshaderconvSwitch setOn:[getPreference(@"disable_gl4es_shaderconv") boolValue] animated:YES]; - [noshaderconvSwitch setEnabled:YES]; - } } else if (textField.tag == TAG_JHOME) { if ([textField.text isEqualToString:java8jben]) { setPreference(@"java_home", libsjava8jben); @@ -857,7 +829,6 @@ int tempIndex; UIAlertAction *renderer = [UIAlertAction actionWithTitle:@"Renderer" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:REND];}]; UIAlertAction *jhome = [UIAlertAction actionWithTitle:@"Java version" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:JHOME];}]; UIAlertAction *gdirectory = [UIAlertAction actionWithTitle:@"Game directory" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:GDIRECTORY];}]; - UIAlertAction *noshaderconv = [UIAlertAction actionWithTitle:@"Disable shaderconv" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:NOSHADERCONV];}]; UIAlertAction *slidehotbar = [UIAlertAction actionWithTitle:@"Slideable hotbar" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:SLIDEHOTBAR];}]; UIAlertAction *safearea = [UIAlertAction actionWithTitle:@"Safe area" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:SAFEAREA];}]; UIAlertAction *resetwarn = [UIAlertAction actionWithTitle:@"Reset warnings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self helpAlertOpt:RESETWARN];}]; @@ -879,7 +850,6 @@ int tempIndex; [helpAlert addAction:renderer]; [helpAlert addAction:jhome]; [helpAlert addAction:gdirectory]; - [helpAlert addAction:noshaderconv]; [helpAlert addAction:slidehotbar]; [helpAlert addAction:safearea]; [helpAlert addAction:resetwarn]; @@ -912,16 +882,13 @@ int tempIndex; message = @"This option allows you to edit arguments that can be passed to Minecraft. Not all arguments work with PojavLauncher, so be aware. This option also requires a restart of the launcher to take effect."; } else if(setting == REND) { title = @"Renderer"; - message = @"This option allows you to change the renderer in use. Choosing GL4ES 1.1.5 or tinygl4angle may fix sheep and banner colors on 1.16 and allow 1.17 to be played, but will also not work with older versions."; + message = @"This option allows you to change the renderer in use. Choosing tinygl4angle will allow 1.17+ to be played, but will also not work with older versions."; } else if(setting == JHOME) { title = @"Java version"; message = @"This option allows you to change the Java executable directory. Choosing Java 16 or Java 17 may allow you to play 1.17, however older versions and most versions of modded Minecraft, as well as the mod installer, will not work. This option also requires a restart of the launcher to take effect."; } else if(setting == GDIRECTORY) { title = @"Game directory"; message = @"This option allows you to change where your Minecraft settings and saves are stored in a MultiMC like fashion. Useful for when you want to have multiple mod loaders installed but do not want to delete/move mods around to switch. This option also requires a restart of the launcher to take effect."; - } else if(setting == NOSHADERCONV) { - title = @"Disable shaderconv"; - message = @"This option allows you to disable the shader converter inside gl4es 1.1.5 in order to let ANGLE processes them directly. This option is experimental and should only be enabled when playing Minecraft 1.17 or above. Alternatively you can use tinygl4angle for 1.17."; } else if(setting == SLIDEHOTBAR) { title = @"Slideable hotbar"; message = @"This option allows you to use a finger to slide between hotbar slots."; @@ -995,9 +962,6 @@ int tempIndex; - (void)switchChanged:(UISwitch *)sender { switch (sender.tag) { - case TAG_NOSHADERCONV: - setPreference(@"disable_gl4es_shaderconv", @(sender.isOn)); - break; case TAG_RESETWARN: setPreference(@"mem_warn", @YES); setPreference(@"option_warn", @YES); diff --git a/Natives/LoginViewController.m b/Natives/LoginViewController.m index faac739d..eb1917f6 100644 --- a/Natives/LoginViewController.m +++ b/Natives/LoginViewController.m @@ -3,11 +3,13 @@ #import +#import "authenticator/BaseAuthenticator.h" + #import "AppDelegate.h" #import "LauncherViewController.h" #import "LoginViewController.h" #import "AboutLauncherViewController.h" -#import "FileListViewController.h" +#import "AccountListViewController.h" #import "LauncherFAQViewController.h" #import "LauncherPreferences.h" #import "UpdateHistoryViewController.h" @@ -57,9 +59,8 @@ if(getenv("POJAV_DETECTEDJB")) { if(strcmp(getenv("POJAV_DETECTEDJB"), "Other") == 0 && [getPreference(@"jb_warn") boolValue] == YES) { - NSString *jbMessage = @"Your current jailbreak does not have the Procursus bootstrap. Certain issues may occur that cannot be fixed, please switch to or wait for a fully compatible jailbreak."; - UIAlertController *jbAlert = [UIAlertController alertControllerWithTitle:@"Jailbreak not completely supported." message:jbMessage preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]; + UIAlertController *jbAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"login.warn.title.otherjb", nil) message:NSLocalizedString(@"login.warn.message.otherjb", nil) preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleCancel handler:nil]; [self presentViewController:jbAlert animated:YES completion:nil]; [jbAlert addAction:ok]; setPreference(@"jb_warn", @NO); @@ -67,9 +68,8 @@ } if(strncmp(getenv("POJAV_DETECTEDHW"), "iPhone6,", 9) == 0 || strncmp(getenv("POJAV_DETECTEDHW"), "iPad4,", 9) == 0) { - NSString *jbMessage = @"Your device will not support PojavLauncher 2.2. This change is due to hardware and software limitations."; - UIAlertController *jbAlert = [UIAlertController alertControllerWithTitle:@"This is the last update for this device." message:jbMessage preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]; + UIAlertController *jbAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"login.warn.title.a7", nil) message:NSLocalizedString(@"login.warn.message.a7", nil) preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleCancel handler:nil]; [self presentViewController:jbAlert animated:YES completion:nil]; [jbAlert addAction:ok]; } @@ -84,11 +84,11 @@ if(@available (iOS 14.0, *)) { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[[UIImage systemImageNamed:@"info.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] style:UIBarButtonItemStyleDone target:self action:@selector(aboutLauncher)]; - UIAction *option1 = [UIAction actionWithTitle:@"About PojavLauncher" image:[[UIImage systemImageNamed:@"eyes.inverse"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil + UIAction *option1 = [UIAction actionWithTitle:NSLocalizedString(@"login.menu.about", nil) image:[[UIImage systemImageNamed:@"eyes.inverse"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self aboutLauncher];}]; - UIAction *option2 = [UIAction actionWithTitle:@"Send your logs" image:[[UIImage systemImageNamed:@"square.and.arrow.up"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil + UIAction *option2 = [UIAction actionWithTitle:NSLocalizedString(@"login.menu.sendlogs", nil) image:[[UIImage systemImageNamed:@"square.and.arrow.up"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self latestLogShare];}]; - UIAction *option3 = [UIAction actionWithTitle:@"Recent updates" image:[[UIImage systemImageNamed:@"arrow.triangle.2.circlepath.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil + UIAction *option3 = [UIAction actionWithTitle:NSLocalizedString(@"login.menu.updates", nil) image:[[UIImage systemImageNamed:@"arrow.triangle.2.circlepath.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self updateHistory];}]; UIMenu *menu = [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:@[option1, option2, option3]]; @@ -96,7 +96,7 @@ self.navigationItem.rightBarButtonItem.primaryAction = nil; self.navigationItem.rightBarButtonItem.menu = menu; } else { - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"About" style:UIBarButtonItemStyleDone target:self action:@selector(aboutLauncher)]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"login.menu.about", nil) style:UIBarButtonItemStyleDone target:self action:@selector(aboutLauncher)]; } UIButton *button_faq = [UIButton buttonWithType:UIButtonTypeRoundedRect]; @@ -121,11 +121,11 @@ [button_login setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [button_login addTarget:self action:@selector(accountType:) forControlEvents:UIControlEventTouchUpInside]; if(@available (iOS 14.0, *)) { - UIAction *option1 = [UIAction actionWithTitle:@"Microsoft account" image:nil identifier:nil + UIAction *option1 = [UIAction actionWithTitle:NSLocalizedString(@"login.option.microsoft", nil) image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self loginMicrosoft];}]; - UIAction *option3 = [UIAction actionWithTitle:@"Demo account" image:nil identifier:nil + UIAction *option3 = [UIAction actionWithTitle:NSLocalizedString(@"login.option.demo", nil) image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self loginDemo:button_login];}]; - UIAction *option4 = [UIAction actionWithTitle:@"Local account" image:nil identifier:nil + UIAction *option4 = [UIAction actionWithTitle:NSLocalizedString(@"login.option.local", nil) image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {[self loginOffline:button_login];}]; UIMenu *menu = [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:@[option4, option3, option1]]; @@ -181,11 +181,11 @@ if(@available (iOS 14.0, *)) { // UIMenu } else { - UIAlertController *fullAlert = [UIAlertController alertControllerWithTitle:@"Let's get you signed in." message:@"What account do you use to log into Minecraft?"preferredStyle:UIAlertControllerStyleActionSheet]; - UIAlertAction *microsoft = [UIAlertAction actionWithTitle:@"Microsoft account" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginMicrosoft];}]; - UIAlertAction *offline = [UIAlertAction actionWithTitle:@"Local account" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginOffline:sender];}]; - UIAlertAction *demo = [UIAlertAction actionWithTitle:@"Demo account" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginDemo:sender];}]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; + UIAlertController *fullAlert = [UIAlertController alertControllerWithTitle:@"Let's get you signed in." message:@"What account do you use to log into Minecraft?" preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *microsoft = [UIAlertAction actionWithTitle:NSLocalizedString(@"login.option.microsoft", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginMicrosoft];}]; + UIAlertAction *offline = [UIAlertAction actionWithTitle:NSLocalizedString(@"login.option.local", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginOffline:sender];}]; + UIAlertAction *demo = [UIAlertAction actionWithTitle:NSLocalizedString(@"login.option.demo", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginDemo:sender];}]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:NSLocalizedString(NSLocalizedString(@"Cancel", nil), nil) style:UIAlertActionStyleCancel handler:nil]; [self setPopoverProperties:fullAlert.popoverPresentationController sender:sender]; [self presentViewController:fullAlert animated:YES completion:nil]; [fullAlert addAction:microsoft]; @@ -198,53 +198,41 @@ - (void)loginAccountInput:(int)type data:(NSString *)data { __block BOOL shouldDismiss = NO; UIAlertController *alert; + BaseAuthenticator *auth; - if (type == TYPE_SELECTACC) { - NSError *error; - NSString *fileContent = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), data] encoding:NSUTF8StringEncoding error:&error]; - shouldDismiss = [fileContent rangeOfString:@"\"accessToken\": \"0\","].location != NSNotFound; + switch (type) { + case TYPE_MICROSOFT: + auth = [[MicrosoftAuthenticator alloc] initWithInput:data]; + break; + case TYPE_OFFLINE: + auth = [[LocalAuthenticator alloc] initWithInput:data]; + break; + case TYPE_SELECTACC: { + NSString *fileContent = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), data] encoding:NSUTF8StringEncoding error:nil]; + shouldDismiss = [fileContent rangeOfString:@"\"accessToken\": \"0\","].location != NSNotFound; + auth = [BaseAuthenticator loadSavedName:data]; + } break; } - + + if (auth == nil) return; + if (type != TYPE_OFFLINE && !shouldDismiss) { - [self displayProgress:@"Logging in"]; + [self displayProgress:NSLocalizedString(@"login.progress.title", nil)]; } - dispatch_async(dispatch_get_global_queue(0, 0), ^{ - JNIEnv *env; - (*runtimeJavaVMPtr)->AttachCurrentThread(runtimeJavaVMPtr, &env, NULL); - - jstring jdata = (*env)->NewStringUTF(env, data.UTF8String); - assert(jdata); - - jclass clazz = (*env)->FindClass(env, "net/kdt/pojavlaunch/uikit/AccountJNI"); - assert(clazz); - - jmethodID method = (*env)->GetStaticMethodID(env, clazz, "loginAccount", "(ILjava/lang/String;)Ljava/lang/String;"); - assert(method); - - jstring result = (*env)->CallStaticObjectMethod(env, clazz, method, type, jdata); - - dispatch_async(dispatch_get_main_queue(), ^{ - self.title = @""; - self.navigationItem.leftBarButtonItem = nil; - }); - - if (result != NULL) { - const char *username = (*env)->GetStringUTFChars(env, result, 0); - - // temporary check, will be replaced once objc rewrite is complete - setenv("POJAV_INTERNAL_ALLOW_DOWNLOAD", (username[0] == '$' ? "1" : "0"), 1); - - setenv("POJAV_INTERNAL_SELECTED_ACCOUNT", username+1, 1); - (*env)->ReleaseStringUTFChars(env, result, username); - dispatch_async(dispatch_get_main_queue(), ^{ - LauncherViewController *vc = [[LauncherViewController alloc] init]; - [self.navigationController pushViewController:vc animated:YES]; - }); + id callback = ^(BOOL success) { + self.title = @""; + self.navigationItem.leftBarButtonItem = nil; + if (success) { + LauncherViewController *vc = [[LauncherViewController alloc] init]; + [self.navigationController pushViewController:vc animated:YES]; } - - (*runtimeJavaVMPtr)->DetachCurrentThread(runtimeJavaVMPtr); - }); + }; + if (type == TYPE_SELECTACC) { + [auth refreshTokenWithCallback:callback]; + } else { + [auth loginWithCallback:callback]; + } } - (void)loginMicrosoft { @@ -268,63 +256,64 @@ NSString *outError = [components[1] stringByReplacingOccurrencesOfString:@"&error_description=" withString:@": "]; outError = [outError stringByRemovingPercentEncoding]; - showDialog(self, @"Error", outError); + showDialog(self, NSLocalizedString(@"Error", nil), outError); } } } else { if (error.code != ASWebAuthenticationSessionErrorCodeCanceledLogin) { - showDialog(self, @"Error", error.localizedDescription); + showDialog(self, NSLocalizedString(@"Error", nil), error.localizedDescription); } } }]; if (@available(iOS 13.0, *)) { + authVC.prefersEphemeralWebBrowserSession = YES; authVC.presentationContextProvider = self; } if ([authVC start] == NO) { - showDialog(self, @"Error", @"Unable to open Safari"); + showDialog(self, NSLocalizedString(@"Error", nil), @"Unable to open Safari"); } } - (void)loginOffline:(UIButton *)sender { if ([getPreference(@"local_warn") boolValue] == YES) { - UIAlertController *offlineAlert = [UIAlertController alertControllerWithTitle:@"Offline mode is now Local mode." message:@"You can continue to play installed versions, but you can no longer download Minecraft without a paid account. No support will be provided for issues with local accounts, or other means of acquiring Minecraft. See the FAQ for more information."preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *offlineAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"login.warn.title.localmode", nil) message:NSLocalizedString(@"login.warn.message.localmode", nil) preferredStyle:UIAlertControllerStyleActionSheet]; [self setPopoverProperties:offlineAlert.popoverPresentationController sender:sender]; - UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginOffline:sender];}]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self accountType:sender];}]; + UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginOffline:sender];}]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self accountType:sender];}]; [self presentViewController:offlineAlert animated:YES completion:nil]; [offlineAlert addAction:ok]; [offlineAlert addAction:cancel]; setPreference(@"local_warn", @NO); } else { - UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Login" message:@"Account type: Local" preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"login.alert.title", nil) message:NSLocalizedString(@"login.option.local", nil) preferredStyle:UIAlertControllerStyleAlert]; [controller addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = @"Username"; + textField.placeholder = NSLocalizedString(@"login.alert.field.username", nil); textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.borderStyle = UITextBorderStyleRoundedRect; }]; - [controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSArray *textFields = controller.textFields; UITextField *usernameField = textFields[0]; if (usernameField.text.length < 3 || usernameField.text.length > 16) { - controller.message = @"Username must be at least 3 characters and maximum 16 characters"; + controller.message = NSLocalizedString(@"login.error.username.outOfRange", nil); [self presentViewController:controller animated:YES completion:nil]; } else { [self loginAccountInput:TYPE_OFFLINE data:usernameField.text]; } }]]; - [controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:controller animated:YES completion:nil]; } } - (void)loginDemo:(UIButton *)sender { if ([getPreference(@"demo_warn") boolValue] == YES) { - UIAlertController *offlineAlert = [UIAlertController alertControllerWithTitle:@"This option is in beta." message:@"As a replacement to offline and local mode, demo mode will allow you to sign into a Microsoft account that does not own the game and play the Java Edition trial, introduced in 1.3.1 and newer versions. Older versions contain the full game, as the official launcher does not place these restrictions. See our website to learn more about this transition from offline mode."preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *offlineAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"login.warn.title.demomode", nil) message:NSLocalizedString(@"login.warn.message.demomode", nil) preferredStyle:UIAlertControllerStyleActionSheet]; [self setPopoverProperties:offlineAlert.popoverPresentationController sender:sender]; - UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginMicrosoft];}]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self accountType:sender];}]; + UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self loginMicrosoft];}]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self accountType:sender];}]; [self presentViewController:offlineAlert animated:YES completion:nil]; [offlineAlert addAction:ok]; [offlineAlert addAction:cancel]; @@ -335,8 +324,7 @@ } - (void)loginAccount:(UIButton *)sender { - FileListViewController *vc = [[FileListViewController alloc] init]; - vc.listPath = [NSString stringWithFormat:@"%s/accounts", getenv("POJAV_HOME")]; + AccountListViewController *vc = [[AccountListViewController alloc] init]; /* vc.whenDelete = ^void(NSString* name) { UIAlertController* alert = showLoadingDialog(vc, @"Logging out..."); diff --git a/Natives/MinecraftDownloader.m b/Natives/MinecraftDownloader.m index bfea03bf..0a77ca3c 100644 --- a/Natives/MinecraftDownloader.m +++ b/Natives/MinecraftDownloader.m @@ -1,5 +1,6 @@ #include +#import "authenticator/BaseAuthenticator.h" #import "AFNetworking.h" #import "LauncherPreferences.h" #import "LauncherViewController.h" @@ -33,10 +34,12 @@ todo for now - might change anytime @implementation MinecraftDownloader +static AFURLSessionManager* manager; + // Check if the account has permission to download + (BOOL)checkAccessWithDialog:(BOOL)show { // for now - if (getenv("POJAV_INTERNAL_ALLOW_DOWNLOAD")[0] == '1') { + if ([BaseAuthenticator.current.authData[@"username"] hasPrefix:@"Demo."] || BaseAuthenticator.current.authData[@"xboxGamertag"] != nil) { return YES; } else { if (show) { @@ -151,7 +154,6 @@ todo for now - might change anytime } callback([NSString stringWithFormat:@"Downloading %@.json", versionStr], mainProgress, nil); - AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:versionURL]]; NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull progress){ @@ -175,7 +177,7 @@ todo for now - might change anytime } ++mainProgress.completedUnitCount; - NSMutableDictionary *json = [self parseVersionJson:jsonPath]; + NSMutableDictionary *json = parseJSONFromFile(jsonPath); if (isAssetIndex) { success(json); return; @@ -187,9 +189,10 @@ todo for now - might change anytime }]; } }]; + callback([NSString stringWithFormat:@"Downloading %@.json", versionStr], mainProgress, [manager downloadProgressForTask:downloadTask]); [downloadTask resume]; } else { - NSMutableDictionary *json = [self parseVersionJson:jsonPath]; + NSMutableDictionary *json = parseJSONFromFile(jsonPath); if (json == nil) { callback(nil, nil, nil); return; @@ -245,24 +248,6 @@ todo for now - might change anytime } } -+ (NSMutableDictionary *)parseVersionJson:(NSString *)path { - NSError *error; - - NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; - if (content == nil) { - NSLog(@"[MCDL] Error: could not read %@: %@", path, error.localizedDescription); - showDialog(viewController, @"Error", [NSString stringWithFormat:@"Could not read %@: %@", path, error.localizedDescription]); - return nil; - } - - NSData* data = [content dataUsingEncoding:NSUTF8StringEncoding]; - NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; - if (error) { - showDialog(viewController, @"Error parsing JSON", error.localizedDescription); - } - return dict; -} - + (void)tweakVersionJson:(NSMutableDictionary *)json { // Exclude some libraries for (NSMutableDictionary *library in json[@"libraries"]) { @@ -286,7 +271,6 @@ todo for now - might change anytime __block BOOL cancel = NO; dispatch_group_t group = dispatch_group_create(); - AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration]; for (NSDictionary *library in libraries) { if (cancel) { @@ -312,7 +296,8 @@ todo for now - might change anytime if (![self checkAccessWithDialog:YES]) { callback(nil, nil); - return NO; + cancel = YES; + continue; } dispatch_group_enter(group); @@ -339,6 +324,7 @@ todo for now - might change anytime dispatch_group_leave(group); ++mainProgress.completedUnitCount; }]; + callback([NSString stringWithFormat:@"Downloading library %@", name], [manager downloadProgressForTask:downloadTask]); [downloadTask resume]; } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); @@ -350,7 +336,6 @@ todo for now - might change anytime callback(@"Begin: download assets", nil); dispatch_group_t group = dispatch_group_create(); - AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration]; int downloadIndex = -1; __block int jobsAvailable = 20; for (NSString *name in assets) { @@ -359,21 +344,29 @@ todo for now - might change anytime } else if (jobsAvailable == 0) { dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } + + /** Special case for 1.19+ + * Since 1.19-pre1, setting the window icon on macOS goes through ObjC. + * However, if an IOException occurrs, it won't try to set. + * We skip downloading the icon file to trigger this. */ + if ([name isEqualToString:@"icons/minecraft.icns") continue; + --jobsAvailable; dispatch_group_enter(group); NSString *hash = assets[name][@"hash"]; - NSString *pathname = [NSString stringWithFormat:@"%@/%@", [hash substringToIndex:2], hash]; + NSString *pathname = [NSString stringWithFormat:@"%@/%@", [hash substringToIndex:2], hash]; NSString *path = [NSString stringWithFormat:@"%s/assets/objects/%@", getenv("POJAV_GAME_DIR"), pathname]; if ([self checkSHA:hash forFile:path altName:name]) { ++mainProgress.completedUnitCount; ++jobsAvailable; dispatch_group_leave(group); + usleep(1000); continue; } if (![self checkAccessWithDialog:NO]) { dispatch_group_leave(group); - return jobsAvailable != -1; + break; } [NSFileManager.defaultManager createDirectoryAtPath:path.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:nil]; @@ -386,13 +379,21 @@ todo for now - might change anytime return [NSURL fileURLWithPath:path]; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { if (error != nil) { - jobsAvailable = -2; + if (jobsAvailable < 0) { + dispatch_group_leave(group); + return; + } + jobsAvailable = -3; NSString *errorStr = [NSString stringWithFormat:@"Failed to download %@: %@", url, error.localizedDescription]; NSLog(@"[MCDL] Error: %@", errorStr); showDialog(viewController, @"Error", errorStr); callback(nil, nil); } else if (![self checkSHA:hash forFile:path altName:name]) { // Abort when a downloaded file's SHA mismatches + if (jobsAvailable < 0) { + dispatch_group_leave(group); + return; + } jobsAvailable = -2; showDialog(viewController, @"Error", [NSString stringWithFormat:@"Failed to verify file %@: SHA1 mismatch", path.lastPathComponent]); callback(nil, nil); @@ -401,6 +402,7 @@ todo for now - might change anytime ++mainProgress.completedUnitCount; dispatch_group_leave(group); }]; + callback([NSString stringWithFormat:@"Downloading %@", name], [manager downloadProgressForTask:downloadTask]); [downloadTask resume]; NSLog(@"%@: %@", name, hash); } @@ -410,13 +412,14 @@ todo for now - might change anytime } + (void)start:(NSObject *)version callback:(void (^)(NSString *stage, NSProgress *mainProgress, NSProgress *progress))callback { + manager = [[AFURLSessionManager alloc] init]; NSProgress *mainProgress = [NSProgress progressWithTotalUnitCount:0]; [self downloadClientJson:version progress:mainProgress callback:callback success:^(NSMutableDictionary *json) { [self tweakVersionJson:json]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ BOOL success; - mainProgress.totalUnitCount = [json[@"libraries"] count] + [json[@"assetObjects"] count] /* TODO: assets */; + mainProgress.totalUnitCount = [json[@"libraries"] count] + [json[@"assetObjects"] count]; id wrappedCallback = ^(NSString *s, NSProgress *p) { dispatch_async(dispatch_get_main_queue(), ^{ callback(s, mainProgress, p); @@ -431,8 +434,6 @@ todo for now - might change anytime isUseStackQueueCall = json[@"arguments"] != nil; - // Finish - // TODO: pass the success value dispatch_async(dispatch_get_main_queue(), ^{ callback(nil, mainProgress, nil); }); diff --git a/Natives/SurfaceViewController.m b/Natives/SurfaceViewController.m index 3d392845..ccde5347 100644 --- a/Natives/SurfaceViewController.m +++ b/Natives/SurfaceViewController.m @@ -137,7 +137,7 @@ const void * _CGDataProviderGetBytePointerCallbackOSMESA(void *info) { viewController = self; isControlModifiable = NO; - setPreference(@"internal_selected_account", nil); + setPreference(@"internal_launch_on_boot", @(NO)); isUseStackQueueCall = [getPreference(@"internal_useStackQueue") boolValue]; setPreference(@"internal_useStackQueue", nil); @@ -167,11 +167,6 @@ const void * _CGDataProviderGetBytePointerCallbackOSMESA(void *info) { self.surfaceView.layer.magnificationFilter = self.surfaceView.layer.minificationFilter = kCAFilterNearest; [self.view addSubview:self.surfaceView]; - // Enable support for desktop GLSL - if ([getPreference(@"disable_gl4es_shaderconv") boolValue]) { - setenv("LIBGL_NOSHADERCONV", "1", 1); - } - notchOffset = insets.left; width = width - notchOffset * 2; CGFloat buttonScale = [getPreference(@"button_scale") floatValue] / 100.0; @@ -196,8 +191,8 @@ const void * _CGDataProviderGetBytePointerCallbackOSMESA(void *info) { if (@available(iOS 13.0, *)) { UIHoverGestureRecognizer *hoverGesture = [[UIHoverGestureRecognizer alloc] - initWithTarget:self action:@selector(surfaceOnHover:)]; - [self.surfaceView addGestureRecognizer:hoverGesture]; + initWithTarget:self action:@selector(surfaceOnHover:)]; + [self.surfaceView addGestureRecognizer:hoverGesture]; } UILongPressGestureRecognizer *longpressGesture = [[UILongPressGestureRecognizer alloc] diff --git a/Natives/UpdateHistoryViewController.m b/Natives/UpdateHistoryViewController.m index a38f29ca..1105fb58 100644 --- a/Natives/UpdateHistoryViewController.m +++ b/Natives/UpdateHistoryViewController.m @@ -46,13 +46,13 @@ CGFloat uhcurrY = 4.0; [scrollView addSubview:[self uhContent:true size:20 text:[NSString stringWithFormat:@"2.1 (%s)", CONFIG_TYPE] width:width]]; [scrollView addSubview:[self uhContent:true size:17 text:@"Changes" width:width]]; [scrollView addSubview:[self uhContent:false size:0 text:@"- Now shows when currently logging in\n" + "- Account list now displays profile picture and account type\n" "- Play button is greyed out when launching\n" "- Back button is hidden when launching\n" "- New toggle to allow restarting JVM before launching\n" "- Added WIP Demo mode support!\n" "- Patch log4shell exploit in client\n" "- Keyboard and mouse support deepened\n" - "- New \"vgpu\" renderer, exports OpenGL 3.0\n" "- Custom controls UI completed\n" "- Arc custom capes integration!\n" "- Removed Mojang account login" width:width]]; diff --git a/Natives/authenticator/BaseAuthenticator.h b/Natives/authenticator/BaseAuthenticator.h new file mode 100644 index 00000000..5afb77be --- /dev/null +++ b/Natives/authenticator/BaseAuthenticator.h @@ -0,0 +1,24 @@ +#import "Foundation/Foundation.h" + +typedef void(^Callback)(BOOL); + +@interface BaseAuthenticator : NSObject + +@property(class) BaseAuthenticator *current; + +@property NSMutableDictionary *authData; + ++ (id)loadSavedName:(NSString *)name; + +- (id)initWithInput:(NSString *)string; +- (void)loginWithCallback:(Callback)callback; +- (void)refreshTokenWithCallback:(Callback)callback; +- (BOOL)saveChanges; + +@end + +@interface LocalAuthenticator : BaseAuthenticator +@end + +@interface MicrosoftAuthenticator : BaseAuthenticator +@end diff --git a/Natives/authenticator/BaseAuthenticator.m b/Natives/authenticator/BaseAuthenticator.m new file mode 100644 index 00000000..033fc7a8 --- /dev/null +++ b/Natives/authenticator/BaseAuthenticator.m @@ -0,0 +1,71 @@ +#import "BaseAuthenticator.h" +#import "../ios_uikit_bridge.h" +#import "../utils.h" + +@implementation BaseAuthenticator + +static BaseAuthenticator *current = nil; + ++ (id)current { + return current; +} + ++ (id)loadSavedName:(NSString *)name { + NSMutableDictionary *authData = parseJSONFromFile([NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), name]); + if (authData[@"error"] != nil) { + showDialog(viewController, NSLocalizedString(@"Error", nil), ((NSError *)authData[@"error"]).localizedDescription); + return nil; + } + if ([authData[@"accessToken"] length] < 5) { + return [[LocalAuthenticator alloc] initWithData:authData]; + } else { + return [[MicrosoftAuthenticator alloc] initWithData:authData]; + } +} + +- (id)initWithData:(NSMutableDictionary *)data { + current = self = [self init]; + self.authData = data; + return self; +} + +- (id)initWithInput:(NSString *)string { + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + data[@"input"] = string; + return [self initWithData:data]; +} + +- (void)loginWithCallback:(void (^)(BOOL success))callback { +} + +- (void)refreshTokenWithCallback:(void (^)(BOOL success))callback { +} + +- (BOOL)saveChanges { + NSError *error; + + [self.authData removeObjectForKey:@"input"]; + + NSString *newPath = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), self.authData[@"username"]]; + if (self.authData[@"oldusername"] != nil && ![self.authData[@"username"] isEqualToString:self.authData[@"oldusername"]]) { + NSString *oldPath = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), self.authData[@"oldusername"]]; + [NSFileManager.defaultManager moveItemAtPath:oldPath toPath:newPath error:&error]; + // handle error? + } + + [self.authData removeObjectForKey:@"oldusername"]; + + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.authData options:NSJSONWritingPrettyPrinted error:&error]; + if (jsonData == nil) { + showDialog(viewController, @"Error while converting to JSON", error.localizedDescription); + return NO; + } + + BOOL success = [jsonData writeToFile:newPath options:NSDataWritingAtomic error:&error]; + if (!success) { + showDialog(viewController, @"Error while saving file", error.localizedDescription); + } + return success; +} + +@end diff --git a/Natives/authenticator/LocalAuthenticator.m b/Natives/authenticator/LocalAuthenticator.m new file mode 100644 index 00000000..0e15d521 --- /dev/null +++ b/Natives/authenticator/LocalAuthenticator.m @@ -0,0 +1,17 @@ +#import "BaseAuthenticator.h" + +@implementation LocalAuthenticator + +- (void)loginWithCallback:(Callback)callback { + self.authData[@"oldusername"] = self.authData[@"username"] = self.authData[@"input"]; + self.authData[@"accessToken"] = @"0"; + self.authData[@"profileId"] = @"00000000-0000-0000-0000-000000000000"; + callback([super saveChanges]); +} + +- (void)refreshTokenWithCallback:(Callback)callback { + // Nothing to do + callback(YES); +} + +@end diff --git a/Natives/authenticator/MicrosoftAuthenticator.m b/Natives/authenticator/MicrosoftAuthenticator.m new file mode 100644 index 00000000..52ce6ba4 --- /dev/null +++ b/Natives/authenticator/MicrosoftAuthenticator.m @@ -0,0 +1,219 @@ +#import "AFNetworking.h" +#import "BaseAuthenticator.h" +#import "../ios_uikit_bridge.h" +#import "../utils.h" + +typedef void(^XSTSCallback)(NSString *xsts, NSString *uhs); + +@implementation MicrosoftAuthenticator + +- (void)acquireAccessToken:(NSString *)authcode refresh:(BOOL)refresh callback:(Callback)callback { + viewController.title = NSLocalizedString(@"login.msa.progress.acquireAccessToken", nil); + + NSDictionary *data = @{ + @"client_id": @"00000000402b5328", + (refresh ? @"refresh_token" : @"code"): authcode, + @"grant_type": refresh ? @"refresh_token" : @"authorization_code", + @"redirect_url": @"https://login.live.com/oauth20_desktop.srf", + @"scope": @"service::user.auth.xboxlive.com::MBI_SSL" + }; + + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + [manager GET:@"https://login.live.com/oauth20_token.srf" parameters:data headers:nil progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + self.authData[@"msaRefreshToken"] = response[@"refresh_token"]; + [self acquireXBLToken:response[@"access_token"] callback:callback]; + } failure:^(NSURLSessionDataTask *task, NSError *error) { + callback(NO); + NSLog(@"[MSA] Error: %@", error); + showDialog(viewController, @"Error", error.localizedDescription); + }]; +} + +- (void)acquireXBLToken:(NSString *)accessToken callback:(Callback)callback { + viewController.title = NSLocalizedString(@"login.msa.progress.acquireXBLToken", nil); + + NSDictionary *data = @{ + @"Properties": @{ + @"AuthMethod": @"RPS", + @"SiteName": @"user.auth.xboxlive.com", + @"RpsTicket": accessToken + }, + @"RelyingParty": @"http://auth.xboxlive.com", + @"TokenType": @"JWT" + }; + + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + manager.requestSerializer = AFJSONRequestSerializer.serializer; + [manager POST:@"https://user.auth.xboxlive.com/user/authenticate" parameters:data headers:nil progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + Callback innerCallback = ^(BOOL success) { + if (success == NO) { + callback(NO); + return; + } + // Obtain XSTS for authenticating to Minecraft + [self acquireXSTSFor:@"rp://api.minecraftservices.com/" token:response[@"Token"] callback:^(NSString *xsts, NSString *uhs){ + if (xsts == nil) { + callback(NO); + return; + } + [self acquireMinecraftToken:uhs xstsToken:xsts callback:callback]; + }]; + }; + + // Obtain XSTS for getting the Xbox gamertag + [self acquireXSTSFor:@"http://xboxlive.com" token:response[@"Token"] callback:^(NSString *xsts, NSString *uhs){ + if (xsts == nil) { + callback(NO); + return; + } + [self acquireXboxProfile:uhs xstsToken:xsts callback:innerCallback]; + }]; + } failure:^(NSURLSessionDataTask *task, NSError *error) { + callback(NO); + NSLog(@"[MSA] Error: %@", error); + showDialog(viewController, @"Error", error.localizedDescription); + }]; +} + +- (void)acquireXSTSFor:(NSString *)replyingParty token:(NSString *)xblToken callback:(XSTSCallback)callback { + viewController.title = NSLocalizedString(@"login.msa.progress.acquireXSTS", nil); + + NSDictionary *data = @{ + @"Properties": @{ + @"SandboxId": @"RETAIL", + @"UserTokens": @[ + xblToken + ] + }, + @"RelyingParty": replyingParty, + @"TokenType": @"JWT", + }; + + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + manager.requestSerializer = AFJSONRequestSerializer.serializer; + [manager POST:@"https://xsts.auth.xboxlive.com/xsts/authorize" parameters:data headers:nil progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + NSString *uhs = response[@"DisplayClaims"][@"xui"][0][@"uhs"]; + callback(response[@"Token"], uhs); + } failure:^(NSURLSessionDataTask *task, NSError *error) { + callback(nil, nil); + NSString *errorString; + NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; + NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil]; + switch ((int)([errorDict[@"XErr"] longValue]-2148916230l)) { + case 3: + errorString = @"login.msa.error.xsts.noxboxacc"; + break; + case 5: + errorString = @"login.msa.error.xsts.noxbox"; + break; + case 6: + case 7: + errorString = @"login.msa.error.xsts.krverify"; + break; + case 8: + errorString = @"login.msa.error.xsts.underage"; + break; + default: + errorString = [NSString stringWithFormat:@"%@\n\nUnknown XErr code, response:\n%@", error.localizedDescription, errorDict]; + break; + } + NSLog(@"[MSA] Error: %@", errorString); + showDialog(viewController, NSLocalizedString(@"Error", nil), NSLocalizedString(errorString, nil)); + }]; +} + + +- (void)acquireXboxProfile:(NSString *)xblUhs xstsToken:(NSString *)xblXsts callback:(Callback)callback { + viewController.title = NSLocalizedString(@"login.msa.progress.acquireXboxProfile", nil); + + NSDictionary *headers = @{ + @"x-xbl-contract-version": @"2", + @"Authorization": [NSString stringWithFormat:@"XBL3.0 x=%@;%@", xblUhs, xblXsts] + }; + + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + [manager GET:@"https://profile.xboxlive.com/users/me/profile/settings?settings=PublicGamerpic,Gamertag" parameters:nil headers:headers progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + self.authData[@"profilePicURL"] = [NSString stringWithFormat:@"%@&h=120&w=120", response[@"profileUsers"][0][@"settings"][0][@"value"]]; + self.authData[@"xboxGamertag"] = response[@"profileUsers"][0][@"settings"][1][@"value"]; + callback(YES); + } failure:^(NSURLSessionDataTask *task, NSError *error) { + callback(NO); + NSLog(@"[MSA] Error: %@", error); + showDialog(viewController, @"Error", error.localizedDescription); + }]; +} + +- (void)acquireMinecraftToken:(NSString *)xblUhs xstsToken:(NSString *)xblXsts callback:(Callback)callback { + viewController.title = NSLocalizedString(@"login.msa.progress.acquireMCToken", nil); + + NSDictionary *data = @{ + @"identityToken": [NSString stringWithFormat:@"XBL3.0 x=%@;%@", xblUhs, xblXsts] + }; + + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + manager.requestSerializer = AFJSONRequestSerializer.serializer; + [manager POST:@"https://api.minecraftservices.com/authentication/login_with_xbox" parameters:data headers:nil progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + self.authData[@"accessToken"] = response[@"access_token"]; + [self checkMCProfile:response[@"access_token"] callback:callback]; + } failure:^(NSURLSessionDataTask *task, NSError *error) { + callback(NO); + NSLog(@"[MSA] Error: %@", error); + showDialog(viewController, @"Error", error.localizedDescription); + }]; +} + +- (void)checkMCProfile:(NSString *)mcAccessToken callback:(Callback)callback { + self.authData[@"expiresAt"] = @((long)[NSDate.date timeIntervalSince1970] + 86400); + + viewController.title = NSLocalizedString(@"login.msa.progress.checkMCProfile", nil); + + NSDictionary *headers = @{ + @"Authorization": [NSString stringWithFormat:@"Bearer %@", mcAccessToken] + }; + AFHTTPSessionManager *manager = AFHTTPSessionManager.manager; + manager.requestSerializer = AFJSONRequestSerializer.serializer; + [manager GET:@"https://api.minecraftservices.com/minecraft/profile" parameters:nil headers:headers progress:nil success:^(NSURLSessionDataTask *task, NSDictionary *response) { + NSString *uuid = response[@"id"]; + self.authData[@"profileId"] = [NSString stringWithFormat:@"%@-%@-%@-%@-%@", + [uuid substringWithRange:NSMakeRange(0, 8)], + [uuid substringWithRange:NSMakeRange(8, 4)], + [uuid substringWithRange:NSMakeRange(12, 4)], + [uuid substringWithRange:NSMakeRange(16, 4)], + [uuid substringWithRange:NSMakeRange(20, 12)] + ]; + self.authData[@"profilePicURL"] = [NSString stringWithFormat:@"https://mc-heads.net/head/%@/120", self.authData[@"profileId"]]; + self.authData[@"oldusername"] = self.authData[@"username"]; + self.authData[@"username"] = response[@"name"]; + callback([super saveChanges]); + } failure:^(NSURLSessionDataTask *task, NSError *error) { + NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; + NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil]; + if ([errorDict[@"error"] isEqualToString:@"NOT_FOUND"]) { + // If there is no profile, use the Xbox gamertag as username with Demo mode + self.authData[@"profileId"] = @"00000000-0000-0000-0000-000000000000"; + self.authData[@"username"] = [NSString stringWithFormat:@"Demo.%@", self.authData[@"xboxGamertag"]]; + + showDialog(viewController, NSLocalizedString(@"Notice", nil), NSLocalizedString(@"login.msa.notice.demomode", nil)); + callback([super saveChanges]); + return; + } + + callback(NO); + NSLog(@"[MSA] Error: %@", errorDict); + showDialog(viewController, @"Error", [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding]); + }]; +} + +- (void)loginWithCallback:(Callback)callback { + [self acquireAccessToken:self.authData[@"input"] refresh:NO callback:callback]; +} + +- (void)refreshTokenWithCallback:(Callback)callback { + if ([NSDate.date timeIntervalSince1970] > [self.authData[@"expiresAt"] longValue]) { + [self acquireAccessToken:self.authData[@"msaRefreshToken"] refresh:YES callback:callback]; + } else { + callback(YES); + } +} + +@end diff --git a/Natives/egl_bridge.m b/Natives/egl_bridge.m index ad4e2973..e3dc1fec 100644 --- a/Natives/egl_bridge.m +++ b/Natives/egl_bridge.m @@ -221,12 +221,11 @@ int pojavInit() { mainThreadID = gettid(); NSString *renderer = @(getenv("POJAV_RENDERER")); - BOOL isVGPU = [renderer hasPrefix:@"libvgpu"]; if ([renderer isEqualToString:@"libOSMesa.8.dylib"]) { config_renderer = RENDERER_VIRGL; setenv("GALLIUM_DRIVER", "virpipe", 1); loadSymbolsVirGL(); - } else if ([renderer hasPrefix:@"libgl4es"] || [renderer hasPrefix:@"libtinygl4angle"] || isVGPU) { + } else if ([renderer hasPrefix:@"libgl4es"] || [renderer hasPrefix:@"libtinygl4angle"]) { config_renderer = RENDERER_MTL_ANGLE; loadSymbols(); } else if ([renderer hasPrefix:@"libOSMesa"]) { @@ -235,7 +234,6 @@ int pojavInit() { loadSymbols(); } - if (config_renderer == RENDERER_MTL_ANGLE || config_renderer == RENDERER_VIRGL) { if (potatoBridge.eglDisplay == EGL_NO_DISPLAY) { potatoBridge.eglDisplay = eglGetPlatformDisplay_p(EGL_PLATFORM_ANGLE_ANGLE, (void *)EGL_DEFAULT_DISPLAY, NULL); @@ -245,6 +243,10 @@ int pojavInit() { } } + // Pre-load the renderer library + void *renderer_handle = dlopen(getenv("POJAV_RENDERER"), RTLD_GLOBAL); + debugLog("%s=%p, error=%s", getenv("POJAV_RENDERER"), renderer_handle, dlerror()); + debugLog("EGLBridge: Initializing"); // printf("EGLBridge: ANativeWindow pointer = %p\n", potatoBridge.androidWindow); //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); @@ -282,11 +284,9 @@ int pojavInit() { //ANativeWindow_setBuffersGeometry(potatoBridge.androidWindow, 0, 0, vid); - if (/*!isVGPU &&*/ !eglBindAPI_p(EGL_OPENGL_API)) { + if (!eglBindAPI_p(EGL_OPENGL_API)) { debugLog("EGLBridge: Failed to bind EGL_OPENGL_API, falling back to EGL_OPENGL_ES_API, error=0x%x", eglGetError_p()); eglBindAPI_p(EGL_OPENGL_ES_API); - } else if (isVGPU) { - eglBindAPI_p(EGL_OPENGL_ES_API); } potatoBridge.eglSurface = eglCreateWindowSurface_p(potatoBridge.eglDisplay, config, (__bridge EGLNativeWindowType) ((SurfaceViewController *)viewController).surfaceView.layer, NULL); @@ -419,20 +419,6 @@ void pojavMakeCurrent(void* window) { eglSwapBuffers(potatoBridge.eglDisplay, potatoBridge.eglSurface); debugLog("First frame error: 0x%x", eglGetError()); #endif - - // ADDITIONAL STEP ON IOS: Initialize gl4es - void *gl4es_handle = dlopen(getenv("POJAV_RENDERER"), RTLD_GLOBAL); - debugLog("%s=%p, error=%s", getenv("POJAV_RENDERER"), gl4es_handle, dlerror()); - - gl4esInitialize_func *gl4esInitialize = (gl4esInitialize_func*) dlsym(gl4es_handle, "initialize_gl4es"); - // debugLog("initialize_gl4es = %p", gl4esInitialize); - if (gl4esInitialize) { - gl4esInitialize(); - } else { - debugLog("%s", dlerror()); - } - regLog("Renderer init success"); - return; } else { // (*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); @@ -469,6 +455,10 @@ void* pojavCreateContext(void* contextSrc) { EGL_NONE }; EGLContext* ctx = eglCreateContext_p(potatoBridge.eglDisplay, config, (void*)contextSrc, ctx_attribs); + if (!ctx) { + debugLog("EGLBridge: Failed to create context: 0x%x, returning previous one.", eglGetError_p()); + return potatoBridge.eglContext; + } potatoBridge.eglContext = ctx; debugLog("EGLBridge: Created CTX pointer = %p",ctx); //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); diff --git a/Natives/en.lproj/InfoPlist.strings b/Natives/en.lproj/InfoPlist.strings index a7361add..b92732c7 100644 --- a/Natives/en.lproj/InfoPlist.strings +++ b/Natives/en.lproj/InfoPlist.strings @@ -1 +1 @@ -/* Localized versions of Info.plist keys */ \ No newline at end of file +/* Localized versions of Info.plist keys */ diff --git a/Natives/en.lproj/Localizable.strings b/Natives/en.lproj/Localizable.strings new file mode 100644 index 00000000..035d8127 --- /dev/null +++ b/Natives/en.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* + Localizable.strings + StringLocalization +*/ + +"login.error.username.outOfRange" = "Username must be at least 3 characters and maximum 16 characters"; + +"login.alert.title" = "Login"; +"login.alert.field.username" = "Username"; + +"login.menu.about" = "About PojavLauncher"; +"login.menu.sendlogs" = "Send your logs"; +"login.menu.updates" = "Recent updates"; + +"login.option.microsoft" = "Microsoft account"; +"login.option.demo" = "Demo account"; +"login.option.local" = "Local account"; + +"login.progress.title" = "Logging in"; + +"login.warn.title.a7" = "This is the last update for this device."; +"login.warn.message.a7" = "Your device will not support PojavLauncher 2.2. This change is due to hardware and software limitations."; + +"login.warn.title.demomode" = "This option is in beta."; +"login.warn.message.demomode" = "As a replacement to offline and local mode, demo mode will allow you to sign into a Microsoft account that does not own the game and play the Java Edition trial, introduced in 1.3.1 and newer versions. Older versions contain the full game, as the official launcher does not place these restrictions. See our website to learn more about this transition from offline mode."; + +"login.warn.title.otherjb" = "Jailbreak not completely supported."; +"login.warn.message.otherjb" = "Your current jailbreak does not have the Procursus bootstrap. Certain issues may occur that cannot be fixed, please switch to or wait for a fully compatible jailbreak."; + +"login.warn.title.localmode" = "Offline mode is now Local mode."; +"login.warn.message.localmode" = "You can continue to play installed versions, but you can no longer download Minecraft without a paid account. No support will be provided for issues with local accounts, or other means of acquiring Minecraft. See the FAQ for more information."; + +"login.msa.error.xsts.noxboxacc" = "An Xbox profile is not registered for this Microsoft account. Please make sure that you have made an Xbox profile.\nError code: 2148916233"; +"login.msa.error.xsts.noxbox" = "Xbox Live is currently unavailable in your country.\nError code: 2148916235"; +"login.msa.error.xsts.krverify" = "Your account needs to be verified by your parents before continue.\nError code: 2148916236 or 2148916237."; +"login.msa.error.xsts.underage" = "Your account needs to be linked to a patent account before continue.\nError code: 2148916238"; + +"login.msa.notice.demomode" = "Could not find a Minecraft profile on this account. The account is now set to Demo mode. Did you buy Minecraft, or is there an active Xbox GamePass subscription? Please configure your profile in https://minecraft.net/msaprofle to restore to Full mode."; + +"login.msa.progress.acquireAccessToken" = "(1/5) Acquiring the Access token"; +"login.msa.progress.acquireXBLToken" = "(2/5) Acquiring the Xbox Live token"; +"login.msa.progress.acquireXSTS" = "Acquiring XSTS"; +"login.msa.progress.acquireXboxProfile" = "(3/5) Acquiring the Xbox profile"; +"login.msa.progress.acquireMCToken" = "(4/5) Acquiring the Minecraft token"; +"login.msa.progress.checkMCProfile" = "(5/5) Checking the Minecraft profile"; diff --git a/Natives/ios_uikit_bridge.m b/Natives/ios_uikit_bridge.m index 2e72e14f..bca8db6e 100644 --- a/Natives/ios_uikit_bridge.m +++ b/Natives/ios_uikit_bridge.m @@ -1,4 +1,4 @@ - +#import "authenticator/BaseAuthenticator.h" #import "AppDelegate.h" #import "BKSSystemService.h" #import "SceneDelegate.h" @@ -24,7 +24,7 @@ void internal_showDialog(UIViewController *viewController, NSString* title, NSSt UIAlertController* alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - + //text.dataDetectorTypes = UIDataDetectorTypeLink; UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:okAction]; @@ -122,8 +122,9 @@ void UIKit_launchJarFile(const char* filepath) { } void UIKit_launchMinecraftSurfaceVC() { + setPreference(@"internal_launch_on_boot", getPreference(@"restart_before_launch")); + setPreference(@"internal_selected_account", BaseAuthenticator.current.authData[@"username"]); setPreference(@"internal_useStackQueue", @(isUseStackQueueCall ? YES : NO)); - setPreference(@"internal_selected_account", @(getenv("POJAV_INTERNAL_SELECTED_ACCOUNT"))); dispatch_async(dispatch_get_main_queue(), ^{ if ([getPreference(@"restart_before_launch") boolValue]) { NSURL *url = [NSURL URLWithString:@"pojavlauncher://"]; @@ -146,11 +147,10 @@ void UIKit_launchMinecraftSurfaceVC() { } void launchInitialViewController(UIWindow *window) { - NSString *selectedAccount = getPreference(@"internal_selected_account"); - if (selectedAccount == nil) { + if ([getPreference(@"internal_launch_on_boot") boolValue]) { + window.rootViewController = [[SurfaceViewController alloc] init]; + } else { LoginViewController *vc = [[LoginViewController alloc] init]; window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc]; - } else { - window.rootViewController = [[SurfaceViewController alloc] init]; } } diff --git a/Natives/resources/Frameworks/libgl4es_114.dylib b/Natives/resources/Frameworks/libgl4es_114.dylib index 58cdd927..8d6a384f 100644 Binary files a/Natives/resources/Frameworks/libgl4es_114.dylib and b/Natives/resources/Frameworks/libgl4es_114.dylib differ diff --git a/Natives/resources/Frameworks/libgl4es_115.dylib b/Natives/resources/Frameworks/libgl4es_115.dylib deleted file mode 100644 index 4eac9a3b..00000000 Binary files a/Natives/resources/Frameworks/libgl4es_115.dylib and /dev/null differ diff --git a/Natives/resources/Frameworks/libvgpu.dylib b/Natives/resources/Frameworks/libvgpu.dylib deleted file mode 100644 index b923fe36..00000000 Binary files a/Natives/resources/Frameworks/libvgpu.dylib and /dev/null differ diff --git a/Natives/utils.h b/Natives/utils.h index bc65e435..963d8be7 100644 --- a/Natives/utils.h +++ b/Natives/utils.h @@ -60,6 +60,9 @@ int savedWidth, savedHeight; BOOL virtualMouseEnabled; +NSMutableDictionary* parseJSONFromFile(NSString *path); +NSError* saveJSONToFile(NSMutableDictionary *dict, NSString *path); + static inline CGFloat clamp(CGFloat x, CGFloat lower, CGFloat upper) { return fmin(upper, fmax(x, lower)); } diff --git a/Natives/utils.m b/Natives/utils.m index 2500982a..46e033d3 100644 --- a/Natives/utils.m +++ b/Natives/utils.m @@ -8,6 +8,39 @@ #include "utils.h" +NSMutableDictionary* parseJSONFromFile(NSString *path) { + NSError *error; + + NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + if (content == nil) { + NSLog(@"[ParseJSON] Error: could not read %@: %@", path, error.localizedDescription); + return [@{@"error": error} mutableCopy]; + } + + NSData* data = [content dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; + if (error) { + NSLog(@"[ParseJSON] Error: could not parse JSON: %@", error.localizedDescription); + return [@{@"error": error} mutableCopy]; + } + return dict; +} + +NSError* saveJSONToFile(NSMutableDictionary *dict, NSString *path) { + // TODO: handle rename + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; + if (jsonData == nil) { + return error; + } + NSString *jsonStr = [NSString stringWithUTF8String:jsonData.bytes]; + BOOL success = [jsonStr writeToFile:[NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), dict[@"username"]] atomically:YES encoding:NSUTF8StringEncoding error:&error]; + if (!success) { + return error; + } + return nil; +} + CGFloat MathUtils_dist(CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2) { const CGFloat x = (x2 - x1); const CGFloat y = (y2 - y1);