From e2db0d35edc9d9974f9fa4918f1a64921a5a4aef Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:14:49 +0800 Subject: [PATCH 1/6] fix[version_list]: Move file to cache This allows people to forcibly reset it to see newer version faster by clearing the cache. Plus if you don't have internet, having this isn't even important. --- .../main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java index 0a18ac0fb..3238a4ffd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java @@ -26,7 +26,7 @@ public class AsyncVersionList { public void getVersionList(@Nullable VersionDoneListener listener, boolean secondPass){ sExecutorService.execute(() -> { - File versionFile = new File(Tools.DIR_DATA + "/version_list.json"); + File versionFile = new File(Tools.DIR_CACHE + "/version_list.json"); JMinecraftVersionList versionList = null; try{ if(!versionFile.exists() || (System.currentTimeMillis() > versionFile.lastModified() + 86400000 )){ @@ -68,7 +68,7 @@ public class AsyncVersionList { // Then save the version list //TODO make it not save at times ? - FileOutputStream fos = new FileOutputStream(Tools.DIR_DATA + "/version_list.json"); + FileOutputStream fos = new FileOutputStream(Tools.DIR_CACHE + "/version_list.json"); fos.write(jsonString.getBytes()); fos.close(); From c32f459caf8fe26b7d782e056cfc8eaa700df9ff Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:21:56 +0800 Subject: [PATCH 2/6] fix[ModVersionListFragment]: Faulty taskProxy logic Code thought taskProxy was being set to null, when in reality it was actually just the value of it. Rather than just read the value of the key, I just delete the key itself because outside of installing something, we don't need it at all. This bug caused anything implementing ModVersionListFragment to disable the list after one install as the code that handles the edgecase of taskProxy being not null was being triggered but not reset post-install. The faulty code was intended to block the user from being able to run multiple install tasks at once, which wouldn't go well. --- .../fragments/ModVersionListFragment.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java index 1260cdde6..7b2952d3b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java @@ -17,7 +17,6 @@ import androidx.fragment.app.Fragment; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.extra.ExtraCore; -import net.kdt.pojavlaunch.mirrors.DownloadMirror; import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener; import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; @@ -106,7 +105,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Object forgeVersion = expandableListView.getExpandableListAdapter().getChild(i, i1); ModloaderListenerProxy taskProxy = new ModloaderListenerProxy(); Runnable downloadTask = createDownloadTask(forgeVersion, taskProxy); - setTaskProxy(taskProxy); + setTaskProxyValue(taskProxy); taskProxy.attachListener(this); mExpandableListView.setEnabled(false); new Thread(downloadTask).start(); @@ -118,7 +117,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); // Read the comment in FabricInstallFragment.onDownloadFinished() to see how this works getParentFragmentManager().popBackStackImmediate(); @@ -131,7 +130,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); Tools.dialog(context, context.getString(R.string.global_error), @@ -144,15 +143,18 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); Tools.showError(context, e); }); } - private void setTaskProxy(ModloaderListenerProxy proxy) { + private void setTaskProxyValue(ModloaderListenerProxy proxy) { ExtraCore.setValue(mExtraTag, proxy); } + private void deleteTaskProxy(){ + ExtraCore.removeValue(mExtraTag); + } private ModloaderListenerProxy getTaskProxy() { return (ModloaderListenerProxy) ExtraCore.getValue(mExtraTag); From 3d7cf8b3adb33e8dac18deb63d932bcab02848c1 Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:49:45 +0800 Subject: [PATCH 3/6] qol[ProfileType]: Add NeoForge install button I don't know how it manages to make a profile but it does --- .../fragments/NeoForgeInstallFragment.java | 80 ++++++++++++ .../fragments/ProfileTypeSelectFragment.java | 2 + .../modloaders/NeoForgeDownloadTask.java | 88 +++++++++++++ .../NeoForgeVersionListAdapter.java | 119 ++++++++++++++++++ .../main/res/layout/fragment_profile_type.xml | 8 ++ .../src/main/res/values/strings.xml | 3 + 6 files changed, 300 insertions(+) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java new file mode 100644 index 000000000..b901da6e8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java @@ -0,0 +1,80 @@ +package net.kdt.pojavlaunch.fragments; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.widget.ExpandableListAdapter; + +import androidx.annotation.NonNull; + +import net.kdt.pojavlaunch.JavaGUILauncherActivity; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.modloaders.ForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.ForgeUtils; +import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; +import net.kdt.pojavlaunch.modloaders.NeoForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.NeoForgeVersionListAdapter; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class NeoForgeInstallFragment extends ModVersionListFragment> { + public static final String TAG = "NeoForgeInstallFragment"; + public NeoForgeInstallFragment() { + super(TAG); + } + + private static final String NEOFORGE_METADATA_URL = "https://meta.prismlauncher.org/v1/net.neoforged/index.json"; + + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + } + + @Override + public int getTitleText() { + return R.string.neoforge_dl_select_version; + } + + @Override + public int getNoDataMsg() { + return R.string.neoforge_dl_no_installer; + } + + @Override + public List loadVersionList() { + String test = null; + try { + test = DownloadUtils.downloadStringCached(NEOFORGE_METADATA_URL, "neoforge_versions", input -> input); + } catch (Exception e) { + Tools.showErrorRemote(e); + } + return Collections.singletonList(test); + // Moved the parsing logic to the adapter because there is no way to get this info easily, we use prism's index + // since neoforge doesn't actually give this information easily anywhere. + // To clarify, neoforge does not provide maven APIs to get supported Minecraft versions for each loader version + + } + + @Override + public ExpandableListAdapter createAdapter(List versionList, LayoutInflater layoutInflater) { + return new NeoForgeVersionListAdapter(versionList, layoutInflater); + } + + @Override + public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) { + return new NeoForgeDownloadTask(listenerProxy, (String) selectedVersion); + } + + @Override + public void onDownloadFinished(Context context, File downloadedFile) { + Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class); + modInstallerStartIntent.putExtra("javaArgs", "-jar "+downloadedFile.getAbsolutePath()+" --install-client"); + context.startActivity(modInstallerStartIntent); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java index 9a82ccb9f..9acb9d15a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java @@ -36,6 +36,8 @@ public class ProfileTypeSelectFragment extends Fragment { tryInstall(FabricInstallFragment.class, FabricInstallFragment.TAG)); view.findViewById(R.id.modded_profile_forge).setOnClickListener((v)-> tryInstall(ForgeInstallFragment.class, ForgeInstallFragment.TAG)); + view.findViewById(R.id.modded_profile_neoforge).setOnClickListener((v)-> + tryInstall(NeoForgeInstallFragment.class, NeoForgeInstallFragment.TAG)); view.findViewById(R.id.modded_profile_modpack).setOnClickListener((v)-> tryInstall(SearchModFragment.class, SearchModFragment.TAG)); view.findViewById(R.id.modded_profile_quilt).setOnClickListener((v)-> diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java new file mode 100644 index 000000000..9cb40e683 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java @@ -0,0 +1,88 @@ +package net.kdt.pojavlaunch.modloaders; + +import com.kdt.mcgui.ProgressLayout; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +public class NeoForgeDownloadTask implements Runnable, Tools.DownloaderFeedback { + private String mDownloadUrl; + private String mFullVersion; + private String mLoaderVersion; + private String mGameVersion; + private final ModloaderDownloadListener mListener; + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String forgeVersion) { + this.mListener = listener; + this.mDownloadUrl = "https://maven.neoforged.net/releases/net/neoforged/neoforge/"+ forgeVersion +"/neoforge-"+forgeVersion+"-installer.jar"; + this.mFullVersion = forgeVersion; + } + + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String gameVersion, String loaderVersion) { + this.mListener = listener; + this.mLoaderVersion = loaderVersion; + this.mGameVersion = gameVersion; + } + @Override + public void run() { + if(determineDownloadUrl()) { + downloadForge(); + } + ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK); + } + + @Override + public void updateProgress(int curr, int max) { + int progress100 = (int)(((float)curr / (float)max)*100f); + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.forge_dl_progress, mFullVersion); + } + + private void downloadForge() { + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_progress, mFullVersion); + try { + File destinationFile = new File(Tools.DIR_CACHE, "neoforge-installer.jar"); + byte[] buffer = new byte[8192]; + DownloadUtils.downloadFileMonitored(mDownloadUrl, destinationFile, buffer, this); + mListener.onDownloadFinished(destinationFile); + }catch (FileNotFoundException e) { + mListener.onDataNotAvailable(); + } catch (IOException e) { + mListener.onDownloadError(e); + } + } + + public boolean determineDownloadUrl() { + if(mDownloadUrl != null && mFullVersion != null) return true; + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_searching); + try { + if(!findVersion()) { + mListener.onDataNotAvailable(); + return false; + } + }catch (IOException e) { + mListener.onDownloadError(e); + return false; + } + return true; + } + + public boolean findVersion() throws IOException { + List forgeVersions = ForgeUtils.downloadForgeVersions(); + if(forgeVersions == null) return false; + String versionStart = mGameVersion+"-"+mLoaderVersion; + for(String versionName : forgeVersions) { + if(!versionName.startsWith(versionStart)) continue; + mFullVersion = versionName; + mDownloadUrl = ForgeUtils.getInstallerUrl(mFullVersion); + return true; + } + return false; + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java new file mode 100644 index 000000000..e02670329 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java @@ -0,0 +1,119 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListAdapter; +import android.widget.TextView; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; + +public class NeoForgeVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter { + private final LayoutInflater mLayoutInflater; + private final LinkedHashMap> minecraftToLoaderVersionsHashmap; + private LinkedHashSet generatedHashSet = null; + + + public NeoForgeVersionListAdapter(List forgeVersions, LayoutInflater layoutInflater) { + this.mLayoutInflater = layoutInflater; + minecraftToLoaderVersionsHashmap = new LinkedHashMap<>(); + JsonArray versionsJsonArray = JsonParser.parseString(forgeVersions.get(0)).getAsJsonObject().getAsJsonArray("versions"); + + ArrayList sortedVersionsList = new ArrayList<>(); + for (JsonElement elem : versionsJsonArray) { + sortedVersionsList.add(elem); + } + Collections.sort(sortedVersionsList, (o1, o2) -> { + String versionString1 = ((JsonObject) o1).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + String versionString2 = ((JsonObject) o2).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + return versionString2.compareTo(versionString1); // Sorts by Minecraft version + }); + + for (JsonElement sortedVersionPick : sortedVersionsList) { + String loaderVersion = ((JsonObject) sortedVersionPick).get("version").getAsString(); + String minecraftVersion = ((JsonObject) sortedVersionPick).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + if (minecraftToLoaderVersionsHashmap.containsKey(minecraftVersion)) { + minecraftToLoaderVersionsHashmap.get(minecraftVersion).add(loaderVersion); + } else { + generatedHashSet = new LinkedHashSet<>(); + generatedHashSet.add(loaderVersion); + minecraftToLoaderVersionsHashmap.put(minecraftVersion, generatedHashSet); + } + } + } + @Override + public int getGroupCount() { + return minecraftToLoaderVersionsHashmap.size(); + } + + @Override + public int getChildrenCount(int i) { + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).size(); + } + + @Override + public Object getGroup(int i) { + return getGameVersion(i); + } + + @Override + public Object getChild(int i, int i1) { + return getForgeVersion(i, i1); + } + + @Override + public long getGroupId(int i) { + return i; + } + + @Override + public long getChildId(int i, int i1) { + return i1; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + + ((TextView) convertView).setText(getGameVersion(i)); + + return convertView; + } + + @Override + public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + ((TextView) convertView).setText(getForgeVersion(i, i1)); + return convertView; + } + + private String getGameVersion(int i) { + return minecraftToLoaderVersionsHashmap.keySet().toArray()[i].toString(); + } + + private String getForgeVersion(int i, int i1){ + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).toArray()[i1].toString(); + } + + @Override + public boolean isChildSelectable(int i, int i1) { + return true; + } +} diff --git a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml index 5ada83d77..5ce18cd93 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml @@ -103,6 +103,14 @@ android:layout_marginTop="@dimen/padding_large" android:text="@string/modloader_dl_install_forge" /> + + Please change accounts to use this function No Minecraft Account Found This feature requires a Microsoft account that owns Minecraft Java Edition. + Create Neoforge profile + Select NeoForge version + Sorry, but this version of NeoForge does not have an installer, which is not yet supported. From c191909dfd25b97a13417f3abd436a2387b8414a Mon Sep 17 00:00:00 2001 From: Jordan Date: Sat, 21 Jun 2025 23:23:05 -0500 Subject: [PATCH 4/6] Fix Crowdin URL (#20) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff1c9ff87..0dce6d353 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Android CI](https://github.com/AngelAuraMC/Amethyst-Android/workflows/Android%20CI/badge.svg)](https://github.com/AngelAuraMC/Amethyst-Android/actions) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/AngelAuraMC/Amethyst-Android)](https://github.com/AngelAuraMC/Amethyst-Android/actions) -[![Crowdin](https://badges.crowdin.net/amethyst/localized.svg)](https://crowdin.com/project/amethyst) +[![Crowdin](https://badges.crowdin.net/pojavlauncher/localized.svg)](https://crowdin.com/project/pojavlauncher) [![Discord](https://img.shields.io/discord/724163890803638273.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/5ptqkyZxEy) *From [Boardwalk](https://github.com/zhuowei/Boardwalk)'s ashes and [PojavLauncher](https://github.com/PojavLauncherTeam/PojavLauncher)'s ruined reputation, here comes Amethyst!* From 2016eba00f043842122d7aecb9410cf9371a7693 Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:38:01 +0800 Subject: [PATCH 5/6] fix(arc_dns_injector): Fix Java 21 CachedAddresses no longer exists, so we use CachedLookup instead since it's the same class, just renamed. This is horrible but it works so don't blame me --- .../artdeell/arcdns/ArcDNSInjectorAgent.java | 19 ++- .../git/artdeell/arcdns/CacheUtil_J21.java | 145 ++++++++++++++++++ 2 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java diff --git a/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java b/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java index f21479feb..02d6249c2 100644 --- a/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java +++ b/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java @@ -5,16 +5,23 @@ public class ArcDNSInjectorAgent { System.out.println("Arc Capes DNS Injector"); System.out.println("Parts of Alibaba's DCM library were used, please read https://github.com/alibaba/java-dns-cache-manipulator/blob/main/README.md for more info"); String[] injectedIps = new String[]{args}; + try { - CacheUtil_J9.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + CacheUtil_J21.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); } catch (Exception e) { try { - CacheUtil_J8.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + CacheUtil_J9.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); } catch (Exception e2) { - System.out.println("Failed to inject cache!"); - e2.addSuppressed(e); - e2.printStackTrace(); - return; + try { + CacheUtil_J8.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + } catch (Exception e3) { + System.out.println("Failed to inject cache!"); + e2.addSuppressed(e); + e2.printStackTrace(); + e3.addSuppressed(e); + e3.printStackTrace(); + return; + } } } System.out.println("Added DNS cache entry: s.optifine.net/"+args); diff --git a/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java b/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java new file mode 100644 index 000000000..8e2501a7a --- /dev/null +++ b/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java @@ -0,0 +1,145 @@ +package git.artdeell.arcdns; +import static git.artdeell.arcdns.CacheUtilCommons.NEVER_EXPIRATION; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListSet; +public class CacheUtil_J21 { + public static void setInetAddressCache(String host, String[] ips, long expireMillis) + throws UnknownHostException, IllegalAccessException, InstantiationException, + InvocationTargetException, ClassNotFoundException, NoSuchFieldException { + long expiration = expireMillis == NEVER_EXPIRATION ? NEVER_EXPIRATION : System.nanoTime() + expireMillis * 1_000_000; + Object cachedAddresses = newCachedAddresses(host, ips, expiration); + + getCacheOfInetAddress().put(host, cachedAddresses); + getExpirySetOfInetAddress().add(cachedAddresses); + } + + private static Object newCachedAddresses(String host, String[] ips, long expiration) + throws ClassNotFoundException, UnknownHostException, IllegalAccessException, + InvocationTargetException, InstantiationException { + // InetAddress.CachedAddresses has only one constructor + return getConstructorOfInetAddress$CachedAddresses().newInstance(host, CacheUtilCommons.toInetAddressArray(host, ips), expiration); + } + + private static volatile Constructor constructorOfInetAddress$CachedAddresses = null; + + private static Constructor getConstructorOfInetAddress$CachedAddresses() throws ClassNotFoundException { + if (constructorOfInetAddress$CachedAddresses != null) return constructorOfInetAddress$CachedAddresses; + + synchronized (CacheUtilCommons.class) { + // double check + if (constructorOfInetAddress$CachedAddresses != null) return constructorOfInetAddress$CachedAddresses; + + final Class clazz = Class.forName(inetAddress$CachedAddresses_ClassName); + + // InetAddress.CacheEntry has only one constructor: + // + // - for jdk 9-jdk12, constructor signature is CachedAddresses(String host, InetAddress[] inetAddresses, long expiryTime) + // code in jdk 9: + // https://hg.openjdk.java.net/jdk9/jdk9/jdk/file/65464a307408/src/java.base/share/classes/java/net/InetAddress.java#l783 + // code in jdk 11: + // https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/net/InetAddress.java#l787 + final Constructor constructor = clazz.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + + constructorOfInetAddress$CachedAddresses = constructor; + return constructor; + } + } + + public static void removeInetAddressCache(String host) throws NoSuchFieldException, IllegalAccessException { + getCacheOfInetAddress().remove(host); + removeHostFromExpirySetOfInetAddress(host); + } + + /** + * @see #getExpirySetOfInetAddress() + */ + private static void removeHostFromExpirySetOfInetAddress(String host) + throws NoSuchFieldException, IllegalAccessException { + for (Iterator iterator = getExpirySetOfInetAddress().iterator(); iterator.hasNext(); ) { + Object cachedAddresses = iterator.next(); + if (getHostOfInetAddress$CacheAddress(cachedAddresses).equals(host)) { + iterator.remove(); + } + } + } + + private static volatile Field hostFieldOfInetAddress$CacheAddress = null; + + private static String getHostOfInetAddress$CacheAddress(Object cachedAddresses) + throws NoSuchFieldException, IllegalAccessException { + if (hostFieldOfInetAddress$CacheAddress == null) { + synchronized (CacheUtil_J21.class) { + if (hostFieldOfInetAddress$CacheAddress == null) { // double check + final Field f = cachedAddresses.getClass().getDeclaredField("host"); + f.setAccessible(true); + hostFieldOfInetAddress$CacheAddress = f; + } + } + } + return (String) hostFieldOfInetAddress$CacheAddress.get(cachedAddresses); + } + + + ////////////////////////////////////////////////////////////////////////////// + // getters of static cache related fields of InetAddress + ////////////////////////////////////////////////////////////////////////////// + + @SuppressWarnings("unchecked") + private static ConcurrentMap getCacheOfInetAddress() + throws NoSuchFieldException, IllegalAccessException { + return (ConcurrentMap) getCacheAndExpirySetOfInetAddress0()[0]; + } + + @SuppressWarnings("unchecked") + private static ConcurrentSkipListSet getExpirySetOfInetAddress() + throws NoSuchFieldException, IllegalAccessException { + return (ConcurrentSkipListSet) getCacheAndExpirySetOfInetAddress0()[1]; + } + + private static volatile Object[] ADDRESS_CACHE_AND_EXPIRY_SET = null; + + private static Object[] getCacheAndExpirySetOfInetAddress0() + throws NoSuchFieldException, IllegalAccessException { + if (ADDRESS_CACHE_AND_EXPIRY_SET != null) return ADDRESS_CACHE_AND_EXPIRY_SET; + + synchronized (CacheUtil_J21.class) { + if (ADDRESS_CACHE_AND_EXPIRY_SET != null) return ADDRESS_CACHE_AND_EXPIRY_SET; + + final Field cacheField = InetAddress.class.getDeclaredField("cache"); + cacheField.setAccessible(true); + + final Field expirySetField = InetAddress.class.getDeclaredField("expirySet"); + expirySetField.setAccessible(true); + + ADDRESS_CACHE_AND_EXPIRY_SET = new Object[]{ + cacheField.get(InetAddress.class), + expirySetField.get(InetAddress.class) + }; + + return ADDRESS_CACHE_AND_EXPIRY_SET; + } + } + + ////////////////////////////////////////////////////////////////////////////// + + private static final String inetAddress$CachedAddresses_ClassName = "java.net.InetAddress$CachedLookup"; + public static void clearInetAddressCache() throws NoSuchFieldException, IllegalAccessException { + getCacheOfInetAddress().clear(); + getExpirySetOfInetAddress().clear(); + } + + private CacheUtil_J21() { + } + +} From 3fe93eb514e328cedc86a0ed4ac81679fb36db3f Mon Sep 17 00:00:00 2001 From: alexytomi <60690056+alexytomi@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:58:31 +0800 Subject: [PATCH 6/6] update(components): Update arc_dns_injector version Needs to be set so current installations have the faulty arc_dns_injector replaced with the fixed one shipped in the apk --- .../arc_dns_injector/arc_dns_injector.jar | Bin 9806 -> 12324 bytes .../components/arc_dns_injector/version | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar index a7565f7d7dc9f8763d048d0f648fcf4ad678afe3..1886d0f7915dcc630f23120bec81874c6f5ef2e3 100644 GIT binary patch delta 3711 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 F{2#UK+x`Fm delta 1254 zcmZ3IaL$J}z?+#xgn@yBgW(6$-if?w%o7%EnrPMlrgkcVsk>SrYBCq2A&B1GF6PL| z3uHtoJg9$Uz{$X{d-6mvwaw1Fj+}xZVONDUx7IK-FhsL4Fz8NJTm5ccv3=P7|35$HGsrENdQ>1t>XKEP;UR;c zP4%aJr~j7yocX(TLsHvB_rCW>qIp(2eXP$Ak?hGj@oER-U#>l+Vn+`tPk9q`p~Uv1 z#U&dKS^K=B3m?R(HW#+d%*v27mFqZhBX3)eZT!U(2Hl=_u1vUW*lKh5N<#&kZostH z!j237iIw}@p6m5dqgk>|p07f}z0JmO)w;{ia`*Z8Y;ALNJ8{_f@luY~i@hy-vsl(W zm}u~#zTmb+?_-5qCTIJE=a=11^{!HTe|&b=%9P)MCm&r~sZn{SylLHO&#gDhQg46R z@p$(6vQ@Wx=Wa@uRyQ)S@l@%nS28^lyEb@{X>+;6vBjHslAa&==z3FpV_Ui8JHfr4 zzj760rpM_lGR^M#cKSwP_NC8Dmv4Zao6!mn@t*6l7eO>g$(in4v1S)MBV)#38;`nW$dC#7J1V%c=-bsKsk zf@WUfknk)N-xT5E=`N{uSYrFzFmsM6L5ap^_UeBAU@}F&^0w%ea?vmQ`byF9d#@eYT|LmCKbszy90oub&j-uyEPj zM2|IpYtOFtV>&p)YHs^n55suxu7_^>pB}o$&0%DkaAn0$Pi|=&k1JUjMk&#oq@$9q z2fljFrlGUCctgYSFQLKv6%D?|E`Bb&$tOy3?P2Zp2hRzY_s&y3nZ|Yd%az{@ZxW_o z3#{p?v7d3*Y1Y33ANwy9CENO}D4lh!{>%CqwezAfuNHs){&~k&;k^Ba?jBpZ@eh}= zFKet~M&{P`t3mwl=UzUiA5s5LO8$x6o5bch(zZ5ACoDT>9haT;+*N1dBh#qh&NI3` zDwm8@DukpKx^bGj{?kz`J@kyj*6pxqA6KHhaF(>W=2V+~$4VyM<664GZ^!u!?#uNa zntii->3j8+#Qep6uWeNCIZU2Z=M~1dl3PjQlELbS3_gb+u?8;vWfpnzntX}UVR28# z`F~jv1p_G4+cBQv=4E6OVTP9#lU;R9z~w@Xu05FEr>hU9Kj>P3X>C1-xTL=H z$)Wn93Mj>fFav`c0|G2*EajT)%P2pY5vmZSAVH{OSkm}XcygD%HQ1J1J!z%~;y`^m XVgcT)z#@l%ffERunHU(hDuZ|coNg`9 diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version index 9ce6c430b..f32989695 100644 --- a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version +++ b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version @@ -1 +1 @@ -53f2f037cef9b7447a6abbdf82150ca8a2f2a587 \ No newline at end of file +2016eba00f043842122d7aecb9410cf9371a7693 \ No newline at end of file