Skip to content

Commit

Permalink
Feat[mc_downloader]: native library downloading and extraction
Browse files Browse the repository at this point in the history
Currently implemented only for JNA
  • Loading branch information
artdeell committed Feb 14, 2025
1 parent 92bde83 commit d713fe5
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,14 @@ public static void launchMinecraft(final AppCompatActivity activity, MinecraftAc
}
javaArgList.add("-Dlog4j.configurationFile=" + configFile);
}

File versionSpecificNativesDir = new File(Tools.DIR_CACHE, "natives/"+versionId);
if(versionSpecificNativesDir.exists()) {
String dirPath = versionSpecificNativesDir.getAbsolutePath();
javaArgList.add("-Djava.library.path="+dirPath+":"+Tools.NATIVE_LIB_DIR);
javaArgList.add("-Djna.boot.library.path="+dirPath);
}

javaArgList.addAll(Arrays.asList(getMinecraftJVMArgs(versionId, gamedir)));
javaArgList.add("-cp");
javaArgList.add(launchClassPath + ":" + getLWJGL3ClassPath());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@
public class MinecraftDownloader {
private static final double ONE_MEGABYTE = (1024d * 1024d);
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
private static final String MAVEN_CENTRAL_REPO1 = "https://repo1.maven.org/maven2/";
private AtomicReference<Exception> mDownloaderThreadException;
private ArrayList<DownloaderTask> mScheduledDownloadTasks;
private ArrayList<File> mDeclaredNatives;
private AtomicLong mProcessedFileCounter;
private AtomicLong mProcessedSizeCounter; // Total bytes of processed files (passed SHA1 or downloaded)
private AtomicLong mInternetUsageCounter; // How many bytes downloaded over Internet
Expand Down Expand Up @@ -88,6 +90,7 @@ private void downloadGame(Activity activity, JMinecraftVersionList.Version verIn

mTargetJarFile = createGameJarPath(versionName);
mScheduledDownloadTasks = new ArrayList<>();
mDeclaredNatives = new ArrayList<>();
mProcessedFileCounter = new AtomicLong(0);
mProcessedSizeCounter = new AtomicLong(0);
mInternetUsageCounter = new AtomicLong(0);
Expand Down Expand Up @@ -120,6 +123,7 @@ private void downloadGame(Activity activity, JMinecraftVersionList.Version verIn
throw thrownException;
} else {
ensureJarFileCopy();
extractNatives(versionName);
}
}catch (InterruptedException e) {
// Interrupted while waiting, which means that the download was cancelled.
Expand Down Expand Up @@ -167,6 +171,25 @@ private void ensureJarFileCopy() throws IOException {
org.apache.commons.io.FileUtils.copyFile(mSourceJarFile, mTargetJarFile, false);
}

private void extractNatives(String versionName) throws IOException {
if(mDeclaredNatives.isEmpty()) return;
int totalCount = mDeclaredNatives.size();

ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0,
R.string.newdl_extracting_native_libraries, 0, totalCount);

File targetDirectory = new File(Tools.DIR_CACHE, "natives/"+versionName);
FileUtils.ensureDirectory(targetDirectory);
NativesExtractor nativesExtractor = new NativesExtractor(targetDirectory);
int extractedCount = 0;
for(File source : mDeclaredNatives) {
nativesExtractor.extractFromAar(source);
extractedCount++;
ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, extractedCount * 100 / totalCount,
R.string.newdl_extracting_native_libraries, extractedCount, totalCount);
}
}

private File downloadGameJson(JMinecraftVersionList.Version verInfo) throws IOException, MirrorTamperedException {
File targetFile = createGameJsonPath(verInfo.id);
if(verInfo.sha1 == null && targetFile.canRead() && targetFile.isFile())
Expand Down Expand Up @@ -274,13 +297,31 @@ private void scheduleDownload(File targetFile, int downloadClass, String url, St
);
}

/**
* Schedule the download of an AAR library containing the required natives, for later extraction
* and adding to the library path.
* @param baseRepository the source Maven repository to download from.
* @param dependentLibrary the DependentLibrary to get the path from
* @throws IOException in case if download scheduling fails.
*/
private void scheduleNativeLibraryDownload(String baseRepository, DependentLibrary dependentLibrary) throws IOException {
String path = FileUtils.removeExtension(Tools.artifactToPath(dependentLibrary)) + ".aar";
String downloadUrl = baseRepository + path;
File targetPath = new File(Tools.DIR_HOME_LIBRARY, path);
mDeclaredNatives.add(targetPath);
scheduleDownload(targetPath, DownloadMirror.DOWNLOAD_CLASS_LIBRARIES, downloadUrl, null, 0, true);
}

