Skip to content

Merge dev in master #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.15.0'

implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.8.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.4'
implementation 'androidx.navigation:navigation-ui-ktx:2.8.4'
implementation 'androidx.preference:preference-ktx:1.2.1'

implementation 'androidx.browser:browser:1.8.0'
Expand Down
93 changes: 93 additions & 0 deletions app/src/main/java/cc/calliope/mini/FirmwareZipCreator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cc.calliope.mini;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class FirmwareZipCreator {
private static final String TAG = "FirmwareZipCreator";

private final Context context;
private final String firmwarePath;
private final String initPacketPath;

public FirmwareZipCreator(Context context, String firmwarePath, String initPacketPath) {
this.context = context;
this.firmwarePath = firmwarePath;
this.initPacketPath = initPacketPath;
}

public String createZip() {
String zipFilePath = context.getCacheDir() + "/update.zip";

File zipFile = initializeZipFile(zipFilePath);
if (zipFile == null) {
return null;
}

if (!addFilesToZip(zipFile, firmwarePath, initPacketPath)) {
return null;
}

return zipFilePath;
}

private File initializeZipFile(String path) {
File zipFile = new File(path);
try {
if (zipFile.exists() && !zipFile.delete()) {
Log.e(TAG, "Failed to delete existing file: " + path);
return null;
}

if (!zipFile.createNewFile()) {
Log.e(TAG, "Failed to create new file: " + path);
return null;
}
} catch (IOException e) {
Log.e(TAG, "Error initializing zip file", e);
return null;
}

return zipFile;
}

private boolean addFilesToZip(File zipFile, String... srcFiles) {
byte[] buffer = new byte[1024];

try (FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) {

for (String file : srcFiles) {
File srcFile = new File(file);

if (!srcFile.exists()) {
Log.e(TAG, "Source file does not exist: " + file);
continue;
}

try (FileInputStream fileInputStream = new FileInputStream(srcFile)) {
zipOutputStream.putNextEntry(new ZipEntry(srcFile.getName()));

int length;
while ((length = fileInputStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, length);
}

zipOutputStream.closeEntry();
}
}

return true;
} catch (IOException e) {
Log.e(TAG, "Error adding files to zip", e);
return false;
}
}
}
152 changes: 85 additions & 67 deletions app/src/main/java/cc/calliope/mini/HexParser.kt
Original file line number Diff line number Diff line change
@@ -1,80 +1,111 @@
package cc.calliope.mini

import cc.calliope.mini.utils.Constants.MINI_V2
import cc.calliope.mini.utils.Constants.MINI_V3
import java.io.File

