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