+ * Copyright (c) 2014 Kenji Sasaki + * Released under the MIT license. + * https://github.com/npedotnet/TGAReader/blob/master/LICENSE + *
+ * English document + * https://github.com/npedotnet/TGAReader/blob/master/README.md + *
+ * Japanese document
+ * http://3dtech.jp/wiki/index.php?TGAReader
+ */
+
+package su.xash.engine.util;
+
+import java.io.IOException;
+
+public final class TGAReader {
+
+ public static final Order ARGB = new Order(16, 8, 0, 24);
+ public static final Order ABGR = new Order(0, 8, 16, 24);
+
+ public static int getWidth(byte[] buffer) {
+ return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8;
+ }
+
+ public static int getHeight(byte[] buffer) {
+ return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8;
+ }
+
+ public static int[] read(byte[] buffer, Order order) throws IOException {
+
+ // header
+// int idFieldLength = buffer[0] & 0xFF;
+// int colormapType = buffer[1] & 0xFF;
+ int type = buffer[2] & 0xFF;
+ int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8;
+ int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8;
+ int colormapDepth = buffer[7] & 0xFF;
+// int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported
+// int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported
+ int width = getWidth(buffer);
+ int height = getHeight(buffer);
+ int depth = buffer[16] & 0xFF;
+ int descriptor = buffer[17] & 0xFF;
+
+ int[] pixels;
+
+ // data
+ switch (type) {
+ case COLORMAP: {
+ int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
+ pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order);
+ }
+ break;
+ case RGB:
+ pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order);
+ break;
+ case GRAYSCALE:
+ pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order);
+ break;
+ case COLORMAP_RLE: {
+ int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
+ byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset);
+ pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order);
+ }
+ break;
+ case RGB_RLE: {
+ byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
+ pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order);
+ }
+ break;
+ case GRAYSCALE_RLE: {
+ byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
+ pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order);
+ }
+ break;
+ default:
+ throw new IOException("Unsupported image type: " + type);
+ }
+
+ return pixels;
+
+ }
+
+ private static final int COLORMAP = 1;
+ private static final int RGB = 2;
+ private static final int GRAYSCALE = 3;
+ private static final int COLORMAP_RLE = 9;
+ private static final int RGB_RLE = 10;
+ private static final int GRAYSCALE_RLE = 11;
+
+ private static final int RIGHT_ORIGIN = 0x10;
+ private static final int UPPER_ORIGIN = 0x20;
+
+ private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) {
+ int elementCount = depth / 8;
+ byte[] elements = new byte[elementCount];
+ int decodeBufferLength = elementCount * width * height;
+ byte[] decodeBuffer = new byte[decodeBufferLength];
+ int decoded = 0;
+ while (decoded < decodeBufferLength) {
+ int packet = buffer[offset++] & 0xFF;
+ if ((packet & 0x80) != 0) { // RLE
+ for (int i = 0; i < elementCount; i++) {
+ elements[i] = buffer[offset++];
+ }
+ int count = (packet & 0x7F) + 1;
+ for (int i = 0; i < count; i++) {
+ for (int j = 0; j < elementCount; j++) {
+ decodeBuffer[decoded++] = elements[j];
+ }
+ }
+ } else { // RAW
+ int count = (packet + 1) * elementCount;
+ for (int i = 0; i < count; i++) {
+ decodeBuffer[decoded++] = buffer[offset++];
+ }
+ }
+ }
+ return decodeBuffer;
+ }
+
+ private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException {
+ int[] pixels;
+ int rs = order.redShift;
+ int gs = order.greenShift;
+ int bs = order.blueShift;
+ int as = order.alphaShift;
+ switch (depth) {
+ case 24:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 3 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * i + (width - j - 1)] = color;
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 3 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * (height - i - 1) + (width - j - 1)] = color;
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 3 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * i + j] = color;
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 3 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * (height - i - 1) + j] = color;
+ }
+ }
+ }
+ }
+ break;
+ case 32:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 4 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = palette[index + 3] & 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * i + (width - j - 1)] = color;
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 4 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = palette[index + 3] & 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * (height - i - 1) + (width - j - 1)] = color;
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 4 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = palette[index + 3] & 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * i + j] = color;
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int colormapIndex = bytes[offset + width * i + j] &
+ 0xFF - colormapOrigin;
+ int color = 0xFFFFFFFF;
+ if (colormapIndex >= 0) {
+ int index = 4 * colormapIndex + 18;
+ int b = palette[index] & 0xFF;
+ int g = palette[index + 1] & 0xFF;
+ int r = palette[index + 2] & 0xFF;
+ int a = palette[index + 3] & 0xFF;
+ color = (r << rs) | (g << gs) | (b << bs) | (a << as);
+ }
+ pixels[width * (height - i - 1) + j] = color;
+ }
+ }
+ }
+ }
+ break;
+ default:
+ throw new IOException("Unsupported depth:" + depth);
+ }
+ return pixels;
+ }
+
+ private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
+ int[] pixels;
+ int rs = order.redShift;
+ int gs = order.greenShift;
+ int bs = order.blueShift;
+ int as = order.alphaShift;
+ switch (depth) {
+ case 24:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 3 * width * i + 3 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = 0xFF;
+ pixels[width * i + (width - j - 1)] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 3 * width * i + 3 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = 0xFF;
+ pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 3 * width * i + 3 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = 0xFF;
+ pixels[width * i + j] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 3 * width * i + 3 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = 0xFF;
+ pixels[width * (height - i - 1) + j] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ }
+ }
+ break;
+ case 32:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 4 * width * i + 4 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = bytes[index + 3] & 0xFF;
+ pixels[width * i + (width - j - 1)] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 4 * width * i + 4 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = bytes[index + 3] & 0xFF;
+ pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 4 * width * i + 4 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = bytes[index + 3] & 0xFF;
+ pixels[width * i + j] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int index = offset + 4 * width * i + 4 * j;
+ int b = bytes[index] & 0xFF;
+ int g = bytes[index + 1] & 0xFF;
+ int r = bytes[index + 2] & 0xFF;
+ int a = bytes[index + 3] & 0xFF;
+ pixels[width * (height - i - 1) + j] = (r << rs) |
+ (g << gs) |
+ (b << bs) |
+ (a << as);
+ }
+ }
+ }
+ }
+ break;
+ default:
+ throw new IOException("Unsupported depth:" + depth);
+ }
+ return pixels;
+ }
+
+ private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
+ int[] pixels;
+ int rs = order.redShift;
+ int gs = order.greenShift;
+ int bs = order.blueShift;
+ int as = order.alphaShift;
+ switch (depth) {
+ case 8:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + width * i + j] & 0xFF;
+ int a = 0xFF;
+ pixels[width * i + (width - j - 1)] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + width * i + j] & 0xFF;
+ int a = 0xFF;
+ pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + width * i + j] & 0xFF;
+ int a = 0xFF;
+ pixels[width * i + j] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + width * i + j] & 0xFF;
+ int a = 0xFF;
+ pixels[width * (height - i - 1) + j] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ }
+ }
+ break;
+ case 16:
+ pixels = new int[width * height];
+ if ((descriptor & RIGHT_ORIGIN) != 0) {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
+ int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
+ pixels[width * i + (width - j - 1)] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerRight
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
+ int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
+ pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ }
+ } else {
+ if ((descriptor & UPPER_ORIGIN) != 0) {
+ // UpperLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
+ int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
+ pixels[width * i + j] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ } else {
+ // LowerLeft
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
+ int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
+ pixels[width * (height - i - 1) + j] = (e << rs) |
+ (e << gs) |
+ (e << bs) |
+ (a << as);
+ }
+ }
+ }
+ }
+ break;
+ default:
+ throw new IOException("Unsupported depth:" + depth);
+ }
+ return pixels;
+ }
+
+ private TGAReader() {
+ }
+
+ public static final class Order {
+ Order(int redShift, int greenShift, int blueShift, int alphaShift) {
+ this.redShift = redShift;
+ this.greenShift = greenShift;
+ this.blueShift = blueShift;
+ this.alphaShift = alphaShift;
+ }
+
+ public int redShift;
+ public int greenShift;
+ public int blueShift;
+ public int alphaShift;
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/su/xash/engine/workers/FileCopyWorker.kt b/android/app/src/main/java/su/xash/engine/workers/FileCopyWorker.kt
new file mode 100644
index 00000000..7627cb64
--- /dev/null
+++ b/android/app/src/main/java/su/xash/engine/workers/FileCopyWorker.kt
@@ -0,0 +1,58 @@
+package su.xash.engine.workers
+
+import android.content.Context
+import android.net.Uri
+import android.provider.DocumentsContract
+import android.util.Log
+import androidx.documentfile.provider.DocumentFile
+import androidx.work.CoroutineWorker
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import su.xash.engine.model.Game
+import java.io.FileInputStream
+
+const val KEY_FILE_URI = "KEY_FILE_URI"
+
+class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
+ companion object {
+ const val Input = "Input"
+ }
+
+ override suspend fun doWork(): Result {
+ withContext(Dispatchers.IO) {
+ val fileUri = inputData.getString(KEY_FILE_URI)
+ setProgress(workDataOf(Input to fileUri))
+
+ val target = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
+ val source = DocumentFile.fromTreeUri(applicationContext, Uri.parse(fileUri))
+
+ source?.copyDirTo(applicationContext, target) ?: return@withContext Result.failure()
+ }
+ return Result.success()
+ }
+}
+
+fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
+ val outFile = file.createFile("application", name!!)!!
+
+ ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
+ ctx.contentResolver.openInputStream(uri).use {
+ it?.copyTo(os!!)
+ }
+ }
+}
+
+fun DocumentFile.copyDirTo(ctx: Context, dir: DocumentFile) {
+ val outDir = dir.createDirectory(name!!)!!
+
+ listFiles().forEach {
+ if (it.isDirectory) {
+ it.copyDirTo(ctx, outDir)
+ } else {
+ it.copyFileTo(ctx, outDir)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_baseline_add_24.xml b/android/app/src/main/res/drawable/ic_baseline_add_24.xml
new file mode 100644
index 00000000..0553ae30
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_baseline_add_24.xml
@@ -0,0 +1,10 @@
+