Android: Add MemoryCardImage class

This commit is contained in:
Connor McLaughlin
2021-03-21 00:40:03 +10:00
parent 4dec0dee2f
commit 6d4a3bb5a5
4 changed files with 535 additions and 6 deletions

View File

@ -0,0 +1,64 @@
package com.github.stenzek.duckstation;
import android.graphics.Bitmap;
import java.nio.ByteBuffer;
public class MemoryCardFileInfo {
public static final int ICON_WIDTH = 16;
public static final int ICON_HEIGHT = 16;
private final String filename;
private final String title;
private final int size;
private final int firstBlock;
private final int numBlocks;
private final byte[][] iconFrames;
public MemoryCardFileInfo(String filename, String title, int size, int firstBlock, int numBlocks, byte[][] iconFrames) {
this.filename = filename;
this.title = title;
this.size = size;
this.firstBlock = firstBlock;
this.numBlocks = numBlocks;
this.iconFrames = iconFrames;
}
public String getFilename() {
return filename;
}
public String getTitle() {
return title;
}
public int getSize() {
return size;
}
public int getFirstBlock() {
return firstBlock;
}
public int getNumBlocks() {
return numBlocks;
}
public int getNumIconFrames() {
return (iconFrames != null) ? iconFrames.length : 0;
}
public byte[] getIconFrame(int index) {
return iconFrames[index];
}
public Bitmap getIconFrameBitmap(int index) {
final byte[] data = getIconFrame(index);
if (data == null)
return null;
final Bitmap bitmap = Bitmap.createBitmap(ICON_WIDTH, ICON_HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data));
return bitmap;
}
}

View File

@ -0,0 +1,203 @@
package com.github.stenzek.duckstation;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
public class MemoryCardImage {
public static final int DATA_SIZE = 128 * 1024;
public static final String FILE_EXTENSION = ".mcd";
private final Context context;
private final Uri uri;
private final byte[] data;
private MemoryCardImage(Context context, Uri uri, byte[] data) {
this.context = context;
this.uri = uri;
this.data = data;
}
public static String getTitleForUri(Uri uri) {
String name = uri.getLastPathSegment();
if (name != null) {
final int lastSlash = name.lastIndexOf('/');
if (lastSlash >= 0)
name = name.substring(lastSlash + 1);
if (name.endsWith(FILE_EXTENSION))
name = name.substring(0, name.length() - FILE_EXTENSION.length());
} else {
name = uri.toString();
}
return name;
}
public static MemoryCardImage open(Context context, Uri uri) {
byte[] data = FileUtil.readBytesFromUri(context, uri, DATA_SIZE);
if (data == null)
return null;
if (!isValid(data))
return null;
return new MemoryCardImage(context, uri, data);
}
public static MemoryCardImage create(Context context, Uri uri) {
byte[] data = new byte[DATA_SIZE];
format(data);
MemoryCardImage card = new MemoryCardImage(context, uri, data);
if (!card.save())
return null;
return card;
}
public static Uri[] getCardUris(Context context) {
final String directory = "/sdcard/duckstation/memcards";
final ArrayList<Uri> results = new ArrayList<>();
if (directory.charAt(0) == '/') {
// native path
final File directoryFile = new File(directory);
final File[] files = directoryFile.listFiles();
for (File file : files) {
if (!file.isFile())
continue;
if (!file.getName().endsWith(FILE_EXTENSION))
continue;
results.add(Uri.fromFile(file));
}
} else {
try {
final Uri baseUri = null;
final String[] scanProjection = new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE};
final ContentResolver resolver = context.getContentResolver();
final String treeDocId = DocumentsContract.getTreeDocumentId(baseUri);
final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(baseUri, treeDocId);
final Cursor cursor = resolver.query(queryUri, scanProjection, null, null, null);
while (cursor.moveToNext()) {
try {
final String mimeType = cursor.getString(2);
final String documentId = cursor.getString(0);
final Uri uri = DocumentsContract.buildDocumentUriUsingTree(baseUri, documentId);
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
continue;
}
final String uriString = uri.toString();
if (!uriString.endsWith(FILE_EXTENSION))
continue;
results.add(uri);
} catch (Exception e) {
e.printStackTrace();
}
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (results.isEmpty())
return null;
Collections.sort(results, (a, b) -> a.compareTo(b));
final Uri[] resultArray = new Uri[results.size()];
results.toArray(resultArray);
return resultArray;
}
private static native boolean isValid(byte[] data);
private static native void format(byte[] data);
private static native int getFreeBlocks(byte[] data);
private static native MemoryCardFileInfo[] getFiles(byte[] data);
private static native boolean hasFile(byte[] data, String filename);
private static native byte[] readFile(byte[] data, String filename);
private static native boolean writeFile(byte[] data, String filename, byte[] fileData);
private static native boolean deleteFile(byte[] data, String filename);
private static native boolean importCard(byte[] data, String filename, byte[] importData);
public boolean save() {
return FileUtil.writeBytesToUri(context, uri, data);
}
public boolean delete() {
return FileUtil.deleteFileAtUri(context, uri);
}
public boolean format() {
format(data);
return save();
}
public Uri getUri() {
return uri;
}
public String getTitle() {
return getTitleForUri(uri);
}
public int getFreeBlocks() {
return getFreeBlocks(data);
}
public MemoryCardFileInfo[] getFiles() {
return getFiles(data);
}
public boolean hasFile(String filename) {
return hasFile(data, filename);
}
public byte[] readFile(String filename) {
return readFile(data, filename);
}
public boolean writeFile(String filename, byte[] fileData) {
if (!writeFile(data, filename, fileData))
return false;
return save();
}
public boolean deleteFile(String filename) {
if (!deleteFile(data, filename))
return false;
return save();
}
public boolean importCard(String filename, byte[] importData) {
if (!importCard(data, filename, importData))
return false;
return save();
}
}