1
1
package cc.calliope.mini
2
2
3
+ import cc.calliope.mini.utils.Constants.MINI_V2
4
+ import cc.calliope.mini.utils.Constants.MINI_V3
3
5
import java.io.File
4
6
5
7
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
+
10
17
for (b in data) {
11
- crc = (crc + b.toUByte()).toUByte().toUInt()
18
+ crc = (crc + (b.toInt() and 0xFF )) and 0xFF
12
19
}
13
- return ((0x100u - crc) and 0xFFu ).toUByte()
20
+
21
+ crc = (0x100 - crc) and 0xFF
22
+ return crc
14
23
}
15
24
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
+ }
19
31
20
- var isUniversal = false
21
- var addressHi: UInt = 0u
22
- var dataType = 0
32
+ val partitions = collectPartitions(addressRange, dataTypeCondition)
23
33
24
- reader.useLines { lines ->
25
- lines.forEach { line ->
26
- var beginIndex = 0
27
- var endIndex = 1
34
+ return buildPaddedBin(partitions)
35
+ }
28
36
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
31
43
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
+ }
35
53
36
- endIndex = beginIndex + 4
37
- val addressLo = line.substring(beginIndex, endIndex).toUInt(16 )
38
- beginIndex = endIndex
54
+ return partitions.sortedBy { it.address }
55
+ }
39
56
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
+ }
43
66
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
+ }
48
70
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())
52
73
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
58
96
59
97
when (type) {
60
- 0 , 13 -> { // Data
98
+ 0 , 13 -> {
61
99
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)
64
103
}
65
104
}
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 -> {
78
109
isUniversal = true
79
110
val dataTypeField = line.substring(9 , 13 )
80
111
dataType = when (dataTypeField) {
@@ -83,21 +114,8 @@ class HexParser(private val path: String) {
83
114
else -> dataType
84
115
}
85
116
}
86
- else -> { /* OTHER */ }
87
117
}
88
118
}
89
119
}
90
120
}
91
121
}
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
- }
0 commit comments