Skip to content

Commit 2187625

Browse files
authored
Merge pull request #37 from calliope-edu/dev
Merge dev in master
2 parents 0ecaebd + 0aa4c62 commit 2187625

29 files changed

+1195
-791
lines changed

app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ dependencies {
4343
implementation 'org.apache.commons:commons-lang3:3.15.0'
4444

4545
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
46-
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.3'
47-
implementation 'androidx.navigation:navigation-ui-ktx:2.8.3'
46+
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.4'
47+
implementation 'androidx.navigation:navigation-ui-ktx:2.8.4'
4848
implementation 'androidx.preference:preference-ktx:1.2.1'
4949

5050
implementation 'androidx.browser:browser:1.8.0'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package cc.calliope.mini;
2+
3+
import android.content.Context;
4+
import android.util.Log;
5+
6+
import java.io.File;
7+
import java.io.FileInputStream;
8+
import java.io.FileOutputStream;
9+
import java.io.IOException;
10+
import java.util.zip.ZipEntry;
11+
import java.util.zip.ZipOutputStream;
12+
13+
public class FirmwareZipCreator {
14+
private static final String TAG = "FirmwareZipCreator";
15+
16+
private final Context context;
17+
private final String firmwarePath;
18+
private final String initPacketPath;
19+
20+
public FirmwareZipCreator(Context context, String firmwarePath, String initPacketPath) {
21+
this.context = context;
22+
this.firmwarePath = firmwarePath;
23+
this.initPacketPath = initPacketPath;
24+
}
25+
26+
public String createZip() {
27+
String zipFilePath = context.getCacheDir() + "/update.zip";
28+
29+
File zipFile = initializeZipFile(zipFilePath);
30+
if (zipFile == null) {
31+
return null;
32+
}
33+
34+
if (!addFilesToZip(zipFile, firmwarePath, initPacketPath)) {
35+
return null;
36+
}
37+
38+
return zipFilePath;
39+
}
40+
41+
private File initializeZipFile(String path) {
42+
File zipFile = new File(path);
43+
try {
44+
if (zipFile.exists() && !zipFile.delete()) {
45+
Log.e(TAG, "Failed to delete existing file: " + path);
46+
return null;
47+
}
48+
49+
if (!zipFile.createNewFile()) {
50+
Log.e(TAG, "Failed to create new file: " + path);
51+
return null;
52+
}
53+
} catch (IOException e) {
54+
Log.e(TAG, "Error initializing zip file", e);
55+
return null;
56+
}
57+
58+
return zipFile;
59+
}
60+
61+
private boolean addFilesToZip(File zipFile, String... srcFiles) {
62+
byte[] buffer = new byte[1024];
63+
64+
try (FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
65+
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) {
66+
67+
for (String file : srcFiles) {
68+
File srcFile = new File(file);
69+
70+
if (!srcFile.exists()) {
71+
Log.e(TAG, "Source file does not exist: " + file);
72+
continue;
73+
}
74+
75+
try (FileInputStream fileInputStream = new FileInputStream(srcFile)) {
76+
zipOutputStream.putNextEntry(new ZipEntry(srcFile.getName()));
77+
78+
int length;
79+
while ((length = fileInputStream.read(buffer)) > 0) {
80+
zipOutputStream.write(buffer, 0, length);
81+
}
82+
83+
zipOutputStream.closeEntry();
84+
}
85+
}
86+
87+
return true;
88+
} catch (IOException e) {
89+
Log.e(TAG, "Error adding files to zip", e);
90+
return false;
91+
}
92+
}
93+
}

app/src/main/java/cc/calliope/mini/HexParser.kt

+85-67
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,111 @@
11
package cc.calliope.mini
22

3+
import cc.calliope.mini.utils.Constants.MINI_V2
4+
import cc.calliope.mini.utils.Constants.MINI_V3
35
import java.io.File
46

57
class HexParser(private val path: String) {
6-
private fun calculateChecksum(address: UShort, type: Int, data: ByteArray): UByte {
7-
var crc: UInt = data.size.toUByte() +
8-
(address.toUInt() shr 8).toUByte() +
9-
((address.toUInt() and 0xFFu) + type.toUInt()).toUByte()
8+
data class Partition(val address: UInt, val data: MutableList<Byte>)
9+
10+
private fun calculateChecksum(address: UInt, type: Int, data: ByteArray): Int {
11+
var crc = data.size and 0xFF
12+
13+
crc = (crc + ((address.toInt() shr 8) and 0xFF)) and 0xFF
14+
crc = (crc + (address.toInt() and 0xFF)) and 0xFF
15+
crc = (crc + type) and 0xFF
16+
1017
for (b in data) {
11-
crc = (crc + b.toUByte()).toUByte().toUInt()
18+
crc = (crc + (b.toInt() and 0xFF)) and 0xFF
1219
}
13-
return ((0x100u - crc) and 0xFFu).toUByte()
20+
21+
crc = (0x100 - crc) and 0xFF
22+
return crc
1423
}
1524

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

20-
var isUniversal = false
21-
var addressHi: UInt = 0u
22-
var dataType = 0
32+
val partitions = collectPartitions(addressRange, dataTypeCondition)
2333

24-
reader.useLines { lines ->
25-
lines.forEach { line ->
26-
var beginIndex = 0
27-
var endIndex = 1
34+
return buildPaddedBin(partitions)
35+
}
2836

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

32-
endIndex = beginIndex + 2
33-
val length = line.substring(beginIndex, endIndex).toInt(16).toUByte()
34-
beginIndex = endIndex
44+
parse { address, data, dataType, isUniversal ->
45+
if (address in addressRange && (dataTypeCondition(dataType) || !isUniversal)) {
46+
if (lastAddress == null || lastAddress!! > address || lastAddress!! + 16u < address) {
47+
partitions.add(Partition(address, mutableListOf()))
48+
}
49+
partitions.lastOrNull()?.data?.addAll(data.asList())
50+
lastAddress = address
51+
}
52+
}
3553

36-
endIndex = beginIndex + 4
37-
val addressLo = line.substring(beginIndex, endIndex).toUInt(16)
38-
beginIndex = endIndex
54+
return partitions.sortedBy { it.address }
55+
}
3956

40-
endIndex = beginIndex + 2
41-
val type = line.substring(beginIndex, endIndex).toInt(16)
42-
beginIndex = endIndex
57+
private fun buildPaddedBin(partitions: List<Partition>): ByteArray {
58+
val paddedApplication = mutableListOf<Byte>()
59+
partitions.zipWithNext { current, next ->
60+
paddedApplication.addAll(current.data)
61+
val paddingSize = (next.address - (current.address + current.data.size.toUInt())).toInt()
62+
if (paddingSize > 0) {
63+
paddedApplication.addAll(ByteArray(paddingSize) { 0xFF.toByte() }.toList())
64+
}
65+
}
4366

44-
endIndex = beginIndex + 2 * length.toInt()
45-
if (endIndex > line.length) return@forEach
46-
val payload = line.substring(beginIndex, endIndex)
47-
beginIndex = endIndex
67+
if (partitions.isNotEmpty()) {
68+
paddedApplication.addAll(partitions.last().data)
69+
}
4870

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

53-
val data = payload.hexStringToByteArray()
54-
val calculatedChecksum = calculateChecksum(addressLo.toUShort(), type, data)
55-
if (checksum != calculatedChecksum) {
56-
return@forEach
57-
}
74+
return paddedApplication.toByteArray()
75+
}
76+
77+
private fun parse(handleDataEntry: (UInt, ByteArray, Int, Boolean) -> Unit) {
78+
val file = File(path)
79+
val reader = file.bufferedReader()
80+
var isUniversal = false
81+
var addressHi = 0u
82+
var dataType = 0
83+
84+
reader.useLines { lines ->
85+
lines.forEach { line ->
86+
if (line.firstOrNull() != ':') return@forEach
87+
val length = line.substring(1, 3).toIntOrNull(16) ?: return@forEach
88+
val addressLo = line.substring(3, 7).toUIntOrNull(16) ?: return@forEach
89+
val type = line.substring(7, 9).toIntOrNull(16) ?: return@forEach
90+
val payload = line.substring(9, 9 + 2 * length)
91+
92+
// Calculate checksum
93+
val checksum = line.substring(9 + 2 * length, 9 + 2 * length + 2).toIntOrNull(16) ?: return@forEach
94+
val calculatedChecksum = calculateChecksum(addressHi + addressLo.toUShort(), type, payload.chunked(2).map { it.toInt(16).toByte() }.toByteArray())
95+
if (checksum != calculatedChecksum) return@forEach
5896

5997
when (type) {
60-
0, 13 -> { // Data
98+
0, 13 -> {
6199
val position = addressHi + addressLo
62-
if (data.size == length.toInt()) {
63-
handleDataEntry(position.toLong(), data, dataType, isUniversal)
100+
val data = payload.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
101+
if (data.size == length) {
102+
handleDataEntry(position, data, dataType, isUniversal)
64103
}
65104
}
66-
1 -> return // EOF
67-
2 -> { // EXT SEGMENT ADDRESS
68-
val segment = payload.toUInt(16)
69-
addressHi = segment shl 4
70-
}
71-
3 -> { /* START SEGMENT ADDRESS */ }
72-
4 -> { // EXT LINEAR ADDRESS
73-
val segment = payload.toUInt(16)
74-
addressHi = segment shl 16
75-
}
76-
5 -> { /* START LINEAR ADDRESS */ }
77-
10 -> { // Block Start Address
105+
1 -> return
106+
2 -> addressHi = payload.toUIntOrNull(16)?.shl(4) ?: 0u
107+
4 -> addressHi = payload.toUIntOrNull(16)?.shl(16) ?: 0u
108+
10 -> {
78109
isUniversal = true
79110
val dataTypeField = line.substring(9, 13)
80111
dataType = when (dataTypeField) {
@@ -83,21 +114,8 @@ class HexParser(private val path: String) {
83114
else -> dataType
84115
}
85116
}
86-
else -> { /* OTHER */ }
87117
}
88118
}
89119
}
90120
}
91121
}
92-
93-
fun String.hexStringToByteArray(): ByteArray {
94-
val len = this.length
95-
val data = ByteArray(len / 2)
96-
var i = 0
97-
while (i < len) {
98-
val byte = this.substring(i, i + 2).toInt(16).toByte()
99-
data[i / 2] = byte
100-
i += 2
101-
}
102-
return data
103-
}

app/src/main/java/cc/calliope/mini/InitPacket.kt

-38
This file was deleted.

0 commit comments

Comments
 (0)