mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
682 lines
17 KiB
Go
682 lines
17 KiB
Go
package manifest
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"strings"
|
|
"unicode/utf16"
|
|
)
|
|
|
|
var ErrUnknownResourceDataType = errors.New("Unknown resource data type")
|
|
|
|
// Contains parsed resources.arsc file.
|
|
type ResourceTable struct {
|
|
mainStrings stringTable
|
|
nextPackageId uint32
|
|
packages map[uint32]*packageGroup
|
|
}
|
|
|
|
type packageGroup struct {
|
|
Name string
|
|
Id uint32
|
|
Packages []*resourcePackage
|
|
|
|
table *ResourceTable
|
|
largestTypeId uint8
|
|
types map[uint8][]resourceTypeSpec
|
|
}
|
|
|
|
type resourcePackage struct {
|
|
Id uint32
|
|
Name string
|
|
|
|
typeIdOffset uint32
|
|
typeStrings stringTable
|
|
keyStrings stringTable
|
|
}
|
|
|
|
type resourceTypeSpec struct {
|
|
Id uint8
|
|
Entries []uint32
|
|
Package *resourcePackage
|
|
|
|
Configs []*resourceType
|
|
}
|
|
|
|
type resourceType struct {
|
|
chunkData []byte
|
|
entryCount uint32
|
|
entriesStart uint32
|
|
indexesStart uint32
|
|
|
|
// ResTable_config config;
|
|
}
|
|
|
|
const (
|
|
tableEntryComplex = 0x0001
|
|
tableEntryPublic = 0x0002
|
|
tableEntryWeak = 0x0004
|
|
)
|
|
|
|
// Describes one resource entry, for example @drawable/icon in the original XML, in one particular config option.
|
|
type ResourceEntry struct {
|
|
size uint16
|
|
flags uint16
|
|
|
|
ResourceType string
|
|
Key string
|
|
Package string
|
|
|
|
value ResourceValue
|
|
}
|
|
|
|
// Handle to the resource's actual value.
|
|
type ResourceValue struct {
|
|
dataType AttrType
|
|
data uint32
|
|
|
|
globalStringTable *stringTable
|
|
convertedData interface{}
|
|
}
|
|
|
|
// Resource config option to pick from options - when @drawable/icon is referenced,
|
|
// use /res/drawable-xhdpi/icon.png or use /res/drawable-mdpi/icon.png?
|
|
//
|
|
// This is not fully implemented, so you can pick only first seen or last seen option.
|
|
type ResourceConfigOption int
|
|
|
|
const (
|
|
ConfigFirst ResourceConfigOption = iota // Usually the smallest
|
|
ConfigLast // Usually the biggest
|
|
|
|
// Try to find the biggest png icon, otherwise same as ConfigLast.
|
|
//
|
|
// Deprecated: use GetIconPng
|
|
ConfigPngIcon
|
|
)
|
|
|
|
// Parses the resources.arsc file
|
|
func ParseResourceTable(r io.Reader) *ResourceTable {
|
|
res := ResourceTable{
|
|
nextPackageId: 2,
|
|
packages: make(map[uint32]*packageGroup),
|
|
}
|
|
|
|
id, hdrLen, totalLen, err := parseChunkHeader(r)
|
|
if err != nil {
|
|
log.Panic("parseChunkHeader() failed", err)
|
|
}
|
|
|
|
var packageCurrent, packagesCnt uint32
|
|
if err = binary.Read(r, binary.LittleEndian, &packagesCnt); err != nil {
|
|
log.Panic("Failed to read packagesCnt", err)
|
|
}
|
|
|
|
if hdrLen < chunkHeaderSize+4 {
|
|
log.Panicf("Invalid header length: %d", hdrLen)
|
|
}
|
|
|
|
totalLen -= uint32(hdrLen)
|
|
hdrLen -= chunkHeaderSize + 4
|
|
|
|
if _, err = io.CopyN(ioutil.Discard, r, int64(hdrLen)); err != nil {
|
|
log.Panic("Failed to read header padding: %s", err.Error())
|
|
}
|
|
|
|
var len uint32
|
|
var lastId uint16
|
|
for i := uint32(0); i < totalLen; i += len {
|
|
id, hdrLen, len, err = parseChunkHeader(r)
|
|
if err != nil {
|
|
log.Panicf("Error parsing header at 0x%08x of 0x%08x %08x: %s", i, totalLen, lastId, err.Error())
|
|
}
|
|
|
|
lastId = id
|
|
|
|
lm := &io.LimitedReader{R: r, N: int64(len) - chunkHeaderSize}
|
|
|
|
switch id {
|
|
case chunkStringTable:
|
|
if res.mainStrings.isEmpty() {
|
|
res.mainStrings, err = parseStringTable(lm)
|
|
}
|
|
case chunkTablePackage:
|
|
if packageCurrent >= packagesCnt {
|
|
log.Panicf("Chunk: 0x%08x: Too many package chunks", id)
|
|
}
|
|
|
|
err = res.parsePackage(lm, hdrLen)
|
|
packageCurrent++
|
|
default:
|
|
err = fmt.Errorf("Unknown chunk: 0x%08x at %d.", id, i+chunkHeaderSize+4)
|
|
//_, err = io.CopyN(ioutil.Discard, lm, lm.N)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Panicf("Chunk: 0x%08x: %s", id, err.Error())
|
|
} else if lm.N != 0 {
|
|
log.Panicf("Chunk: 0x%08x: was not fully read", id)
|
|
}
|
|
}
|
|
return &res
|
|
}
|
|
|
|
func (x *ResourceTable) parsePackage(r *io.LimitedReader, hdrLen uint16) error {
|
|
pkgBlock, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading package block: %s", err.Error())
|
|
}
|
|
|
|
pkgReader := bytes.NewReader(pkgBlock)
|
|
|
|
const valsSize = chunkHeaderSize + 4 + 2*128 + 4*5
|
|
vals := struct {
|
|
Id uint32
|
|
Name [128]uint16
|
|
TypeStrings uint32
|
|
LastPublicType uint32
|
|
KeyStrings uint32
|
|
LastPublicKey uint32
|
|
TypeIdOffset uint32
|
|
}{}
|
|
|
|
if err := binary.Read(pkgReader, binary.LittleEndian, &vals); err != nil {
|
|
return fmt.Errorf("error reading values: %s", err.Error())
|
|
}
|
|
|
|
if vals.Id >= 256 {
|
|
return fmt.Errorf("package id out of range: %d", vals.Id)
|
|
}
|
|
|
|
if vals.Id == 0 {
|
|
vals.Id = x.nextPackageId
|
|
x.nextPackageId++
|
|
}
|
|
|
|
pkg := &resourcePackage{
|
|
Id: vals.Id,
|
|
}
|
|
|
|
// TypeIdOffset was added later and may not be present (frameworks/base@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e)
|
|
if hdrLen >= valsSize {
|
|
pkg.typeIdOffset = vals.TypeIdOffset
|
|
}
|
|
|
|
pkg.Name = string(utf16.Decode(vals.Name[:]))
|
|
if idx := strings.IndexRune(pkg.Name, 0); idx != -1 {
|
|
pkg.Name = pkg.Name[:idx]
|
|
}
|
|
|
|
if vals.TypeStrings < chunkHeaderSize || vals.KeyStrings <= chunkHeaderSize {
|
|
return fmt.Errorf("Invalid strings offset: %d %d", vals.TypeStrings, vals.KeyStrings)
|
|
}
|
|
|
|
vals.TypeStrings -= chunkHeaderSize
|
|
vals.KeyStrings -= chunkHeaderSize
|
|
|
|
if _, err := pkgReader.Seek(int64(vals.TypeStrings), io.SeekStart); err != nil {
|
|
return err
|
|
}
|
|
|
|
if pkg.typeStrings, err = parseStringTableWithChunk(pkgReader); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := pkgReader.Seek(int64(vals.KeyStrings), io.SeekStart); err != nil {
|
|
return err
|
|
}
|
|
|
|
if pkg.keyStrings, err = parseStringTableWithChunk(pkgReader); err != nil {
|
|
return err
|
|
}
|
|
|
|
group, prs := x.packages[pkg.Id]
|
|
if !prs {
|
|
group = &packageGroup{
|
|
Id: pkg.Id,
|
|
Name: pkg.Name,
|
|
table: x,
|
|
types: make(map[uint8][]resourceTypeSpec),
|
|
}
|
|
x.packages[pkg.Id] = group
|
|
|
|
/*
|
|
// Find all packages that reference this package
|
|
size_t N = mpackageGroups.size();
|
|
for (size_t i = 0; i < N; i++) {
|
|
mpackageGroups[i]->dynamicRefTable.addMapping(
|
|
group->name, static_cast<uint8_t>(group->id));
|
|
}
|
|
*/
|
|
}
|
|
|
|
group.Packages = append(group.Packages, pkg)
|
|
|
|
if _, err := pkgReader.Seek(int64(hdrLen-chunkHeaderSize), io.SeekStart); err != nil {
|
|
return err
|
|
}
|
|
|
|
for {
|
|
chunkStartOffset, _ := pkgReader.Seek(0, io.SeekCurrent)
|
|
|
|
id, hdrLen, totalLen, err := parseChunkHeader(pkgReader)
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return fmt.Errorf("Error parsing package internal header: %s", err.Error())
|
|
}
|
|
|
|
// Sample: 7e97541191621e72bd794b5b2d60eb2f68669ea8782421e54ec719ccda06c8a4
|
|
if chunkStartOffset+int64(totalLen) >= int64(len(pkgBlock)) {
|
|
totalLen = uint32(int64(len(pkgBlock)) - chunkStartOffset)
|
|
}
|
|
|
|
lm := &io.LimitedReader{R: pkgReader, N: int64(totalLen) - chunkHeaderSize}
|
|
|
|
switch id {
|
|
case chunkTableTypeSpec:
|
|
err = x.parseTypeSpec(lm, pkg, group)
|
|
case chunkTableType:
|
|
block := pkgBlock[chunkStartOffset : chunkStartOffset+int64(totalLen)]
|
|
if err = x.parseType(lm, pkg, group, block, hdrLen); err != nil {
|
|
break
|
|
}
|
|
fallthrough
|
|
default:
|
|
_, err = io.CopyN(ioutil.Discard, lm, lm.N)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("Chunk: 0x%08x: %s", id, err.Error())
|
|
} else if lm.N != 0 {
|
|
return fmt.Errorf("Chunk: 0x%08x: was not fully read", id)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *ResourceTable) parseTypeSpec(r io.Reader, pkg *resourcePackage, group *packageGroup) error {
|
|
var id uint8
|
|
if err := binary.Read(r, binary.LittleEndian, &id); err != nil {
|
|
return fmt.Errorf("Failed to read type spec id: %s", err.Error())
|
|
}
|
|
|
|
if id == 0 {
|
|
return fmt.Errorf("Invalid type spec id: %d", id)
|
|
}
|
|
|
|
if _, err := io.CopyN(ioutil.Discard, r, 1+2); err != nil {
|
|
return fmt.Errorf("Failed to skip padding: %s", err.Error())
|
|
}
|
|
|
|
var entryCount uint32
|
|
if err := binary.Read(r, binary.LittleEndian, &entryCount); err != nil {
|
|
return fmt.Errorf("Failed to read entryCount: %s", err.Error())
|
|
}
|
|
|
|
if entryCount > 0 {
|
|
var entries []uint32
|
|
for i := uint32(0); i < entryCount; i++ {
|
|
var e uint32
|
|
if err := binary.Read(r, binary.LittleEndian, &e); err != nil {
|
|
return fmt.Errorf("Failed to read type spec entry: %s", err.Error())
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
|
|
group.types[id] = append(group.types[id], resourceTypeSpec{
|
|
Id: id,
|
|
Entries: entries,
|
|
Package: pkg,
|
|
})
|
|
|
|
if id > group.largestTypeId {
|
|
group.largestTypeId = id
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (x *ResourceTable) parseType(r io.Reader, pkg *resourcePackage, group *packageGroup, chunkData []byte, hdrLen uint16) error {
|
|
vals := struct {
|
|
Id uint8
|
|
Res0 uint8
|
|
Res1 uint16
|
|
|
|
EntryCount uint32
|
|
EntriesStart uint32
|
|
|
|
//ResTable_config config;
|
|
}{}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &vals); err != nil {
|
|
return fmt.Errorf("error reading values: %s", err.Error())
|
|
}
|
|
|
|
if vals.Id == 0 {
|
|
return fmt.Errorf("Invalid type id: %d", vals.Id)
|
|
}
|
|
|
|
if vals.EntryCount > 0 {
|
|
typeList := group.types[vals.Id]
|
|
if len(typeList) == 0 {
|
|
return fmt.Errorf("No spec entry for type %d", vals.Id)
|
|
}
|
|
|
|
i := len(typeList) - 1
|
|
typeList[i].Configs = append(typeList[i].Configs, &resourceType{
|
|
chunkData: chunkData,
|
|
entryCount: vals.EntryCount,
|
|
entriesStart: vals.EntriesStart,
|
|
indexesStart: uint32(hdrLen),
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Converts the resource id to readable name including the package name like "@drawable:com.example.app.icon".
|
|
func (x *ResourceTable) GetResourceName(resId uint32) (string, error) {
|
|
pkgId := (resId >> 24)
|
|
typ := ((resId >> 16) & 0xFF) - 1
|
|
entryId := (resId & 0xFFFF)
|
|
|
|
group := x.packages[pkgId]
|
|
if group == nil {
|
|
return "", fmt.Errorf("Invalid package identifier.")
|
|
}
|
|
|
|
entry, err := x.getEntry(group, typ, entryId, ConfigFirst)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("@%s:%s.%s", entry.ResourceType, group.Name, entry.Key), nil
|
|
}
|
|
|
|
// Returns the resource entry for resId and the first configuration option it finds.
|
|
func (x *ResourceTable) GetResourceEntry(resId uint32) (*ResourceEntry, error) {
|
|
return x.GetResourceEntryEx(resId, ConfigFirst)
|
|
}
|
|
|
|
// Returns the resource entry for resId and config configuration option.
|
|
func (x *ResourceTable) GetResourceEntryEx(resId uint32, config ResourceConfigOption) (*ResourceEntry, error) {
|
|
if config == ConfigPngIcon {
|
|
return x.GetIconPng(resId)
|
|
}
|
|
|
|
pkgId := (resId >> 24)
|
|
typ := ((resId >> 16) & 0xFF) - 1
|
|
entryId := (resId & 0xFFFF)
|
|
|
|
group := x.packages[pkgId]
|
|
if group == nil {
|
|
return nil, fmt.Errorf("Invalid package identifier.")
|
|
}
|
|
|
|
return x.getEntry(group, typ, entryId, config)
|
|
}
|
|
|
|
// Return the biggest last config ending with .png. Falls back to GetResourceEntry() if none found.
|
|
func (x *ResourceTable) GetIconPng(resId uint32) (*ResourceEntry, error) {
|
|
pkgId := (resId >> 24)
|
|
typ := ((resId >> 16) & 0xFF) - 1
|
|
entryId := (resId & 0xFFFF)
|
|
|
|
group := x.packages[pkgId]
|
|
if group == nil {
|
|
return nil, fmt.Errorf("Invalid package identifier.")
|
|
}
|
|
|
|
entries, err := x.getEntryConfigs(group, typ, entryId, 256)
|
|
if len(entries) == 0 {
|
|
return nil, err
|
|
}
|
|
|
|
var res *ResourceEntry
|
|
for i := 0; i < len(entries) && i < 1024; i++ {
|
|
e := entries[i]
|
|
if e.value.dataType == AttrTypeReference {
|
|
pkgId = (e.value.data >> 24)
|
|
typ = ((e.value.data >> 16) & 0xFF) - 1
|
|
entryId = (e.value.data & 0xFFFF)
|
|
|
|
if more, _ := x.getEntryConfigs(group, typ, entryId, 256); len(more) != 0 {
|
|
entries = append(entries, more...)
|
|
}
|
|
} else if val, _ := e.value.String(); strings.HasSuffix(val, ".png") {
|
|
res = e
|
|
}
|
|
}
|
|
|
|
if res == nil {
|
|
return x.GetResourceEntry(resId)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (x *ResourceTable) getEntry(group *packageGroup, typeId, entry uint32, config ResourceConfigOption) (*ResourceEntry, error) {
|
|
limit := 1024
|
|
if config == ConfigFirst {
|
|
limit = 1
|
|
}
|
|
|
|
entries, err := x.getEntryConfigs(group, typeId, entry, limit)
|
|
if len(entries) == 0 {
|
|
return nil, err
|
|
}
|
|
res := entries[len(entries)-1]
|
|
return res, err
|
|
}
|
|
|
|
func (x *ResourceTable) getEntryConfigs(group *packageGroup, typeId, entry uint32, limit int) ([]*ResourceEntry, error) {
|
|
typeList := group.types[uint8(typeId+1)]
|
|
if len(typeList) == 0 {
|
|
return nil, fmt.Errorf("Invalid type: %d", typeId)
|
|
}
|
|
|
|
var lastErr error
|
|
var entries []*ResourceEntry
|
|
for _, typ := range typeList {
|
|
for _, thisType := range typ.Configs {
|
|
if entry >= thisType.entryCount {
|
|
continue
|
|
}
|
|
|
|
r := bytes.NewReader(thisType.chunkData)
|
|
if _, err := r.Seek(int64(thisType.indexesStart+entry*4), io.SeekStart); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var thisOffset uint32
|
|
if err := binary.Read(r, binary.LittleEndian, &thisOffset); err != nil {
|
|
return nil, fmt.Errorf("Failed to read this type offset: %s", err.Error())
|
|
}
|
|
|
|
if thisOffset == math.MaxUint32 {
|
|
continue
|
|
}
|
|
|
|
offset := thisType.entriesStart + thisOffset
|
|
|
|
if int(offset) >= len(thisType.chunkData) || ((offset & 0x03) != 0) {
|
|
return nil, fmt.Errorf("Invalid entry 0x%04x offset: %d!", entry, offset)
|
|
}
|
|
|
|
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := x.parseEntry(r, typ.Package, typeId)
|
|
if err != nil {
|
|
lastErr = err
|
|
} else {
|
|
entries = append(entries, res)
|
|
}
|
|
|
|
if len(entries) >= limit {
|
|
goto exit
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(entries) == 0 {
|
|
return nil, fmt.Errorf("No entry found.")
|
|
}
|
|
exit:
|
|
return entries, lastErr
|
|
}
|
|
|
|
func (x *ResourceTable) parseEntry(r io.Reader, pkg *resourcePackage, typeId uint32) (*ResourceEntry, error) {
|
|
var err error
|
|
var res ResourceEntry
|
|
var keyIndex uint32
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &res.size); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry size: %s", err.Error())
|
|
}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &res.flags); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry flags: %s", err.Error())
|
|
}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &keyIndex); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry key index: %s", err.Error())
|
|
}
|
|
|
|
res.Package = pkg.Name
|
|
|
|
res.ResourceType, err = pkg.typeStrings.get(typeId - pkg.typeIdOffset)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Invalid typeString: %s", err.Error())
|
|
}
|
|
|
|
res.Key, err = pkg.keyStrings.get(keyIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Invalid keyString: %s", err.Error())
|
|
}
|
|
|
|
if !res.IsComplex() {
|
|
var size uint16
|
|
if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry value size: %s", err.Error())
|
|
}
|
|
|
|
if size < 8 {
|
|
return nil, fmt.Errorf("Invalid Res_value size: %d!", size)
|
|
}
|
|
|
|
if _, err := io.CopyN(ioutil.Discard, r, 1); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry value res0: %s", err.Error())
|
|
}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &res.value.dataType); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry value data type: %s", err.Error())
|
|
}
|
|
|
|
if err := binary.Read(r, binary.LittleEndian, &res.value.data); err != nil {
|
|
return nil, fmt.Errorf("Failed to read entry value data: %s", err.Error())
|
|
}
|
|
|
|
res.value.globalStringTable = &x.mainStrings
|
|
|
|
} else {
|
|
// NYI
|
|
}
|
|
|
|
return &res, nil
|
|
}
|
|
|
|
// Returns true if the resource entry is complex (for example arrays, string plural arrays...).
|
|
//
|
|
// Complex ResourceEntries are not yet supported.
|
|
func (e *ResourceEntry) IsComplex() bool {
|
|
return (e.flags & tableEntryComplex) != 0
|
|
}
|
|
|
|
// Returns the resource value handle
|
|
func (e *ResourceEntry) GetValue() *ResourceValue {
|
|
return &e.value
|
|
}
|
|
|
|
// Returns the resource data type
|
|
func (v *ResourceValue) Type() AttrType {
|
|
return v.dataType
|
|
}
|
|
|
|
// Returns the raw data of the resource
|
|
func (v *ResourceValue) RawData() uint32 {
|
|
return v.data
|
|
}
|
|
|
|
// Returns the data converted to their native type (e.g. AttrTypeString to string).
|
|
//
|
|
// Returns ErrUnknownResourceDataType if the type is not handled by this library
|
|
func (v *ResourceValue) Data() (interface{}, error) {
|
|
if v.convertedData != nil {
|
|
return v.convertedData, nil
|
|
}
|
|
|
|
var val interface{}
|
|
var err error
|
|
|
|
switch v.dataType {
|
|
case AttrTypeNull:
|
|
case AttrTypeString:
|
|
val, err = v.globalStringTable.get(v.data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case AttrTypeIntDec, AttrTypeIntHex, AttrTypeIntBool,
|
|
AttrTypeIntColorArgb8, AttrTypeIntColorRgb8,
|
|
AttrTypeIntColorArgb4, AttrTypeIntColorRgb4,
|
|
AttrTypeReference:
|
|
val = v.data
|
|
default:
|
|
return nil, ErrUnknownResourceDataType
|
|
}
|
|
|
|
v.convertedData = val
|
|
return val, nil
|
|
}
|
|
|
|
// Returns the data converted to a readable string, to the format it was likely in the original AndroidManifest.xml.
|
|
//
|
|
// Unknown data types are returned as the string from ErrUnknownResourceDataType.Error().
|
|
func (v *ResourceValue) String() (res string, err error) {
|
|
switch v.dataType {
|
|
case AttrTypeNull:
|
|
res = "null"
|
|
case AttrTypeIntHex:
|
|
res = fmt.Sprintf("0x%x", v.data)
|
|
case AttrTypeIntBool:
|
|
if v.data != 0 {
|
|
res = "true"
|
|
} else {
|
|
res = "false"
|
|
}
|
|
case AttrTypeIntColorArgb8:
|
|
res = fmt.Sprintf("#%08x", v.data)
|
|
case AttrTypeIntColorRgb8:
|
|
res = fmt.Sprintf("#%06x", v.data)
|
|
case AttrTypeIntColorArgb4:
|
|
res = fmt.Sprintf("#%04x", v.data)
|
|
case AttrTypeIntColorRgb4:
|
|
res = fmt.Sprintf("#%03x", v.data)
|
|
case AttrTypeReference:
|
|
res = fmt.Sprintf("@%x", v.data)
|
|
default:
|
|
var val interface{}
|
|
val, err = v.Data()
|
|
if err == nil {
|
|
res = fmt.Sprintf("%v", val)
|
|
}
|
|
}
|
|
return
|
|
}
|