private void scheduleLibraryDownloads(DependentLibrary[] dependentLibraries) throws IOException {
Tools.preProcessLibraries(dependentLibraries);
growDownloadList(dependentLibraries.length);
for(DependentLibrary dependentLibrary : dependentLibraries) {
// Don't download lwjgl, we have our own bundled in.
if(dependentLibrary.name.startsWith("org.lwjgl")) continue;

// Special handling for JNA Android natives
if(dependentLibrary.name.startsWith("net.java.dev.jna:jna:")) {
scheduleNativeLibraryDownload(MAVEN_CENTRAL_REPO1, dependentLibrary);
}
String libArtifactPath = Tools.artifactToPath(dependentLibrary);
String sha1 = null, url = null;
long size = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package net.kdt.pojavlaunch.tasks;

import net.kdt.pojavlaunch.Architecture;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.utils.FileUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class NativesExtractor {
private static final ArrayList<String> LIBRARY_BLACKLIST = createLibraryBlacklist();
private final File mDestinationDir;
private final String mLibraryLocation;

public NativesExtractor(File mDestinationDir) {
this.mDestinationDir = mDestinationDir;
this.mLibraryLocation = "jni/"+getAarArchitectureName()+"/";
}

/**
* Create a library blacklist so that downloaded natives are not able to
* override built-in libraries.
* @return the resulting blacklist of library file names
*/
private static ArrayList<String> createLibraryBlacklist() {
String[] includedLibraryNames = new File(Tools.NATIVE_LIB_DIR).list();
ArrayList<String> blacklist = new ArrayList<>(includedLibraryNames.length);
for(String libraryName : includedLibraryNames) {
// allow overriding jnidispatch (as the integrated version may be too old)
if(libraryName.equals("libjnidispatch.so")) continue;
blacklist.add(libraryName);
}
blacklist.trimToSize();
return blacklist;
}

private static String getAarArchitectureName() {
switch (Architecture.getDeviceArchitecture()) {
case Architecture.ARCH_ARM:
return "armeabi-v7a";
case Architecture.ARCH_ARM64:
return "arm64-v8a";
case Architecture.ARCH_X86:
return "x86";
case Architecture.ARCH_X86_64:
return "x86_64";
}
throw new RuntimeException("Unknown CPU architecture!");
}

public void extractFromAar(File source) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(source)) {
try(ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) {
// Wrap the ZIP input stream into a non-closeable stream to
// avoid it being closed by processEntry()
NonCloseableInputStream entryCopyStream = new NonCloseableInputStream(zipInputStream);

while(true) {
ZipEntry entry = zipInputStream.getNextEntry();
if(entry == null) break;

String entryName = entry.getName();
if(!entryName.startsWith(mLibraryLocation) || entry.isDirectory()) continue;
// Entry name is actually the full path, so we need to strip the path before extraction
entryName = FileUtils.getFileName(entryName);
// getFileName may make the file name null, avoid that case.
if(entryName == null || LIBRARY_BLACKLIST.contains(entryName)) continue;

processEntry(entryCopyStream, entry, new File(mDestinationDir, entryName));
}
}
}
}

private static long fileCrc32(File target) throws IOException {
try(FileInputStream fileInputStream = new FileInputStream(target)) {
CRC32 crc32 = new CRC32();
byte[] buffer = new byte[1024];
int len;
while((len = fileInputStream.read(buffer)) != -1) {
crc32.update(buffer, 0, len);
}
return crc32.getValue();
}
}

private void processEntry(InputStream sourceStream, ZipEntry zipEntry, File entryDestination) throws IOException {
if(entryDestination.exists()) {
long expectedSize = zipEntry.getSize();
long expectedCrc32 = zipEntry.getCrc();
long realSize = entryDestination.length();
long realCrc32 = fileCrc32(entryDestination);
// File in archive is the same as the local one, don't extract
if(realSize == expectedSize && realCrc32 == expectedCrc32) return;
}
// copyInputStreamToFile copies the stream to a file and then closes it.
org.apache.commons.io.FileUtils.copyInputStreamToFile(sourceStream, entryDestination);
}


private static class NonCloseableInputStream extends FilterInputStream {

protected NonCloseableInputStream(InputStream in) {
super(in);
}

@Override
public void close() {
// Do nothing (the point of this class)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public static String getFileName(String pathOrUrl) {
return pathOrUrl.substring(lastSlashIndex);
}

/**
* Remove the extension (all text after the last dot) from a path/URL string.
* @param pathOrUrl the path or the URL of the file
* @return the input with the extension removed
*/
public static String removeExtension(String pathOrUrl) {
int lastDotIndex = pathOrUrl.lastIndexOf('.');
if(lastDotIndex == -1) return pathOrUrl;
return pathOrUrl.substring(0, lastDotIndex);
}

/**
* Ensure that a directory exists, is a directory and is writable.
* @param targetFile the directory to check
Expand Down
1 change: 1 addition & 0 deletions app_pojavlauncher/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -429,4 +429,5 @@
<string name="bta_installer_available_versions">Supported BTA versions</string>
<string name="bta_installer_untested_versions">Untested BTA versions</string>
<string name="bta_installer_nightly_versions">Nightly BTA versions</string>
<string name="newdl_extracting_native_libraries">Extracting native libraries (%d/%d)</string>
</resources>

0 comments on commit d713fe5

Please sign in to comment.