pxlycan

PixeLycan: devinfo viewer and modifier for Tensor-based Google Pixels
git clone git://git.luxferre.top/pxlycan.git
Log | Files | Refs

pxlycan.tcl (8379B)


      1 #!/usr/bin/env tclsh
      2 # PixeLycan: devinfo partition modification utility for Google Pixel phones
      3 # (Tensor/Exynos-based, starting with Pixel 6)
      4 # Usage:
      5 # View info: pxlycan.tcl devinfofile
      6 # Set info field: pxlycan.tcl devinfofile setinfofield fieldname hex_value
      7 # Set PS tags: pxlycan.tcl devinfofile setps tag1 val1 tag2 val2 ...
      8 # Set PSENV tags: pxlycan.tcl devinfofile setpsenv tag1 val1 tag2 val2 ...
      9 # where tag1, tag2 etc must start with DIUS_ or DIFR_ based on their type
     10 # Requires Tcl 8.6 and up with no external dependencies
     11 # Created by Luxferre in 2024, released into public domain
     12 
     13 # parse the PS and PS env tags into the pstags and psenvtags list
     14 # every tag is a dictionary with the following fields:
     15 # offset type key value key_len full_len
     16 # where type can be DIFR or DIUS
     17 proc scantags {infodata} {
     18   set pstags {}
     19   set psenvtags {}
     20   set offset 128
     21   while {$offset < 8192} {
     22     set scanval [string range $infodata $offset end]
     23     binary scan $scanval a4 tmagic
     24     if {$tmagic eq {DIFR} || $tmagic eq {DIUS}} {
     25       set scanval [string range $scanval 4 end]
     26       binary scan $scanval iu full_len 
     27       if {$full_len > 0xf00} { # handle special case of the last tag
     28         # the rule is: current offset + 12 + full_len = 8192
     29         incr offset 12
     30         set scanval [string range $infodata $offset end]
     31         binary scan $scanval iu key_len
     32         set scanval [string range $scanval 4 end]
     33         set tagdata [string range $scanval 0 end]
     34 
     35       } else { # normal tag
     36         set scanval [string range $scanval 4 end]
     37         binary scan $scanval iu key_len
     38         set scanval [string range $scanval 4 end]
     39         set tagdata [string range $scanval 0 [expr {$full_len - 1}]]
     40       }
     41       set tagkey [string trim [string range $tagdata 0 [expr {$key_len - 1}]]]
     42       if {$tagkey ne ""} {
     43         set tagkey [string cat $tmagic _ $tagkey]
     44         set tagvalue [string range $tagdata $key_len end]
     45         if {$tagvalue ne ""} {
     46           if {$offset >= 4096} {
     47             dict set psenvtags $tagkey $tagvalue
     48           } else {
     49             dict set pstags $tagkey $tagvalue
     50           }
     51         }
     52       }
     53       incr offset 12
     54       incr offset $full_len
     55     } else {incr offset 4}
     56   }
     57   return [list pstags $pstags psenvtags $psenvtags]
     58 }
     59 
     60 # build the PS/PSENV tag blob from a Tcl dictionary-like list
     61 # where key names are like DIUS_name or DIFR_name
     62 # key names are stripped, values are passed as is
     63 # pass 3968 as maxlen for PS tag zone, 4096 for PSENV tag zone
     64 proc buildtagblob {inputdict maxlen} {
     65   set tagblob {}
     66   # iterate over the input dictionary
     67   dict for {ckey val} $inputdict {
     68     if {[string match DI??_* $ckey]} {
     69       set key "[string trim [string range $ckey 5 end]]\0"
     70       set type [string range $ckey 0 3]
     71       set keylen [string length $key]
     72       set fullen [expr {$keylen + [string length $val]}]
     73       append tagblob [binary format a4iuiu $type $fullen $keylen]
     74       append tagblob $key $val
     75     }
     76   }
     77   # build the last padding tag of DIFR type
     78   set fullen [expr {$maxlen - [string length $tagblob] - 12}]
     79   append tagblob [binary format a4iuiux$fullen DIFR $fullen 0]
     80   return $tagblob
     81 }
     82 
     83 
     84 # parse the argument
     85 set devinfofile ""
     86 if {[llength $argv] > 0} {
     87   set devinfofile [lindex $argv 0]
     88 } else {
     89   puts "No devinfo file specified"
     90   exit 1
     91 }
     92 if {![file exists $devinfofile]} {
     93   puts "Invalid devinfo file specified"
     94   exit 1
     95 }
     96 
     97 # read the devinfo partition
     98 set fp [open $devinfofile r]
     99 fconfigure $fp -translation binary -buffering none
    100 set infodata [read $fp]
    101 close $fp
    102 
    103 # parse the basic info
    104 if {[string length $infodata] != 8192} {
    105   puts "Invalid devinfo file length (must be 8192 bytes)"
    106   exit 1
    107 }
    108 if {[string first "DEVI" $infodata] ne 0} {
    109   puts "Invalid devinfo file magic (must be DEVI)"
    110   exit 1
    111 }
    112 # the infomap has the format {name offset:formattype name2 offset2:formattype2 ...}
    113 set infomap {
    114   magic 0:a4
    115   ver_major 4:su
    116   ver_minor 6:su
    117   board_project 40:su
    118   board_stage_code 42:cu
    119   board_rev_hi 43:cu
    120   board_rev_lo 44:cu
    121   board_variant 45:cu
    122   slota_retrycount 48:cu
    123   slota_flags 49:cu
    124   slotb_retrycount 52:cu
    125   slotb_flags 53:cu
    126 }
    127 array set info {}
    128 dict for {key val} $infomap {
    129   set vparts [split $val :]
    130   set offset [lindex $vparts 0]
    131   set fmt [lindex $vparts 1]
    132   set scanval [string range $infodata $offset [expr {$offset + 4}]]
    133   binary scan $scanval $fmt info($key)
    134 }
    135 
    136 puts "==========================="
    137 puts "   PixeLycan by Luxferre"
    138 puts "==========================="
    139 puts "\nInput file: $devinfofile"
    140 puts "\n------- Info fields -------\n"
    141 puts "File magic: $info(magic)"
    142 puts "Major format version: $info(ver_major)"
    143 puts "Minor format version: $info(ver_minor)"
    144 puts "Board project version: [format %04X $info(board_project)]"
    145 puts "Board stage code: $info(board_stage_code)"
    146 puts "Board revision: [format %X $info(board_rev_hi)].[format %02X $info(board_rev_lo)]"
    147 puts "Board variant: $info(board_variant)"
    148 puts "Slot A retries: $info(slota_retrycount)"
    149 puts "Slot A flags: [format %02X $info(slota_flags)]"
    150 puts "Slot B retries: $info(slotb_retrycount)"
    151 puts "Slot B flags: [format %02X $info(slotb_flags)]"
    152 
    153 set taglist [scantags $infodata]
    154 
    155 puts "\n--------- PS tags ---------\n"
    156 dict for {key val} [dict get $taglist pstags] {
    157   if {$key ne ""} {
    158     set hexval [binary encode hex $val]
    159     puts "$key: $val (HEX:$hexval)"
    160   }
    161 }
    162 puts "\n------- PSENV tags --------\n"
    163 dict for {key val} [dict get $taglist psenvtags] {
    164   if {$key ne ""} {
    165     set hexval [binary encode hex $val]
    166     puts "$key: $val (HEX:$hexval)"
    167   }
    168 }
    169 puts "\n---------------------------\n"
    170 
    171 # read the additional arguments as key/value dictionary to set
    172 if {[llength $argv] > 1} {
    173   set mode [lindex $argv 1]
    174   set stoffset 0
    175   set maxlen 0
    176   set activedict {}
    177   # the mode can be setinfofield, setps or setpsenv, start offset depends on it
    178   if {$mode eq "setinfofield"} { # only one infofield can be set at a time
    179     if {[llength $argv] > 3} {
    180       set targetname [lindex $argv 2]
    181       if {![dict exists $infomap $targetname]} {
    182         puts "Invalid info field name!"
    183         puts "Valid field names: [dict keys $infomap]"
    184         exit 1
    185       }
    186       puts "Setting the $targetname info field"
    187       set targetparams [split [dict get $infomap $targetname] :]
    188       set targetoffset [lindex $targetparams 0]
    189       set targetfmt [lindex $targetparams 1]
    190       set fmtlen 1
    191       if {$targetfmt eq "su"} {set fmtlen 2}
    192       binary scan [binary decode hex [string trim [lindex $argv 3]]] $targetfmt targetvalue
    193       set finalvalue [binary format $targetfmt $targetvalue]
    194       set infodata [string replace $infodata $targetoffset [expr {$fmtlen + int($targetoffset) - 1}] $finalvalue]
    195       puts "Saving the devinfo image to $devinfofile..."
    196       set fp [open $devinfofile w]
    197       fconfigure $fp -translation binary -buffering none
    198       puts -nonewline $fp $infodata
    199       close $fp
    200       puts "Devinfo file saved"
    201       exit 0
    202     } else {
    203       puts {Invalid setinfofield parameters!}
    204       puts {Usage: pxlycan.tcl [devinfofile] setinfofield [name] [hexvalue]}
    205       puts "Valid field names: [dict keys $infomap]"
    206     }
    207   } elseif {$mode eq "setps"} {
    208     set stoffset 128
    209     set maxlen 3968
    210     set activedict [dict get $taglist pstags]
    211   } elseif {$mode eq "setpsenv"} {
    212     set stoffset 4096
    213     set maxlen 4096
    214     set activedict [dict get $taglist psenvtags]
    215   } else {
    216     puts "Invalid setting mode, please choose setinfofield, setps or setpsenv!"
    217     exit 1
    218   }
    219   if {[llength $argv] > 2} {
    220     set params [lrange $argv 2 end]
    221     dict for {ckey val} $params {
    222       if {[string match DI??_* $ckey]} {
    223         puts "($mode) Setting tag $ckey"
    224         if {[string match HEX:* $val]} { # direct hex value
    225           set val [string range $val 4 end]
    226           dict set activedict $ckey [binary decode hex $val]
    227         } else { # string value
    228           dict set activedict $ckey [string cat $val \0]
    229         }
    230       }
    231     }
    232     puts "Building blob..."
    233     set tagblob [buildtagblob $activedict $maxlen]
    234     puts "Writing blob to devinfo..."
    235     set infodata [string replace $infodata $stoffset [expr {$maxlen + $stoffset - 1}] $tagblob]
    236     puts "Saving the devinfo image to $devinfofile..."
    237     set fp [open $devinfofile w]
    238     fconfigure $fp -translation binary -buffering none
    239     puts -nonewline $fp $infodata
    240     close $fp
    241     puts "Devinfo file saved"
    242   }
    243 }
    244