class HexParser(private val path: String) {
private fun calculateChecksum(address: UShort, type: Int, data: ByteArray): UByte {
var crc: UInt = data.size.toUByte() +
(address.toUInt() shr 8).toUByte() +
((address.toUInt() and 0xFFu) + type.toUInt()).toUByte()
data class Partition(val address: UInt, val data: MutableList<Byte>)

private fun calculateChecksum(address: UInt, type: Int, data: ByteArray): Int {
var crc = data.size and 0xFF

crc = (crc + ((address.toInt() shr 8) and 0xFF)) and 0xFF
crc = (crc + (address.toInt() and 0xFF)) and 0xFF
crc = (crc + type) and 0xFF

for (b in data) {
crc = (crc + b.toUByte()).toUByte().toUInt()
crc = (crc + (b.toInt() and 0xFF)) and 0xFF
}
return ((0x100u - crc) and 0xFFu).toUByte()

crc = (0x100 - crc) and 0xFF
return crc
}

fun parse(handleDataEntry: (Long, ByteArray, Int, Boolean) -> Unit) {
val file = File(path)
val reader = file.bufferedReader()
fun getCalliopeBin(version: Int): ByteArray {
val (addressRange, dataTypeCondition) = when (version) {
MINI_V2 -> 0x18000u..0x3BFFFu to { dataType: Int -> dataType == 1 }
MINI_V3 -> 0x1C000u..0x72FFFu to { dataType: Int -> dataType == 2 }
else -> throw IllegalArgumentException("Unsupported version: $version")
}

var isUniversal = false
var addressHi: UInt = 0u
var dataType = 0
val partitions = collectPartitions(addressRange, dataTypeCondition)

reader.useLines { lines ->
lines.forEach { line ->
var beginIndex = 0
var endIndex = 1
return buildPaddedBin(partitions)
}

if (line.isEmpty() || line[beginIndex] != ':') return@forEach
beginIndex = endIndex
private fun collectPartitions(
addressRange: UIntRange,
dataTypeCondition: (Int) -> Boolean
): List<Partition> {
val partitions = mutableListOf<Partition>()
var lastAddress: UInt? = null

endIndex = beginIndex + 2
val length = line.substring(beginIndex, endIndex).toInt(16).toUByte()
beginIndex = endIndex
parse { address, data, dataType, isUniversal ->
if (address in addressRange && (dataTypeCondition(dataType) || !isUniversal)) {
if (lastAddress == null || lastAddress!! > address || lastAddress!! + 16u < address) {
partitions.add(Partition(address, mutableListOf()))
}
partitions.lastOrNull()?.data?.addAll(data.asList())
lastAddress = address
}
}

endIndex = beginIndex + 4
val addressLo = line.substring(beginIndex, endIndex).toUInt(16)
beginIndex = endIndex
return partitions.sortedBy { it.address }
}

endIndex = beginIndex + 2
val type = line.substring(beginIndex, endIndex).toInt(16)
beginIndex = endIndex
private fun buildPaddedBin(partitions: List<Partition>): ByteArray {
val paddedApplication = mutableListOf<Byte>()
partitions.zipWithNext { current, next ->
paddedApplication.addAll(current.data)
val paddingSize = (next.address - (current.address + current.data.size.toUInt())).toInt()
if (paddingSize > 0) {
paddedApplication.addAll(ByteArray(paddingSize) { 0xFF.toByte() }.toList())
}
}

endIndex = beginIndex + 2 * length.toInt()
if (endIndex > line.length) return@forEach
val payload = line.substring(beginIndex, endIndex)
beginIndex = endIndex
if (partitions.isNotEmpty()) {
paddedApplication.addAll(partitions.last().data)
}

endIndex = beginIndex + 2
if (endIndex > line.length) return@forEach
val checksum = line.substring(beginIndex, endIndex).toUIntOrNull(16)?.toUByte() ?: return@forEach
val paddingSize = (4 - (paddedApplication.size % 4)) % 4
paddedApplication.addAll(ByteArray(paddingSize) { 0xFF.toByte() }.toList())

val data = payload.hexStringToByteArray()
val calculatedChecksum = calculateChecksum(addressLo.toUShort(), type, data)
if (checksum != calculatedChecksum) {
return@forEach
}
return paddedApplication.toByteArray()
}

private fun parse(handleDataEntry: (UInt, ByteArray, Int, Boolean) -> Unit) {
val file = File(path)
val reader = file.bufferedReader()
var isUniversal = false
var addressHi = 0u
var dataType = 0

reader.useLines { lines ->
lines.forEach { line ->
if (line.firstOrNull() != ':') return@forEach
val length = line.substring(1, 3).toIntOrNull(16) ?: return@forEach
val addressLo = line.substring(3, 7).toUIntOrNull(16) ?: return@forEach
val type = line.substring(7, 9).toIntOrNull(16) ?: return@forEach
val payload = line.substring(9, 9 + 2 * length)

// Calculate checksum
val checksum = line.substring(9 + 2 * length, 9 + 2 * length + 2).toIntOrNull(16) ?: return@forEach
val calculatedChecksum = calculateChecksum(addressHi + addressLo.toUShort(), type, payload.chunked(2).map { it.toInt(16).toByte() }.toByteArray())
if (checksum != calculatedChecksum) return@forEach

when (type) {
0, 13 -> { // Data
0, 13 -> {
val position = addressHi + addressLo
if (data.size == length.toInt()) {
handleDataEntry(position.toLong(), data, dataType, isUniversal)
val data = payload.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
if (data.size == length) {
handleDataEntry(position, data, dataType, isUniversal)
}
}
1 -> return // EOF
2 -> { // EXT SEGMENT ADDRESS
val segment = payload.toUInt(16)
addressHi = segment shl 4
}
3 -> { /* START SEGMENT ADDRESS */ }
4 -> { // EXT LINEAR ADDRESS
val segment = payload.toUInt(16)
addressHi = segment shl 16
}
5 -> { /* START LINEAR ADDRESS */ }
10 -> { // Block Start Address
1 -> return
2 -> addressHi = payload.toUIntOrNull(16)?.shl(4) ?: 0u
4 -> addressHi = payload.toUIntOrNull(16)?.shl(16) ?: 0u
10 -> {
isUniversal = true
val dataTypeField = line.substring(9, 13)
dataType = when (dataTypeField) {
Expand All @@ -83,21 +114,8 @@ class HexParser(private val path: String) {
else -> dataType
}
}
else -> { /* OTHER */ }
}
}
}
}
}

fun String.hexStringToByteArray(): ByteArray {
val len = this.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
val byte = this.substring(i, i + 2).toInt(16).toByte()
data[i / 2] = byte
i += 2
}
return data
}
38 changes: 0 additions & 38 deletions app/src/main/java/cc/calliope/mini/InitPacket.kt

This file was deleted.

Loading
Loading