This commit is contained in:
relikd
2025-11-25 22:46:14 +01:00
commit a28636a7ca
28 changed files with 2380 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
// MARK: - DataType
/// Type of the data value.
public enum DataType : UInt8 {
/// `data` is either `0` or `1`, specifying this resource is either undefined or empty, respectively.
case Null = 0x00
/// `data` holds a `TblTableRef`, a reference to another resource table entry.
case Reference = 0x01
/// `data` holds an attribute resource identifier.
case Attribute = 0x02
/// `data` holds an index into the containing resource table's global value string pool.
case String = 0x03
/// `data` holds a single-precision floating point number.
case Float = 0x04
/// `data` holds a complex number encoding a dimension value, such as `100in`.
case Dimension = 0x05
/// `data` holds a complex number encoding a fraction of a container.
case Fraction = 0x06
/// `data` holds a dynamic `TblTableRef`, which needs to be resolved before it can be used like a `.Reference`.
case DynamicReference = 0x07
/// `data` holds an attribute resource identifier, which needs to be resolved before it can be used like a `.Attribute`.
case DynamicAttribute = 0x08
/// `data` is a raw integer value of the form `n..n`.
case IntDec = 0x10
/// `data` is a raw integer value of the form `0xn..n`.
case IntHex = 0x11
/// `data` is either `0` or `1`, for input `false` or `true` respectively.
case IntBoolean = 0x12
/// `data` is a raw integer value of the form `#aarrggbb`.
case IntColorARGB8 = 0x1c
/// `data` is a raw integer value of the form `#rrggbb`.
case IntColorRGB8 = 0x1d
/// `data` is a raw integer value of the form `#argb`.
case IntColorARGB4 = 0x1e
/// `data` is a raw integer value of the form `#rgb`.
case IntColorRGB4 = 0x1f
var isColor: Bool {
self == .IntColorARGB8 || self == .IntColorRGB8 || self == .IntColorARGB4 || self == .IntColorRGB4
}
}
// MARK: - ResValue
/**
* Representation of a value in a resource, supplying type information.
*/
/// Size: `8 Bytes`
public struct ResValue {
/// Number of bytes in this structure.
let size: UInt16
/// Always set to 0.
let res0: UInt8
/// Type of the data value.
public let dataType: DataType // UInt8
/// The data for this item, as interpreted according to dataType.
let data: UInt32
init(type: DataType, data rawData: UInt32) {
size = 8
res0 = 0
dataType = type
data = rawData
}
init(_ br: inout RawBytes.Reader) throws {
size = br.read16()
res0 = br.read8()
let rawType: UInt8 = br.read8()
guard let typ = DataType(rawValue: rawType) else {
throw AXMLError("Unknown ResValue data-type \(rawType) (offset: \(br.index - 1))")
}
dataType = typ
data = br.read32()
assert(dataType != .DynamicAttribute, "TODO: .DynamicAttribute data type")
assert(dataType != .DynamicReference, "TODO: .DynamicReference data type")
}
/// Convenience getter for `DataType.String`
public var asStringRef: StringPoolRef { data }
/// Convenience getter for `DataType.Reference` and `DataType.Attribute`
public var asTableRef: TblTableRef { TblTableRef(data) }
/// Convenience getter for `DataType.Float`
public var asFloat: Float { Float(bitPattern: data) }
/// Convenience getter for `DataType.Dimension`
public var asDimension: ComplexType { ComplexType(data, isFraction: false) }
/// Convenience getter for `DataType.Fraction`
public var asFraction: ComplexType { ComplexType(data, isFraction: true) }
/// Convenience getter for `DataType.IntDec`
public var asIntDec: Int { Int(Int32(truncatingIfNeeded: data)) }
/// Convenience getter for `DataType.IntHex` with format `00AA22CC`
public var asIntHex: String { data.hex(8) }
/// Convenience getter for `DataType.IntBoolean` (value `> 0`)
public var asBoolean: Bool { data > 0 }
/// Convenience getter for `DataType.IntColor*`
public var asColor: ColorComponents { ColorComponents(data) }
// see: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/libs/androidfw/ResourceTypes.cpp#7637
/// Use meta data to display value according to type.
/// `StringPool` only required for type `.String`
public func resolve(_ pool: StringPool?) -> String {
switch dataType {
case .Null:
// switch data {
// case 0: return "(null)" // not defined
// case 1: return "(null empty)" // explicitly defined as empty
// default: return String(format: "(null) 0x%08x\n", data) // should never happen
// }
return ""
case .DynamicReference:
// TODO: can we treat them as their non-dynamic counterpart?
fallthrough
case .Reference: return ((data >> 24) & 0xFF == 1 ? "@android:" : "@") + data.hex(8)
case .DynamicAttribute:
fallthrough
case .Attribute: return ((data >> 24) & 0xFF == 1 ? "?android:" : "?") + data.hex(8)
case .String: return pool!.getString(data)
case .Float: return "\(asFloat)"
case .Dimension: return asDimension.asString()
case .Fraction: return asFraction.asString()
case .IntDec: return "\(asIntDec)"
case .IntHex: return "0x" + data.hex(8)
case .IntBoolean: return data > 0 ? "true" : "false"
case .IntColorARGB8: return asColor.argb8()
case .IntColorRGB8: return asColor.rgb8()
case .IntColorARGB4: return asColor.argb4()
case .IntColorRGB4: return asColor.rgb4()
}
}
}
// MARK: - ComplexType
/**
* Structure of complex data values (`DataType.Dimension` and `DataType.Fraction`)
*
* `MANTISSA_MASK = 0xFFFFFF00`
*
* Where the actual value is.
* This gives us 23 bits of precision.
* The top bit is the sign.
*
* `RADIX_MASK = 0x00000030`
*
* Where the radix information is, telling where the decimal place appears in the mantissa.
* This give us 4 possible fixed point representations as defined below.
*
* `UNIT_MASK = 0x0000000F`
*
* Where the unit type information is.
* This gives us 16 possible types, as defined below.
*/
public struct ComplexType {
public let value: Float
public let unit: String
init(_ complex: UInt32, isFraction: Bool) {
// UNIT
var scale: Float = 1.0
if (isFraction) {
scale = 100
switch (complex & 0xF) {
case 0: unit = "%" // A basic fraction of the overall size.
case 1: unit = "%p" // A fraction of the parent size.
default: unit = ""
}
} else {
switch (complex & 0xF) {
case 0: unit = "px" // raw pixels
case 1: unit = "dp" // Device Independent Pixels
case 2: unit = "sp" // Scaled device independent Pixels
case 3: unit = "pt" // points
case 4: unit = "in" // inches
case 5: unit = "mm" // millimeters
default: unit = ""
}
}
// VALUE
let neg = (complex & 0x80000000) != 0
let mantissa = Float((complex & 0x7FFFFF00) >> 8) - (neg ? Float(0x7FFFFF) : 0)
let val = mantissa / (radix[Int(complex & 0x30) >> 4] / scale)
// for `.Fraction`, round to nearest integral number (if equivalent)
let isIntegral = isFraction && abs(val.rounded() - val) < 1e-05
value = isIntegral ? val.rounded() : val
}
public func asString() -> String {
value.rounded() == value ? "\(Int(value))\(unit)" : "\(value)\(unit)"
}
}
// 23p0 - The mantissa is an integral number -- i.e., 0xnnnnnn.0
// 16p7 - The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
// 8p15 - The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
// 0p23 - The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
private let radix: [Float] = [1, 0x80, 0x8000, 0x800000]