-
-
Save joevt/32e5efffe3459958759fb702579b9529 to your computer and use it in GitHub Desktop.
#!/bin/bash | |
#!/bin/zsh | |
# by joevt May 24/2023 | |
#========================================================================================= | |
edid_decode=edid-decode | |
#========================================================================================= | |
# Modify EDID | |
getarrstart () { | |
# bash arrays start at 0 | |
# zsh arrays start at 1 (applies only to [] syntax) but this can be changed with "setopt ksh_arrays" | |
# zsh arrays start at 0 when using ${arr:x:x} syntax | |
local arr=(1 0) | |
arrstart=${arr[1]} | |
} | |
getarrstart | |
if ((0)); then | |
# to clear errors in ShellCheck | |
debug=1 | |
dodump=1 | |
afterdetailedblockoffset=0 | |
doclearserialnumber=0 | |
docleardate=0 | |
do8bpc=0 | |
doRGB=0 | |
do422=0 | |
dosetpreferredisnative=0 | |
doclearchromaticity=0 | |
doclearestablishedtimings=0 | |
doclearmanufacturerstimings=0 | |
doclearstandardtimings=0 | |
patches=() | |
fi | |
replacebytes () { | |
local bytepos=$(($1*2)) | |
local thebytes=$2 | |
local thelen=${#thebytes} | |
[[ -n $3 ]] && thelen=$(($3*2)) | |
theedid=${theedid:0:$bytepos}${thebytes}${theedid:$bytepos+thelen} | |
} | |
repairchecksums () { | |
local blockoffset=0 | |
while (( blockoffset*2 < ${#theedid} )); do | |
local blocktag=${theedid:$blockoffset*2:2} | |
if [[ $blocktag = 70 ]]; then | |
local sectionsize=$((0x${theedid:$blockoffset*2+4:2})) | |
replacebytes $((blockoffset+sectionsize+5)) "$(printf "%02x" $(( -($(echo -n "${theedid:$blockoffset*2+2:$sectionsize*2+8}" | sed "s/../+0x&/g")) & 0xff )))" | |
fi | |
replacebytes $((blockoffset+127)) "$(printf "%02x" $(( -($(echo -n "${theedid:$blockoffset*2:254}" | sed "s/../+0x&/g")) & 0xff )))" | |
((blockoffset+=128)) | |
done | |
} | |
deleteextensionblock () { | |
local blockoffset=$(($1*128)) | |
if (( blockoffset*2 < ${#theedid} )); then | |
(( debug )) && echo ": deleteextensionblock $blockoffset" 1>&2 | |
replacebytes $blockoffset "" 128 | |
replacebytes 126 "$(printf "%02x" $((0x${theedid:252:2}-1)))" | |
processedid | |
repairchecksums | |
else | |
echo "Block at $blockoffset doesn't exist" | |
fi | |
} | |
deleteextensionblocktype () { | |
local blocktype="$1" | |
local blockoffset=128 | |
local blocknum=1 | |
while (( blockoffset*2 < ${#theedid} )); do | |
local blocktag=${theedid:$blockoffset*2:2} | |
(( debug )) && echo ": deleteextensionblocktype $blocknum) $blockoffset $blocktag" 1>&2 | |
if (( 0x$blocktag == blocktype )); then | |
deleteextensionblock $blocknum | |
else | |
((blockoffset+=128)) | |
((blocknum++)) | |
fi | |
done | |
} | |
adddisplayidextensionblock () { | |
local version="$1" | |
local producttype="$2" | |
(( debug )) && echo ": adddisplayidextensionblock $version $producttype" 1>&2 | |
#echo adddisplayidextensionblock $version $producttype | |
replacebytes 126 "$(printf "%02x" $((0x${theedid:252:2}+1)))" | |
theedid+=$(printf "70%02x79%02x%0248x" $((0x${version//./})) "$producttype" 0) | |
repairchecksums | |
} | |
addctaextensionblock () { | |
local version="$1" | |
local YCbCrSupportbyte="$2" | |
local offset="$3" | |
if [[ -z $YCbCrSupportbyte ]]; then | |
YCbCrSupportbyte=4 | |
fi | |
if [[ -z $offset ]]; then | |
offset=$((${#theedid} / 2)) | |
fi | |
(( debug )) && echo ": addctaextensionblock $version" 1>&2 | |
#echo addctaextensionblock $version $producttype | |
replacebytes 126 "$(printf "%02x" $((0x${theedid:252:2}+1)))" | |
# theedid=${theedid:0:$offset*2}$(printf "02%02x0400%0248x" $((version)) 0)${theedid:$offset*2} | |
# 00 edid tag 02 | |
# | |
# 01 1 CTA version | |
# 02 2 detailed descriptors offset | |
# 03 3 YCbCrSupportbyte | |
theedid=${theedid:0:$offset*2}$(printf "02%02x04%02x%0248x" $((version)) $((YCbCrSupportbyte)) 0)${theedid:$offset*2} | |
repairchecksums | |
} | |
detailed_block () { | |
local detailedblock=$1 | |
# other parameters are optional | |
local detailedblockoffset=$2 | |
local willbereplaced=$3 | |
local willbedeleted=$4 | |
[[ -z $willbereplaced ]] && willbereplaced=1 # just output it | |
[[ -z $willbedeleted ]] && willbedeleted=1 # just output it | |
local dodump=$dodump | |
if (( $# == 1 )); then | |
dodump=1 | |
fi | |
if [[ ${detailedblock:0:4} = 0000 ]]; then | |
case ${detailedblock} in | |
000000000000000000000000000000000000) (( dodump )) && printf "Empty" ;; | |
000000100000000000000000000000000000) (( dodump )) && printf "Dummy block" ;; | |
*) | |
case ${detailedblock:6:2} in | |
0*) (( dodump )) && printf "Manufacturer-specified data" ;; | |
10) (( dodump )) && printf "Dummy block" ;; | |
f7) (( dodump )) && printf "Established timings III" ;; | |
f8) (( dodump )) && printf "CVT 3-byte timing codes" ;; | |
f9) (( dodump )) && printf "Color management data" ;; | |
fa) (( dodump )) && printf "More standard timings" ;; | |
fb) (( dodump )) && printf "Color point" ;; | |
fc) (( dodump )) && printf "Monitor name" ;; | |
fd) (( dodump )) && printf "Display range limits" ;; | |
fe) (( dodump )) && printf "ASCII string" ;; | |
ff) (( dodump )) && printf "Serial number" ;; | |
*) (( dodump )) && printf "Reserved(%s)" "${detailedblock:6:2}" ;; | |
esac | |
(( dodump )) && printf ": %s" "$detailedblock" | |
esac | |
case ${detailedblock:6:2} in | |
fd) | |
local minV=$((((0x${detailedblock:8:2} >> 0) & 1) * 255 + 0x${detailedblock:10:2})) | |
local maxV=$((((0x${detailedblock:8:2} >> 1) & 1) * 255 + 0x${detailedblock:12:2})) | |
local minH=$((((0x${detailedblock:8:2} >> 2) & 1) * 255 + 0x${detailedblock:14:2})) | |
local maxH=$((((0x${detailedblock:8:2} >> 3) & 1) * 255 + 0x${detailedblock:16:2})) | |
local maxP=$((0x${detailedblock:18:2} * 10)) | |
local timingtype="?" | |
case $((0x${detailedblock:20:2})) in | |
0) timingtype="default GTF" ;; | |
1) timingtype="limits only" ;; | |
2) timingtype="Secondary GTF" ;; | |
4) timingtype="CVT" ;; | |
*) timingtype="Reserved($((0x${detailedblock:20:2})))" ;; | |
esac | |
(( dodump )) && printf " = %d-%dHz %d-%dkHz %dMHz (%s:%s)\n" $minV $maxV $minH $maxH $maxP "$timingtype" "${detailedblock:22}" | |
;; | |
*) (( dodump )) && echo | |
;; | |
esac | |
else | |
local MHz=00$((0x${detailedblock:2:2}${detailedblock:0:2})) | |
local hmm=$((0x${detailedblock:28:1}${detailedblock:24:2})) | |
local vmm=$((0x${detailedblock:29:1}${detailedblock:26:2})) | |
local hactive=$((0x${detailedblock:8:1}${detailedblock:4:2})) | |
local hfront=$((((0x${detailedblock:22:1} >> 2) << 8) + 0x${detailedblock:16:2})) | |
local hpulse=$((((0x${detailedblock:22:1} & 3) << 8) + 0x${detailedblock:18:2})) | |
local hblank=$((0x${detailedblock:9:1}${detailedblock:6:2})) | |
local vactive=$((0x${detailedblock:14:1}${detailedblock:10:2})) | |
local vfront=$((((0x${detailedblock:23:1} >> 2) << 4) + 0x${detailedblock:20:1})) | |
local vpulse=$((((0x${detailedblock:23:1} & 3) << 4) + 0x${detailedblock:21:1})) | |
local vblank=$((0x${detailedblock:15:1}${detailedblock:12:2})) | |
local hborder=$((0x${detailedblock:30:2})) | |
local vborder=$((0x${detailedblock:32:2})) | |
local hsync='-' | |
local vsync='-' | |
local interlaced='' | |
local hback=$((hblank - hfront - hpulse)) | |
local vback=$((vblank - vfront - vpulse)) | |
(( 0x${detailedblock:34:2} >> 7 )) && interlaced='i' | |
(( 0x${detailedblock:34:2} & 2 )) && hsync='+' | |
(( 0x${detailedblock:34:2} & 4 )) && vsync='+' | |
local kHz='000' | |
local Hz='000' | |
if (( hactive + hblank )); then | |
kHz=000$(((10#$MHz * 100000 / (hactive+hblank) + 5)/10)) | |
fi | |
if (( (vactive+vblank)*(hactive+hblank) )); then | |
Hz=000$(((10#$MHz * 100000000 / ((vactive+vblank)*(hactive+hblank)) + 5)/10)) | |
fi | |
local timingstring="" | |
timingstring=$( | |
printf "%dx%d%s@%d.%sHz %d.%skHz %d.%sMHz h(%d %d %d %s) v(%d %d %d %s)" \ | |
$hactive $vactive "$interlaced" \ | |
$((10#$Hz/1000)) ${Hz: -3} $((10#$kHz/1000)) ${kHz: -3} $((10#$MHz/100)) ${MHz: -2} \ | |
$hfront $hpulse $hback "$hsync" \ | |
$vfront $vpulse $vback "$vsync" | |
) | |
(( debug )) && echo ":(( $willbereplaced == 0 ))" 1>&2 | |
if (( willbereplaced == 0 && willbedeleted == 0 )); then | |
thetimings+=("$timingstring") | |
thetimingsoffsets+=("$detailedblockoffset") | |
thetimingshex+=("$detailedblock") | |
fi | |
(( dodump )) && printf "Detailed timing: %s = %s\n" "$detailedblock" "$timingstring" | |
fi | |
} | |
one_detailed_block () { | |
(( debug )) && echo ": one_detailed_block $*" 1>&2 | |
local detailedblockoffset=$1 | |
local willbereplaced=$2 | |
local willbedeleted=$3 | |
(( dodump )) && echo -n " ${detailedblockoffset}) " | |
detailed_block "${theedid:$detailedblockoffset*2:36}" "$detailedblockoffset" "$willbereplaced" "$willbedeleted" | |
} | |
detailed_blocks () { | |
(( debug )) && echo ": detailed_blocks $*" 1>&2 | |
local param="" | |
for param in detailedblockoffset afterdetailedblockoffset dodump doreplacedescriptoroffset deletedescriptoroffset replacementdescriptor; do | |
(( debug )) && echo ": param $param=$1" 1>&2 | |
eval "local $param=\"$1\""; shift | |
done | |
(( afterdetailedblockoffset = detailedblockoffset + (afterdetailedblockoffset - detailedblockoffset) / 18 * 18 )) | |
if (( detailedblockoffset + 18 <= afterdetailedblockoffset )); then | |
(( dodump )) && echo " Descriptors:" | |
while (( detailedblockoffset + 18 <= afterdetailedblockoffset )); do | |
local willbereplaced=$(( detailedblockoffset == doreplacedescriptoroffset )) | |
local willbedeleted=$(( detailedblockoffset == deletedescriptoroffset )) | |
one_detailed_block "$detailedblockoffset" "$willbereplaced" "$willbedeleted" | |
if (( willbereplaced )); then | |
replacebytes "$detailedblockoffset" "$replacementdescriptor" | |
(( dodump )) && echo " changed:" | |
one_detailed_block "$detailedblockoffset" 0 0 | |
fi | |
if (( willbedeleted )); then | |
deletedescriptoroffset=0 | |
replacebytes "$detailedblockoffset" "${theedid:(detailedblockoffset+18)*2:(afterdetailedblockoffset-detailedblockoffset-18)*2}""$(printf "000000%c00000000000000000000000000000" $((detailedblockoffset < 128)) )" | |
(( dodump )) && echo " changed: deleted" | |
else | |
((detailedblockoffset+=18)) | |
fi | |
done | |
fi | |
} | |
colorc () { | |
local ccc=$1 | |
# [0..1023] | |
# [0..9990] | |
# [10005..19995] add leading zeros (ignore the 1) and rounding | |
local rx=$(( | |
( | |
( | |
(0x${theedid:54+ccc*2:2} << 2) | ((0x${theedid:50:4} >> (14-ccc*2)) & 3) | |
) * 10000 / 1024 | |
) + 10005 | |
)); | |
echo -n 0.${rx:1:3} # output 3 digits (ignoring the leading 1) | |
} | |
dumptype1timingdescriptor () { | |
local type1timing=$1 | |
local MHz=00$((0x${type1timing:4:2}${type1timing:2:2}${type1timing:0:2} + 1)) | |
local preferred='' | |
(( 0x${type1timing:6:2} >> 7 )) && preferred=' preferred' | |
local support3D=' 3D:undefined' | |
case $(( (0x${type1timing:6:2} >> 5) & 3 )) in | |
0) support3D='' ;; | |
1) support3D=' stereo' ;; | |
2) support3D=' stereo/mono' ;; | |
3) support3D=' 3D:reserved' ;; | |
esac | |
local interlaced='' | |
(( (0x${type1timing:6:2} >> 4) & 1 )) && interlaced='i' | |
local aspect=' aspect:reserved' | |
case $(( 0x${type1timing:6:2} & 15 )) in | |
0) aspect=' 1:1' ;; | |
1) aspect=' 5:4' ;; | |
2) aspect=' 4:3' ;; | |
3) aspect=' 15:9' ;; | |
4) aspect=' 16:9' ;; | |
5) aspect=' 16:10' ;; | |
6) aspect=' 64:27' ;; | |
7) aspect=' 256:135' ;; | |
8) aspect=' aspect:undefined' ;; | |
esac | |
local hactive=$((0x${type1timing:10:2}${type1timing:8:2} + 1)) | |
local hblank=$((0x${type1timing:14:2}${type1timing:12:2} + 1)) | |
local hfront=$(((0x${type1timing:18:2}${type1timing:16:2} & 32767) + 1)) | |
local hsync='-' | |
(( 0x${type1timing:18:2} >> 7 )) && hsync='+' | |
local hpulse=$((0x${type1timing:22:2}${type1timing:20:2} + 1)) | |
local hback=$((hblank - hfront - hpulse)) | |
local vactive=$((0x${type1timing:26:2}${type1timing:24:2} + 1)) | |
local vblank=$((0x${type1timing:30:2}${type1timing:28:2} + 1)) | |
local vfront=$(((0x${type1timing:34:2}${type1timing:32:2} & 32767) + 1)) | |
local vsync='-' | |
(( 0x${type1timing:34:2} >> 7 )) && vsync='+' | |
local vpulse=$((0x${type1timing:38:2}${type1timing:36:2} + 1)) | |
local vback=$((vblank - vfront - vpulse)) | |
local kHz=000$(((10#$MHz * 100000 / (hactive+hblank) + 5)/10)) | |
local Hz=000$(((10#$MHz * 100000000 / ((vactive+vblank)*(hactive+hblank)) + 5)/10)) | |
printf "%dx%d%s@%d.%sHz %d.%skHz %d.%sMHz h(%d %d %d %s) v(%d %d %d %s)%s%s%s" \ | |
$hactive $vactive "$interlaced" \ | |
$((10#$Hz/1000)) ${Hz: -3} $((10#$kHz/1000)) ${kHz: -3} $((10#$MHz/100)) ${MHz: -2} \ | |
$hfront $hpulse $hback "$hsync" \ | |
$vfront $vpulse $vback "$vsync" \ | |
"$support3D" "$aspect" "$preferred" | |
} | |
dumptileddisplaytopologyblock () { | |
local tiledblock=$1 | |
local revision=$((0x${tiledblock:2:2} & 7)) # high 5 bits should be 0 | |
local length=$((0x${tiledblock:4:2})) # should be 22 | |
local capabilities=$((0x${tiledblock:6:2})) | |
local enclosuretype=$(( (capabilities >> 7) & 1 )) | |
local tilebezelinformationdescriptoravailable=$(( (capabilities >> 6) & 1 )) | |
# bit 5 reserved | |
local behaviorsome=$(( (capabilities >> 3) & 3 )) | |
local behavioronly=$(( (capabilities >> 0) & 7 )) | |
tileCountH=$(( 0x${tiledblock:8:1} + ((0x${tiledblock:12:2} >> 2) & 0x30) + 1 )) | |
tileCountV=$(( 0x${tiledblock:9:1} + ((0x${tiledblock:12:2} >> 0) & 0x30) + 1 )) | |
local tileLocationH=$(( 0x${tiledblock:10:1} + ((0x${tiledblock:12:2} << 2) & 0x30) )) | |
local tileLocationV=$(( 0x${tiledblock:11:1} + ((0x${tiledblock:12:2} << 4) & 0x30) )) | |
tileSizeH=$(( 0x${tiledblock:16:2}${tiledblock:14:2} + 1 )) | |
tileSizeV=$(( 0x${tiledblock:20:2}${tiledblock:18:2} + 1 )) | |
# depends on tilebezelinformationdescriptoravailable | |
local pixelmultiplier=$(( 0x${tiledblock:22:2} )) | |
local bezelsizetop=$(( 0x${tiledblock:24:2} )) | |
local bezelsizebottom=$(( 0x${tiledblock:26:2} )) | |
local bezelsizeleft=$(( 0x${tiledblock:28:2} )) | |
local bezelsizeright=$(( 0x${tiledblock:30:2} )) | |
local vendorid="" | |
vendorid=$(echo -n "${tiledblock:32:6}" | xxd -p -r) | |
local productid=$((0x${tiledblock:40:2}${tiledblock:38:2})) | |
local serialnumber=$((0x${tiledblock:48:2}${tiledblock:46:2}${tiledblock:44:2}${tiledblock:42:2})) | |
if (( dodump )); then | |
printf "%dx%d @ (%d,%d) of (%dx%d) vendor:%s product:0x%04x serial:%d" \ | |
$tileSizeH $tileSizeV \ | |
$tileLocationH $tileLocationV \ | |
$tileCountH $tileCountV \ | |
"$vendorid" $productid $serialnumber | |
printf ' enclosure:' | |
case $enclosuretype in | |
0) printf 'multiple' ;; | |
1) printf 'single' ;; | |
esac | |
printf ' some:' | |
case $behaviorsome in | |
0) printf 'undescribed' ;; | |
1) printf 'location' ;; | |
*) printf 'reserved%s' $behaviorsome ;; | |
esac | |
printf ' one:' | |
case $behavioronly in | |
0) printf 'undescribed' ;; | |
1) printf 'location' ;; | |
2) printf 'scaled' ;; | |
3) printf 'cloned' ;; | |
*) printf 'reserved%s' $behavioronly ;; | |
esac | |
if (( tilebezelinformationdescriptoravailable )); then | |
printf " bezel(t,l,b,r):(%d,%d,%d,%d)x%d" \ | |
$bezelsizetop \ | |
$bezelsizeleft \ | |
$bezelsizebottom \ | |
$bezelsizeright \ | |
$pixelmultiplier | |
fi | |
fi | |
} | |
flagsstring () { | |
local label=$1 | |
local bitfirst=$2 | |
local bitlast=$3 | |
local bitdirection=1 | |
local theflags="$4" | |
local result="" | |
if [[ -z $theflags ]]; then | |
result="missing" | |
else | |
shift 4 | |
theflags=$((0x$theflags)) | |
(( bitlast < bitfirst )) && bitdirection=-1 | |
result="" | |
local thebit=$bitfirst | |
while (( 1 )); do | |
if (( theflags & (2 ** thebit) )); then | |
[[ -n $result ]] && result+="," | |
[[ -z $1 ]] && result+="reserved$thebit" || result+="$1" | |
fi | |
(( $# )) && shift | |
(( thebit == bitlast )) && break | |
(( thebit = thebit + bitdirection )) | |
done | |
fi | |
[[ -n $result ]] && echo -n " $label:$result" | |
} | |
dumpdisplayinterfacefeaturesdata () { | |
local theblock=$1 | |
local revision=$((0x${theblock:2:2} & 7)) # high 5 bits should be 0 | |
local length=$((0x${theblock:4:2})) # should be 9+N | |
local colorDepthsRGB="" | |
local colorDepthsYCbCr444="" | |
local colorDepthsYCbCr422="" | |
local colorDepthsYCbCr420="" | |
local audio="" | |
local colorspace_eotf_combinations1="" | |
local colorspace_eotf_combinations2="" | |
colorDepthsRGB=$(flagsstring "RGB" 0 7 "${theblock:6:2}" 6 8 10 12 14 16) | |
colorDepthsYCbCr444=$(flagsstring "444" 0 7 "${theblock:8:2}" 6 8 10 12 14 16) | |
colorDepthsYCbCr422=$(flagsstring "422" 0 7 "${theblock:10:2}" 8 10 12 14 16) | |
colorDepthsYCbCr420=$(flagsstring "420" 0 7 "${theblock:12:2}" 8 10 12 14 16) | |
local minRateYCbCr420string=" 420(MHz):missing" | |
if [[ -n ${theblock:14:2} ]]; then | |
local minRateYCbCr420=$((0x${theblock:14:2} * 7425)) # / 100 | |
(( minRateYCbCr420 > 0 )) && minRateYCbCr420string=" 420:≥$(( minRateYCbCr420/100 )).${minRateYCbCr420: -2}MHz" || minRateYCbCr420string="" | |
fi | |
audio=$(flagsstring "audio(kHz)" 7 0 "${theblock:16:2}" "32" "44.1" "48") | |
colorspace_eotf_combinations1=$(flagsstring "colorspace/eotf#1" 0 7 "${theblock:18:2}" \ | |
"sRGB" \ | |
"BT.601" \ | |
"BT.709/BT.1886" \ | |
"Adobe RGB" \ | |
"DCI-P3" \ | |
"BT.2020" \ | |
"BT.2020/SMPTE ST 2084") | |
colorspace_eotf_combinations2=$(flagsstring "colorspace/eotf#2" 0 7 "${theblock:20:2}") | |
local additional_colorspace_eotf="missing" | |
if [[ -n ${theblock:22:2} ]]; then | |
local num_additional_colorspace_eotf=$((0x${theblock:22:2} & 7)) | |
additional_colorspace_eotf="" | |
local cendx="" | |
for (( cendx = 0; cendx < num_additional_colorspace_eotf; cendx++ )); do | |
(( cendx > 0 )) && additional_colorspace_eotf+="," | |
local colorspace="" | |
local eotf="" | |
if [[ -z ${theblock:24+$cendx*2:2} ]]; then | |
additional_colorspace_eotf+="missing" | |
else | |
case $((0x${theblock:24+$cendx*2:2} >> 4)) in | |
0) colorspace="Undefined" ;; | |
1) colorspace="sRGB" ;; | |
2) colorspace="BT.601" ;; | |
3) colorspace="BT.709" ;; | |
4) colorspace="Adobe RGB" ;; | |
5) colorspace="DCI-P3" ;; | |
6) colorspace="BT.2020" ;; | |
7) colorspace="Custom" ;; | |
*) colorspace="Reserved$((0x${theblock:24+$cendx*2:2} >> 4))" ;; | |
esac | |
case $((0x${theblock:24+$cendx*2:2} & 15)) in | |
0) eotf="Undefined" ;; | |
1) eotf="sRGB" ;; | |
2) eotf="BT.601" ;; | |
3) eotf="BT.1886" ;; | |
4) eotf="Adobe RGB" ;; | |
5) eotf="DCI-P3" ;; | |
6) eotf="BT.2020" ;; | |
7) eotf="Gamma function" ;; | |
8) eotf="SMPTE ST 2084" ;; | |
9) eotf="Hybrid Log" ;; | |
10) eotf="Custom" ;; | |
*) eotf="Reserved$((0x${theblock:24+$cendx*2:2} & 15))" ;; | |
esac | |
[[ $colorspace = "$eotf" ]] && additional_colorspace_eotf+="$colorspace" || additional_colorspace_eotf+="$colorspace/$eotf" | |
fi | |
done | |
fi | |
local lengtherror="" | |
(( length != 9 + num_additional_colorspace_eotf )) && lengtherror+=" ($length != $((9 + num_additional_colorspace_eotf)))" | |
[[ -n $additional_colorspace_eotf ]] && additional_colorspace_eotf=" colorspace/eotf#n:$additional_colorspace_eotf" | |
if (( dodump )); then | |
printf "revision:%d%s%s%s%s%s%s%s%s%s%s" \ | |
$revision \ | |
"$colorDepthsRGB" \ | |
"$colorDepthsYCbCr444" \ | |
"$colorDepthsYCbCr422" \ | |
"$colorDepthsYCbCr420" \ | |
"$minRateYCbCr420string" \ | |
"$audio" \ | |
"$colorspace_eotf_combinations1" \ | |
"$colorspace_eotf_combinations2" \ | |
"$additional_colorspace_eotf" \ | |
"$lengtherror" | |
fi | |
} | |
processedid () { | |
(( debug )) && echo ": processedid $*" 1>&2 | |
local ctablocktypetodelete=":" | |
local newctablocks=() | |
local currentnewctablock=0 | |
local newctadescriptors=() | |
local currentnewctadescriptor=0 | |
local displayidblocktypetodelete=9999 | |
local newdisplayidblocks=() | |
local currentnewdisplayidblock=0 | |
while (( $# )); do | |
local param="$1"; shift | |
eval "local $param=1" | |
case "$param" in | |
doreplacedescriptor) | |
local doreplacedescriptoroffset="$1"; shift | |
local replacementdescriptor="$1"; shift ;; | |
doaddctablock) | |
local doaddctablockoffset="$1"; shift | |
if (( doaddctablockoffset == 0 )); then | |
doaddctablockoffset=$EndOfLastctablock | |
fi | |
(( debug )) && echo ": doaddctablock $doaddctablockoffset" 1>&2 | |
while (( $# )); do | |
(( debug )) && echo ": doaddctablock param: $1" 1>&2 | |
newctablocks+=("$1"); shift | |
done | |
;; | |
dodeletectablock) | |
local deletectablockoffset="$1"; shift ;; | |
dodeletectablocktype) | |
while [[ "$1" =~ [0-9a-f]+ ]]; do | |
ctablocktypetodelete="${ctablocktypetodelete}$1:"; shift | |
done | |
;; | |
doaddctadescriptor) | |
local doaddctadescriptoroffset="$1"; shift | |
if (( doaddctadescriptoroffset == 0 )); then | |
doaddctadescriptoroffset=$EndOfLastctadescriptor | |
fi | |
(( debug )) && echo ": doaddctadescriptor $doaddctadescriptoroffset" 1>&2 | |
while (( $# )); do | |
(( debug )) && echo ": doaddctadescriptor param: $1" 1>&2 | |
newctadescriptors+=("$1"); shift | |
done | |
;; | |
dodeletedescriptor) | |
local deletedescriptoroffset="$1"; shift ;; | |
doadddisplayidblock) | |
local doadddisplayidblockoffset="$1"; shift | |
if (( doadddisplayidblockoffset == 0 )); then | |
doadddisplayidblockoffset=$EndOfLastDisplayIDblock | |
fi | |
(( debug )) && echo ": doadddisplayidblock $doadddisplayidblockoffset" 1>&2 | |
while (( $# )); do | |
(( debug )) && echo ": doadddisplayidblock param: $1" 1>&2 | |
newdisplayidblocks+=("$1"); shift | |
done | |
;; | |
dodeletedisplayidblock) | |
local deletedisplayidblockoffset="$1"; shift ;; | |
dodeletedisplayidblockblocktype) | |
local displayidblocktypetodelete="$1"; shift ;; | |
dosetpreferredisnative) | |
preferredvalue="$1"; shift ;; | |
esac | |
done | |
# Remember the end of the last CTA block (-1 means no CTA blocks exist. | |
# It is also used by addctablock for inserting new CTA blocks at the first available location. | |
EndOfLastctablock=-1 | |
# Keep a list of found CTA blocks. | |
ctablocks=() | |
# Remember the end of the last CTA descriptor block (-1 means no CTA blocks exist. | |
# It is also used by addctadescriptor for inserting new CTA blocks at the first available location. | |
EndOfLastctadescriptor=-1 | |
# Keep a list of found CTA blocks. | |
ctadescriptors=() | |
# Remember the end of the last DisplayID block (-1 means no DisplayID blocks exist. | |
# It is also used by adddisplayidblock for inserting new DisplayID blocks at the first available location. | |
EndOfLastDisplayIDblock=-1 | |
# Keep a list of found DisplayID blocks. | |
DisplayIDblocks=() | |
# Keep a list of found timings and their offsets. | |
thetimings=() | |
thetimingsoffsets=() | |
thetimingshex=() | |
HasAudio=0 | |
HasTile=0 | |
(( debug )) && echo ": parse 1" 1>&2 | |
theproductid=$((0x${theedid:22:2}${theedid:20:2})) | |
thevendorid=$((0x${theedid:16:4})) | |
(( debug )) && echo ": parse 2" 1>&2 | |
thevendorcode="$(printf "%06x" $((((thevendorid&0x7c00)<<6)+((thevendorid&0x3e0)<<3)+(thevendorid&0x1f)+0x404040)) | xxd -p -r)" | |
(( debug )) && echo ": parse 2.1" 1>&2 | |
theweekofmanufacture=$((0x${theedid:32:2})) | |
(( debug )) && echo ": parse 2.2" 1>&2 | |
theyearofmanufacture=$((0x${theedid:34:2}+1990)) | |
(( debug )) && echo ": parse 3" 1>&2 | |
thevendordir="DisplayVendorID-$(printf "%x" $thevendorid)" | |
theproductfile="${thevendordir}/DisplayProductID-$(printf "%x" $theproductid)" | |
themanufacturefile="${thevendordir}/DisplayYearManufacture-$theyearofmanufacture-DisplayWeekManufacture-$theweekofmanufacture" | |
thefilenamebase="$(printf "EDID_%s_%x_%x" "$thevendorcode" $thevendorid $theproductid)" | |
(( debug )) && echo ": parse 4" 1>&2 | |
theEDIDversion=$((0x${theedid:0x12*2:2})).$((0x${theedid:0x13*2:2})) # 1.3 or 1.4 | |
isdigital=$(( 0x${theedid:0x14*2:1} > 7 )) # 0 or 1 | |
colordepth=$(( 0x${theedid:0x14*2:1} & 7 )) | |
featuresupportbyte=$((0x${theedid:0x18*2:2})) | |
featuresupport=$(( (featuresupportbyte >> 3) & 3 )) | |
preferredisnative=$(( (featuresupportbyte >> 1) & 1 )) | |
iscontinuous=$(( (featuresupportbyte >> 0) & 1 )) | |
(( dodump )) && echo "0) EDID $theEDIDversion" | |
(( dodump )) && printf " Vendor ID: %s %d = 0x%x\n" "$thevendorcode" $thevendorid $thevendorid | |
(( dodump )) && printf " Product ID: %d = 0x%x\n" $theproductid $theproductid | |
if [[ ${theedid:24:8} != 00000000 ]]; then | |
(( dodump )) && printf " Serial Number: %d" $((0x${theedid:30:2}${theedid:28:2}${theedid:26:2}${theedid:24:2})) | |
if (( doclearserialnumber )); then | |
replacebytes 12 00000000 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
if [[ ${theedid:32:4} != 0000 ]]; then | |
if (( dodump )); then | |
case ${theedid:32:4} in | |
00*) printf " Made in year %s" "$theyearofmanufacture" ;; | |
ff*) printf " Model year: %s" "$theyearofmanufacture" ;; | |
*) printf " Made in week %s of %s" "$theweekofmanufacture" "$theyearofmanufacture" | |
esac | |
fi | |
if (( docleardate )); then | |
replacebytes 16 0000 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
(( dodump )) && printf " " | |
if [[ " $theEDIDversion" < " 1.4" ]]; then | |
(( dodump )) && | |
case $featuresupport in | |
0) echo "Monochrome or Grayscale" ;; | |
1) echo "RGB color" ;; | |
2) echo "Non-RGB color" ;; | |
3) echo "Undefined" ;; | |
esac | |
else | |
(( dodump )) && | |
case $isdigital$featuresupport in | |
00) echo "Monochrome or Grayscale" ;; | |
01) echo "RGB color" ;; | |
02) echo "Non-RGB color" ;; | |
03) echo "Undefined" ;; | |
10) printf "RGB 4:4:4" ;; | |
11) printf "RGB 4:4:4, YCrCb 4:4:4" ;; | |
12) printf "RGB 4:4:4, YCrCb 4:2:2" ;; | |
13) printf "RGB 4:4:4, YCrCb 4:4:4, YCrCb 4:2:2" ;; | |
esac | |
if (( isdigital )); then | |
(( dodump )) && | |
case $colordepth in | |
0) echo "; undefined bpc" ;; | |
1) echo "; 6 bpc" ;; | |
2) echo "; 8 bpc" ;; | |
3) echo "; 10 bpc" ;; | |
4) echo "; 12 bpc" ;; | |
5) echo "; 14 bpc" ;; | |
6) echo "; 16 bpc" ;; | |
7) echo "; 18 bpc error" ;; | |
esac | |
if (( do8bpc )); then | |
replacebytes 20 "$(printf "%02x" $(((0x${theedid:0x14*2:2} & ~0x70) | 0x20)))" | |
(( dodump )) && echo ' changed: 8 bpc' | |
fi | |
fi | |
if (( doRGB )); then | |
local newfeaturesupportbyte=$(((featuresupportbyte & ~0x18) | (0 << 3) )) | |
replacebytes 24 "$(printf "%02x" $newfeaturesupportbyte)" | |
(( dodump )) && | |
case $isdigital in | |
0) echo ' changed: Monochrome or Grayscale' ;; | |
1) echo ' changed: RGB 4:4:4' ;; | |
esac | |
fi | |
if (( do422 )); then | |
local newfeaturesupportbyte=$(((featuresupportbyte & ~0x18) | (3 << 3) )) | |
replacebytes 24 "$(printf "%02x" $newfeaturesupportbyte)" | |
(( dodump )) && | |
case $isdigital in | |
0) echo ' changed: Undefined' ;; | |
1) echo ' changed: RGB 4:4:4, YCrCb 4:4:4, YCrCb 4:2:2' ;; | |
esac | |
fi | |
fi | |
(( dodump )) && | |
case $preferredisnative in | |
1) echo " Preferred Timing Mode is native pixel format and preferred refresh rate" ;; | |
esac | |
if (( dosetpreferredisnative && (preferredvalue != preferredisnative) )); then | |
local newfeaturesupportbyte=$(((featuresupportbyte & ~2) | (preferredvalue << 1) )) | |
replacebytes 24 "$(printf "%02x" $newfeaturesupportbyte)" | |
(( dodump )) && echo ' changed' | |
fi | |
(( dodump )) && | |
case $iscontinuous in | |
0) echo " Display is non-continuous frequency" ;; | |
1) echo " Display is continuous frequency" ;; | |
esac | |
if [[ ${theedid:50:20} != 00000000000000000000 ]]; then | |
(( dodump )) && printf " Color characteristics: R(%s,%s) G(%s,%s) B(%s,%s) W(%s,%s)" "$(colorc 0)" "$(colorc 1)" "$(colorc 2)" "$(colorc 3)" "$(colorc 4)" "$(colorc 5)" "$(colorc 6)" "$(colorc 7)" | |
if (( doclearchromaticity )); then | |
replacebytes 25 00000000000000000000 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
if [[ ${theedid:70:4} != 0000 ]]; then | |
(( dodump )) && printf " Established timings: %s" "${theedid:70:4}" | |
if (( doclearestablishedtimings )); then | |
replacebytes 35 0000 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
if [[ ${theedid:74:2} != 00 ]]; then | |
(( dodump )) && printf " Manufacturer's timings: %s" "${theedid:74:2}" | |
if (( doclearmanufacturerstimings )); then | |
replacebytes 37 00 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
if [[ ${theedid:76:32} != 01010101010101010101010101010101 ]]; then | |
(( dodump )) && printf " Standard timings: %s" "${theedid:76:32}" | |
if (( doclearstandardtimings )); then | |
replacebytes 38 01010101010101010101010101010101 | |
(( dodump )) && printf " changed: unspecified" | |
fi | |
(( dodump )) && echo | |
fi | |
detailed_blocks 54 126 "$dodump" "$doreplacedescriptoroffset" "$deletedescriptoroffset" "$replacementdescriptor" | |
local blockoffset=128 | |
while (( blockoffset*2 < ${#theedid} )); do | |
(( debug )) && echo ": extension block $blockoffset" 1>&2 | |
local theblock=${theedid:$blockoffset*2:254} | |
(( debug )) && echo ": extension theblock = $theblock" 1>&2 | |
local blocktag=${theblock:0:2} | |
(( debug )) && echo ": blocktag = $blocktag" 1>&2 | |
if [[ $blocktag = 02 ]]; then | |
(( debug )) && echo ": parsing CTA extension block" 1>&2 | |
# 00 edid tag 02 | |
# | |
# 01 1 CTA version | |
# 02 2 detailed descriptors offset | |
# 03 3 YCbCrSupportbyte | |
# | |
# 04 00 CTA data block #1 3 bit type (0-7), 5 bit length - 1 (1-32) | |
# 05 01 CTA data block #1 extended tag code if type is 7 | |
# | |
# detailedTimingDescriptorsOffset 18 byte descriptors | |
# | |
# 7F EDID checksum | |
CTAversion=$((0x${theblock:2:2})) | |
(( debug )) && echo ": CTAversion = $CTAversion" 1>&2 | |
local detailedTimingDescriptorsOffset=$((0x${theblock:4:2})) | |
local afterctadescriptorblockoffset=127 | |
YCbCrSupportbyte=$((0x${theblock:6:2})) | |
if (( CTAversion > 1 )); then | |
(( dodump )) && echo "$blockoffset) CTA-861 extension block with new version $CTAversion" | |
YCbCrSupport=$((( YCbCrSupportbyte >> 4) & 3)) | |
(( dodump )) && | |
case $YCbCrSupport in | |
0) echo " No YCbCr support" ;; | |
1) echo " YCbCr 4:2:2" ;; | |
2) echo " YCbCr 4:4:4" ;; | |
3) echo " YCbCr 4:4:4, YCbCr 4:2:2" ;; | |
esac | |
if (( doRGB )); then | |
newYCbCrSupportbyte=$(((YCbCrSupportbyte & ~0x30) | (0 << 4) )) | |
replacebytes $((blockoffset+3)) "$(printf "%02x" $newYCbCrSupportbyte)" | |
(( dodump )) && echo ' changed: No YCbCr support' | |
fi | |
if (( do422 )); then | |
newYCbCrSupportbyte=$(((YCbCrSupportbyte & ~0x30) | (3 << 4) )) | |
replacebytes $((blockoffset+3)) "$(printf "%02x" $newYCbCrSupportbyte)" | |
(( dodump )) && echo ' changed: YCbCr 4:4:4, YCbCr 4:2:2' | |
fi | |
BasicAudioSupport=$((( YCbCrSupportbyte >> 6) & 1)) | |
(( dodump )) && | |
case $BasicAudioSupport in | |
1) echo " Basic audio support" ;; | |
esac | |
else | |
(( dodump )) && echo "$blockoffset) CTA-861 extension block with old version $CTAversion" | |
fi | |
local newctablock="" | |
local lastctablockoffset=0 | |
local ctablocksAdded=0 | |
local newctadescriptor="" | |
local lastctadescriptoroffset=0 | |
local ctadescriptorsAdded=0 | |
if (( CTAversion > 2 )); then | |
# Loop CTA blocks | |
local CTAdatablockoffset=4 | |
if (( doaddctablockoffset == -1 )); then | |
doaddctablockoffset=$((blockoffset + CTAdatablockoffset)) | |
fi | |
local overflow=0 | |
while (( CTAdatablockoffset <= detailedTimingDescriptorsOffset )); do | |
if (( blockoffset + CTAdatablockoffset == doaddctablockoffset )); then | |
(( debug )) && echo ": found CTA block $doaddctablockoffset" 1>&2 | |
# concatenate all the new blocks that will fit | |
while (( currentnewctablock < ${#newctablocks[@]} )); do | |
local CTAdatablock=${newctablocks[currentnewctablock+arrstart]} | |
(( debug )) && echo ": testing $CTAdatablock" 1>&2 | |
if (( ${#CTAdatablock}/2 > 0x7b )); then | |
echo "Error: CTA block is too large: ${CTAdatablock}" 1>&2 | |
else | |
if (( 4 + (${#newctablock} + ${#CTAdatablock})/2 > 0x7f )); then | |
(( debug )) && echo ": overflow" 1>&2 | |
overflow=1 | |
break | |
fi | |
newctablock+="${CTAdatablock}" | |
((ctablocksAdded++)) | |
(( debug )) && echo ": $ctablocksAdded: concetenate result: $newctablock" 1>&2 | |
fi | |
((currentnewctablock++)) | |
done | |
newctablocks=("${newctablocks[@]:$currentnewctablock}") | |
currentnewctablock=0 | |
if (( ctablocksAdded == 0 )); then | |
# none fit here, try in the next block | |
doaddctablockoffset=-1 | |
fi | |
fi | |
if (( CTAdatablockoffset >= detailedTimingDescriptorsOffset)); then | |
break | |
fi | |
local CTAdatablocklength=$(((0x${theblock:$CTAdatablockoffset*2:2} & 0x1f) + 1)) | |
local CTAdatablock=${theblock:$CTAdatablockoffset*2:$CTAdatablocklength * 2} | |
# blocks that won't fit are inserted into a list that will be added later | |
if (( overflow || (4 + ${#newctablock}/2 + CTAdatablocklength > 0x7f) )); then | |
overflow=1 | |
newctablocks+=("$CTAdatablock") | |
if ((lastctablockoffset == 0)); then | |
lastctablockoffset=$CTAdatablockoffset | |
fi | |
else | |
newctablock+="${CTAdatablock}" | |
fi | |
((CTAdatablockoffset+=CTAdatablocklength)) | |
done | |
if ((lastctablockoffset == 0)); then | |
lastctablockoffset=$CTAdatablockoffset | |
fi | |
fi # CTAversion > 2 | |
if ((1)); then | |
# Loop CTA descriptors | |
local ctadescriptorblockoffset=$((detailedTimingDescriptorsOffset)) | |
if (( doaddctadescriptoroffset == -1 )); then | |
doaddctadescriptoroffset=$((blockoffset + ctadescriptorblockoffset)) | |
fi | |
local overflow=0 | |
while (( ctadescriptorblockoffset <= afterctadescriptorblockoffset )); do | |
if (( blockoffset + ctadescriptorblockoffset == doaddctadescriptoroffset )); then | |
(( debug )) && echo ": found CTA descriptor offset $doaddctadescriptoroffset" 1>&2 | |
# concatenate all the new blocks that will fit | |
while (( currentnewctadescriptor < ${#newctadescriptors[@]} )); do | |
local ctadescriptor=${newctadescriptors[currentnewctadescriptor+arrstart]} | |
(( debug )) && echo ": testing $ctadescriptor" 1>&2 | |
if (( ${#ctadescriptor}/2 != 18 )); then | |
echo "Error: CTA descriptor is wrong size (18 bytes expected): ${ctadescriptor}" 1>&2 | |
else | |
if (( 4 + (${#newctablock} + ${#newctadescriptor} + ${#ctadescriptor})/2 > 0x7f )); then | |
(( debug )) && echo ": overflow" 1>&2 | |
overflow=1 | |
break | |
fi | |
newctadescriptor+="${ctadescriptor}" | |
((ctadescriptorsAdded++)) | |
(( debug )) && echo ": $ctadescriptorsAdded: concetenate result: $newctadescriptor" 1>&2 | |
fi | |
((currentnewctadescriptor++)) | |
done | |
newctadescriptors=("${newctadescriptors[@]:$currentnewctadescriptor}") | |
currentnewctadescriptor=0 | |
if (( ctadescriptorsAdded == 0 )); then | |
# none fit here, try in the next block | |
doaddctadescriptoroffset=-1 | |
fi | |
fi | |
if (( ctadescriptorblockoffset + 18 > afterctadescriptorblockoffset )); then | |
break | |
fi | |
local ctadescriptorlength=18 | |
local ctadescriptor=${theblock:$ctadescriptorblockoffset*2:$ctadescriptorlength * 2} | |
# blocks that won't fit are inserted into a list that will be added later | |
if (( overflow || (4 + (${#newctablock} + ${#newctadescriptor})/2 + ctadescriptorlength > 0x7f) )); then | |
overflow=1 | |
case ${ctadescriptor} in | |
000000000000000000000000000000000000) ;; # "Empty" | |
000000100000000000000000000000000000) ;; # "Dummy block" | |
*) | |
newctadescriptors+=("$ctadescriptor") | |
if ((lastctadescriptoroffset == 0)); then | |
lastctadescriptoroffset=$ctadescriptorblockoffset | |
fi | |
;; | |
esac | |
else | |
newctadescriptor+="${ctadescriptor}" | |
fi | |
((ctadescriptorblockoffset+=ctadescriptorlength)) | |
done | |
if ((lastctadescriptoroffset == 0)); then | |
lastctadescriptoroffset=$ctadescriptorblockoffset | |
fi | |
fi | |
if (( ctablocksAdded || ctadescriptorsAdded )); then | |
detailedTimingDescriptorsOffset=$(( 4 + ${#newctablock}/2 )) | |
replacebytes $(( 2 + blockoffset )) "$( printf "%02x" "$detailedTimingDescriptorsOffset" )" | |
replacebytes $(( 4 + blockoffset )) "${newctablock}${newctadescriptor}$(printf "%0*x" $((0x7b*2 - ${#newctablock} - ${#newctadescriptor})) 0)" | |
theblock=${theedid:$blockoffset*2:254} | |
if (( ${#newctablocks[@]} > 0 )); then | |
doaddctablockoffset=-1 | |
fi | |
if (( ${#newctadescriptors[@]} > 0 )); then | |
doaddctadescriptoroffset=-1 | |
fi | |
fi | |
if (( CTAversion > 2 )); then | |
# Process CTA blocks | |
CTAdatablockoffset=4 | |
(( dodump && (CTAdatablockoffset < detailedTimingDescriptorsOffset) )) && echo " CTA data blocks:" | |
while (( CTAdatablockoffset < detailedTimingDescriptorsOffset )); do | |
CTAdatablocklength=$(((0x${theblock:$CTAdatablockoffset*2:2} & 0x1f) + 1)) | |
CTAdatablock=${theblock:$CTAdatablockoffset*2:$CTAdatablocklength * 2} | |
CTAtagcode=$((0x${CTAdatablock:0:2} >> 5)) | |
if (( CTAtagcode == 7 )); then | |
CTAtagcode=$(printf "%x" $((0x${CTAdatablock:2:2} + 0x700))) | |
fi | |
if (( dodump )); then | |
echo -n " $((blockoffset + CTAdatablockoffset))) " | |
case $CTAtagcode in | |
0) printf "Reserved" ;; | |
1) printf "Audio" ;; | |
2) printf "Video" ;; | |
3) printf "Vendor-specific" ;; | |
4) printf "Speaker allocation" ;; | |
5) printf "Display transfer characteristic" ;; | |
6) printf "Reserved" ;; | |
700) printf "Video capability" ;; | |
701) printf "Vendor-specific video" ;; | |
702) printf "VESA display device" ;; | |
703) printf "VESA video timing block" ;; | |
704) printf "Reserved for HDMI video" ;; | |
705) printf "Colorimetry" ;; | |
706) printf "HDR static metadata" ;; | |
707) printf "HDR dynamic metadata" ;; | |
70d) printf "Video format preference" ;; | |
70e) printf "YCbCr 4:2:0 video" ;; | |
70f) printf "YCbCr 4:2:0 capability map" ;; | |
710) printf "Reserved for CTA miscellaneous audio fields" ;; | |
711) printf "Vendor-specific audio" ;; | |
712) printf "Reserved for HDMI audio" ;; | |
713) printf "Room configuration" ;; | |
714) printf "Speaker location" ;; | |
720) printf "InfoFrames" ;; | |
*) | |
if (( 0x$CTAtagcode < 0x70d )); then | |
printf "Reserved for video-related" | |
elif (( 0x$CTAtagcode < 0x720 )); then | |
printf "Reserved for audio-related" | |
else | |
printf "Reserved" | |
fi | |
printf " (e%s)" "${CTAtagcode: -2}" | |
;; | |
esac | |
echo -n ": $CTAdatablock" | |
fi | |
local thepattern=".*:$CTAtagcode:.*" | |
local willberemoved=0 | |
if (( blockoffset + CTAdatablockoffset == deletectablockoffset )); then | |
willberemoved=1 | |
deletectablockoffset=0 | |
fi | |
if [[ $ctablocktypetodelete =~ $thepattern ]]; then | |
willberemoved=1 | |
fi | |
case $CTAtagcode in | |
1) | |
(( dodump )) && echo | |
HasAudio=1 | |
;; | |
3) | |
IEEEOUI=${CTAdatablock:6:2}${CTAdatablock:4:2}${CTAdatablock:2:2} | |
(( dodump )) && echo -n " = $IEEEOUI:" | |
case $IEEEOUI in | |
000c03) | |
(( dodump )) && echo " HDMI Licensing, LLC -> H14b VSDB" | |
H14bByte=${CTAdatablock:12:2} | |
if [[ -n $H14bByte ]]; then | |
H14bByte=$((0x${H14bByte})) | |
(( dodump )) && printf " Supports AI - %s\n" "$( (( H14bByte & 0x80 )) && echo "Yes" || echo "No")" | |
(( dodump )) && | |
case $(( (H14bByte >> 4) & 7 )) in | |
0) echo " Supports Deep Color - No" ;; | |
1) echo " Supports Deep Color - 10 bpc" ;; | |
2) echo " Supports Deep Color - 12 bpc" ;; | |
3) echo " Supports Deep Color - 10 bpc, 12 bpc" ;; | |
4) echo " Supports Deep Color - 16 bpc" ;; | |
5) echo " Supports Deep Color - 10 bpc, 16 bpc" ;; | |
6) echo " Supports Deep Color - 12 bpc, 16 bpc" ;; | |
7) echo " Supports Deep Color - 10 bpc, 12 bpc, 16 bpc" ;; | |
esac | |
if (( do8bpc )); then | |
local newH14bByte=$(( H14bByte & ~0x70 )) | |
replacebytes $((blockoffset+CTAdatablockoffset+6)) "$(printf "%02x" $newH14bByte)" | |
(( dodump )) && echo " changed: Supports Deep Color - No" | |
fi | |
(( dodump )) && printf " Supports Deep Color YCbCr 4:4:4 - %s\n" "$( (( H14bByte & 0x08 )) && echo "Yes" || echo "No")" | |
if (( doRGB )); then | |
local newH14bByte=$(( (H14bByte & ~0x08) | (0 << 3) )) # 0: No (RGB only), 1: Yes (YCbCr 4:4:4) | |
replacebytes $((blockoffset+CTAdatablockoffset+6)) "$(printf "%02x" $newH14bByte)" | |
(( dodump )) && echo " changed: Support YCbCr 4:4:4 - No" | |
fi | |
if (( do422 )); then | |
local newH14bByte=$(( (H14bByte & ~0x08) | (1 << 3) )) # 0: No (RGB only), 1: Yes (YCbCr 4:4:4) | |
replacebytes $((blockoffset+CTAdatablockoffset+6)) "$(printf "%02x" $newH14bByte)" | |
(( dodump )) && echo " changed: Support YCbCr 4:4:4 - Yes" | |
fi | |
(( dodump )) && printf " Supports DVI Dual-Link - %s\n" "$( (( H14bByte & 0x01 )) && echo "Yes" || echo "No")" | |
fi | |
;; | |
c45dd8) | |
(( dodump )) && echo " HDMI Forum -> HF-VSDB" | |
HFByte=$((0x${CTAdatablock:14:2})) | |
HF=$(( (HFByte >> 0) & 7 )) | |
(( dodump )) && | |
case $HF in | |
0) echo " 4:2:0 10/12/16 bpc - No" ;; | |
1) echo " 4:2:0 10 bpc - Yes" ;; | |
2) echo " 4:2:0 12 bpc - Yes" ;; | |
3) echo " 4:2:0 10/12 bpc - Yes" ;; | |
4) echo " 4:2:0 16 bpc - Yes" ;; | |
5) echo " 4:2:0 10/16 bpc - Yes" ;; | |
6) echo " 4:2:0 12/16 bpc - Yes" ;; | |
7) echo " 4:2:0 10/12/16 bpc - Yes" ;; | |
esac | |
if (( doRGB )); then | |
newHFByte=$(( (HFByte & ~0x07) | (0 << 0) )) | |
replacebytes $((blockoffset+CTAdatablockoffset+7)) "$(printf "%02x" $newHFByte)" | |
(( dodump )) && echo " changed: 4:2:0 10/12/16 bpc - No" | |
fi | |
if (( do422 )); then | |
newHFByte=$(( (HFByte & ~0x07) | (7 << 0) )) | |
replacebytes $((blockoffset+CTAdatablockoffset+7)) "$(printf "%02x" $newHFByte)" | |
(( dodump )) && echo " changed: 4:2:0 10/12/16 bpc - Yes" | |
fi | |
;; | |
*) | |
(( dodump )) && echo " Unknown OUI" | |
;; | |
esac | |
;; | |
*) | |
(( dodump )) && echo | |
;; | |
esac | |
if (( willberemoved )); then | |
((detailedTimingDescriptorsOffset-=CTAdatablocklength)) | |
replacebytes $((blockoffset+2)) "$(printf "%02x" "$detailedTimingDescriptorsOffset")" | |
local nextctablockoffset=$((CTAdatablockoffset+CTAdatablocklength)) | |
replacebytes $((blockoffset+CTAdatablockoffset)) "${theedid:(blockoffset+nextctablockoffset)*2:(127-nextctablockoffset)*2}${CTAdatablock//?/0}" | |
theblock=${theedid:$blockoffset*2:254} | |
(( dodump )) && echo " changed: deleted" | |
else | |
ctablocks+=("$CTAdatablock") | |
((CTAdatablockoffset+=CTAdatablocklength)) | |
EndOfLastctablock=$((blockoffset + CTAdatablockoffset)) | |
fi | |
done | |
(( dodump && (ctablocksAdded > 0) )) && echo " changed: added $ctablocksAdded" | |
if ((CTAdatablockoffset < detailedTimingDescriptorsOffset || detailedTimingDescriptorsOffset + 18 > 0x7f )); then | |
(( dodump )) && echo " $((blockoffset + CTAdatablockoffset)))" | |
fi | |
fi | |
# Process CTA descriptors | |
detailed_blocks $((blockoffset+detailedTimingDescriptorsOffset)) $((blockoffset + 127)) "$dodump" "$doreplacedescriptoroffset" "$deletedescriptoroffset" "$replacementdescriptor" | |
local ctadescriptorblockoffset=$((detailedTimingDescriptorsOffset)) | |
local ctadescriptorlength=18 | |
while (( ctadescriptorblockoffset + 18 <= afterctadescriptorblockoffset )); do | |
local ctadescriptor=${theblock:$ctadescriptorblockoffset*2:$ctadescriptorlength * 2} | |
case ${ctadescriptor} in | |
000000000000000000000000000000000000) ;; # "Empty" | |
000000100000000000000000000000000000) ;; # "Dummy block" | |
*) | |
ctadescriptors+=("$ctadescriptor") | |
EndOfLastctadescriptor=$((blockoffset + ctadescriptorblockoffset + ctadescriptorlength)) | |
;; | |
esac | |
((ctadescriptorblockoffset+=ctadescriptorlength)) | |
done | |
(( dodump && (ctadescriptorsAdded > 0) )) && echo " changed: added $ctadescriptorsAdded" | |
elif [[ $blocktag = 70 ]]; then | |
(( debug )) && echo ": parsing DisplayID extension block" 1>&2 | |
# 00 edid tag 70 | |
# | |
# 01 1 DisplayID version | |
# 02 2 DisplayID len 0x79 | |
# 03 3 DisplayID product type | |
# 04 4 DisplayID nextcount | |
# | |
# 05 00 DisplayID block #1 | |
# 06 01 DisplayID block #1 revision | |
# 07 02 DisplayID block #1 length | |
# | |
# 7E 5 displayID checksum | |
# | |
# 7F EDID checksum | |
DisplayIDversion=${theblock:2:1}.${theblock:3:1} | |
# maximum length of DisplayID section in an EDID extension block is 121 = 0x79 bytes. | |
# this is the length of all the DisplayID blocks | |
DisplayIDlength=$((0x${theblock:4:2})) | |
DisplayIDproducttype=$((0x${theblock:6:2})) | |
#DisplayIDextcount=$((0x${theblock:8:2})) | |
DisplayIDblockoffset=5 | |
if (( doadddisplayidblockoffset == -1 )); then | |
doadddisplayidblockoffset=$((blockoffset + DisplayIDblockoffset)) | |
fi | |
local newdisplayidblock="" | |
local newDisplayIDblocklength=0 | |
local lastDisplayIDblockoffset=0 | |
local displayidblocksAdded=0 | |
local overflow=0 | |
while (( DisplayIDblockoffset < DisplayIDlength + 5 )); do | |
if (( blockoffset + DisplayIDblockoffset == doadddisplayidblockoffset )); then | |
(( debug )) && echo ": found DisplayID block $doadddisplayidblockoffset" 1>&2 | |
# concatenate all the new blocks that will fit | |
while (( currentnewdisplayidblock < ${#newdisplayidblocks[@]} )); do | |
local DisplayIDblock=${newdisplayidblocks[currentnewdisplayidblock+arrstart]} | |
(( debug )) && echo ": testing $DisplayIDblock" 1>&2 | |
if (( ${#DisplayIDblock}/2 > 0x79 )); then | |
echo "Error: DisplayID block is too large: ${DisplayIDblock}" 1>&2 | |
else | |
if (( (${#newdisplayidblock} + ${#DisplayIDblock})/2 > 0x7e - DisplayIDblockoffset )); then | |
(( debug )) && echo ": overflow" 1>&2 | |
overflow=1 | |
break | |
fi | |
newdisplayidblock+="${DisplayIDblock}" | |
((displayidblocksAdded++)) | |
(( debug )) && echo ": $displayidblocksAdded: concetenate result: $newdisplayidblock" 1>&2 | |
fi | |
((currentnewdisplayidblock++)) | |
done | |
newdisplayidblocks=("${newdisplayidblocks[@]:$currentnewdisplayidblock}") | |
currentnewdisplayidblock=0 | |
newDisplayIDblocklength=$((${#newdisplayidblock}/2)) | |
if (( displayidblocksAdded == 0 )); then | |
# none fit here, try in the next block | |
doadddisplayidblockoffset=-1 | |
fi | |
fi | |
# stop when the remaining bytes are 0 | |
local remains=${theblock:$DisplayIDblockoffset * 2:($DisplayIDlength + 5 - $DisplayIDblockoffset)*2} | |
if [ -z "${remains//0/}" ]; then | |
break | |
fi | |
local DisplayIDblocklength=$((0x${theblock:$DisplayIDblockoffset * 2 + 4:2} + 3)) | |
# blocks that won't fit are inserted into a list that will be added later | |
if (( overflow || (DisplayIDblockoffset + DisplayIDblocklength + newDisplayIDblocklength > 0x7e) )); then | |
overflow=1 | |
local DisplayIDblock=${theblock:$DisplayIDblockoffset*2:$DisplayIDblocklength * 2} | |
newdisplayidblocks+=("$DisplayIDblock") | |
if ((lastDisplayIDblockoffset == 0)); then | |
lastDisplayIDblockoffset=$DisplayIDblockoffset | |
fi | |
fi | |
((DisplayIDblockoffset+=DisplayIDblocklength)) | |
done | |
if ((lastDisplayIDblockoffset == 0)); then | |
lastDisplayIDblockoffset=$DisplayIDblockoffset | |
fi | |
(( dodump )) && printf "%s) DisplayID extension block: version %s, type %d\n" $blockoffset "$DisplayIDversion" $DisplayIDproducttype | |
DisplayIDblockoffset=5 | |
while (( DisplayIDblockoffset < DisplayIDlength + 5 )); do | |
if (( blockoffset + DisplayIDblockoffset == doadddisplayidblockoffset )); then | |
replacebytes $((doadddisplayidblockoffset)) "$newdisplayidblock${theblock:$DisplayIDblockoffset*2:($lastDisplayIDblockoffset-$DisplayIDblockoffset)*2}" | |
doadddisplayidblockoffset=-1 | |
((lastDisplayIDblockoffset+=newDisplayIDblocklength)) | |
if (( DisplayIDlength + 5 < lastDisplayIDblockoffset )); then | |
DisplayIDlength=$((lastDisplayIDblockoffset - 5)) | |
replacebytes $((blockoffset+2)) "$(printf "%02x" $DisplayIDlength)" | |
fi | |
theblock=${theedid:$blockoffset*2:254} | |
fi | |
local DisplayIDblocklength=$((0x${theblock:$DisplayIDblockoffset * 2 + 4:2} + 3)) | |
(( dodump )) && echo -n " $((blockoffset + DisplayIDblockoffset)))" | |
local DisplayIDblock=${theblock:$DisplayIDblockoffset*2:$DisplayIDblocklength * 2} | |
local DisplayIDtagcode=${DisplayIDblock:0:2} | |
# stop when the remaining bytes are 0 | |
local remains=${theblock:$DisplayIDblockoffset * 2:($DisplayIDlength + 5 - $DisplayIDblockoffset)*2} | |
if [ -z "${remains//0/}" ]; then | |
(( dodump )) && echo | |
break | |
fi | |
if (( dodump )); then | |
printf " " | |
case $DisplayIDtagcode in | |
# DisplayID 1.3 | |
00) printf "Product identification" ;; | |
01) printf "Display parameters" ;; | |
02) printf "Color characteristics" ;; | |
03) printf "Type 1 detailed timing" ;; | |
04) printf "Type 2 detailed timing" ;; | |
05) printf "Type 3 short timing" ;; | |
06) printf "Type 4 DMT timing" ;; | |
07) printf "VESA timings" ;; | |
08) printf "CTA timings" ;; | |
09) printf "Video timing range" ;; | |
0a) printf "Product serial number" ;; | |
0b) printf "General purpose ASCII string" ;; | |
0c) printf "Display device data" ;; | |
0d) printf "Interface power sequencing" ;; | |
0e) printf "Transfer characterisitics" ;; | |
0f) printf "Display interface" ;; | |
10) printf "Stereo display interface" ;; | |
12) printf "Tiled display topology" ;; | |
# DisplayID 2.0 | |
20) printf "Product ID data" ;; | |
21) printf "Display parameters data" ;; | |
22) printf "Type 7 timing - detailed timing data" ;; | |
23) printf "Type 8 timing - enumerated timing code data" ;; | |
24) printf "Type 9 timing - formula-based timing data" ;; | |
25) printf "Dynamic video timing range limits data" ;; | |
26) printf "Display interface features data" ;; | |
27) printf "Stereo display interface data" ;; | |
28) printf "Tiled display topology data" ;; | |
29) printf "ContainerID data" ;; | |
# 2Ah .. 7Dh RESERVED for Additional VESA-defined Data Blocks | |
7e) printf "2.0 Vendor-specific data" ;; | |
7f) printf "1.3 Vendor-specific data" ;; | |
81) printf "CTA DisplayID data" ;; | |
# 82h .. FFh RESERVED | |
*) | |
if (( 0x$DisplayIDtagcode <= 0x1f )); then | |
printf "Reserved for legacy" | |
elif (( 0x$DisplayIDtagcode <= 0x7d )); then | |
printf "Reserved for VESA" | |
elif (( 0x$DisplayIDtagcode <= 7f )); then | |
printf "Reserved" | |
else | |
printf "Reserved for external standards" | |
fi | |
printf " (%s)" "$DisplayIDtagcode" | |
;; | |
esac | |
echo -n ": $DisplayIDblock" | |
fi | |
local willberemoved=0 | |
if (( blockoffset + DisplayIDblockoffset == deletedisplayidblockoffset )); then | |
willberemoved=1 | |
deletedisplayidblockoffset=0 | |
fi | |
if (( 0x$DisplayIDtagcode == displayidblocktypetodelete )); then | |
willberemoved=1 | |
fi | |
case $DisplayIDtagcode in | |
03) | |
local timing1offset=3 | |
(( dodump == 1 && DisplayIDblocklength > 23 )) && echo | |
while ((timing1offset < DisplayIDblocklength )); do | |
local type1timing=${DisplayIDblock:$timing1offset*2:40} | |
if (( dodump )); then | |
(( DisplayIDblocklength > 23 )) && echo -n " $((blockoffset+DisplayIDblockoffset+timing1offset))) $type1timing" | |
fi | |
local type1timingtext="" | |
type1timingtext="$(dumptype1timingdescriptor "$type1timing")" | |
if (( willberemoved == 0 )); then | |
thetimings+=("$type1timingtext") | |
thetimingsoffsets+=("$(( blockoffset + DisplayIDblockoffset + timing1offset ))") | |
thetimingshex+=("$type1timing") | |
fi | |
(( dodump )) && echo " = $type1timingtext" | |
((timing1offset+=20)) | |
done | |
;; | |
12) | |
HasTile=1 | |
(( dodump )) && printf " = " | |
dumptileddisplaytopologyblock "$DisplayIDblock" | |
(( dodump )) && echo | |
;; | |
26) (( dodump )) && printf " = " | |
dumpdisplayinterfacefeaturesdata "$DisplayIDblock" | |
(( dodump )) && echo | |
if (( do8bpc || doRGB )); then | |
# RGB: 6 8 10 12 14 16 | |
# 444: 6 8 10 12 14 16 | |
# 422: 8 10 12 14 16 | |
# 420: 8 10 12 14 16 | |
local validdepths=$((0x3f3f1f1f)) | |
if (( do8bpc )); then | |
((validdepths &= 0x03030101)) | |
fi | |
if (( doRGB )); then | |
((validdepths &= 0xff000000)) | |
fi | |
replacebytes $((blockoffset + DisplayIDblockoffset + 3)) "$( printf "%08x" $(( 0x${DisplayIDblock:6:8} & validdepths )) )" | |
theblock=${theedid:$blockoffset*2:254} | |
DisplayIDblock=${theblock:$DisplayIDblockoffset*2:$DisplayIDblocklength * 2} | |
if (( dodump )); then | |
printf " changed: %s = " "$DisplayIDblock" | |
dumpdisplayinterfacefeaturesdata "$DisplayIDblock" | |
echo | |
fi | |
fi | |
;; | |
*) | |
(( dodump )) && echo | |
;; | |
esac | |
if (( willberemoved )); then | |
(( debug )) && echo ":" replacebytes $((blockoffset + DisplayIDblockoffset)) "${theblock:($DisplayIDblockoffset + $DisplayIDblocklength)*2:($lastDisplayIDblockoffset - $DisplayIDblockoffset - $DisplayIDblocklength)*2}${DisplayIDblock//?/0}" 1>&2 | |
(( debug )) && echo ":" $DisplayIDblockoffset $DisplayIDblocklength "$lastDisplayIDblockoffset" "${DisplayIDblock//?/0}" 1>&2 | |
replacebytes $((blockoffset + DisplayIDblockoffset)) "${theblock:($DisplayIDblockoffset + $DisplayIDblocklength)*2:($lastDisplayIDblockoffset - $DisplayIDblockoffset - $DisplayIDblocklength)*2}${DisplayIDblock//?/0}" | |
((lastDisplayIDblockoffset-=DisplayIDblocklength)) | |
theblock=${theedid:$blockoffset*2:254} | |
(( dodump )) && echo " changed: deleted" | |
else | |
DisplayIDblocks+=("$DisplayIDblock") | |
((DisplayIDblockoffset+=DisplayIDblocklength)) | |
EndOfLastDisplayIDblock=$((blockoffset + DisplayIDblockoffset)) | |
fi | |
done | |
(( dodump && (displayidblocksAdded > 0) )) && echo " changed: added $displayidblocksAdded" | |
else | |
(( dodump )) && echo "$blockoffset) Extension block: type:$blocktag: $theblock" | |
fi | |
((blockoffset+=128)) | |
if (( ((blockoffset*2 == ${#theedid}) || (0x${theedid:$blockoffset*2:2} != 2) ) && ((doaddctablockoffset == -1) && (${#newctablocks[@]} > 0) || (doaddctadescriptoroffset == -1) && (${#newctadescriptors[@]} > 0)) )); then | |
addctaextensionblock "${CTAversion:=3}" "$YCbCrSupportbyte" "$blockoffset" | |
fi | |
if (( (blockoffset*2 == ${#theedid}) && (doadddisplayidblockoffset == -1) && (${#newdisplayidblocks[@]} > 0) )); then | |
adddisplayidextensionblock "${DisplayIDversion:=1.3}" ${DisplayIDproducttype:=0} | |
fi | |
done | |
(( dodump )) && echo "$blockoffset) End" | |
# post processing | |
local timingndx=0 | |
local tileTimingsCurrent="" | |
for ((timingndx = 0 ; timingndx < ${#thetimings[@]} ; timingndx++)); do | |
local thetiming="${thetimings[timingndx+arrstart]}" | |
local thewidth=${thetiming%%x*} | |
local theheight=${thetiming#*x} | |
theheight=${theheight%%@*} | |
local thetimingrefresh=${thetiming#*@} | |
thetimingrefresh=${thetimingrefresh%%Hz*} | |
thetimingrefresh=0000${thetimingrefresh/./} | |
thetimingrefresh=${thetimingrefresh: -7} | |
if (( thewidth > maxresH )); then | |
maxresH=$thewidth | |
maxresV=$theheight | |
fi | |
if (( HasTile )); then | |
if [[ "$thetiming" =~ ${tileSizeH}x${tileSizeV}@([0-9.]+)Hz.* ]]; then | |
tileTimingsCurrent+="$thetimingrefresh $thetiming\n" | |
fi | |
fi | |
done | |
if (( HasTile )); then | |
if [[ -n $tileTimingsCurrent ]]; then | |
tileTimings=$(echo "$tileTimingsCurrent" | sort -r) | |
tileRefresh=$(( (10#${tileTimings%% *} + 50)/100)) # round to 1 decimal | |
if (( tileRefresh % 10 )); then | |
tileRefresh=$((tileRefresh / 10)).${tileRefresh: -1} | |
else | |
tileRefresh=$((tileRefresh / 10)) | |
fi | |
fi | |
fi | |
} | |
createdetailedtiming () { | |
# currently supports only "Normal Display" with "Digital Separate Sync" | |
local param="" | |
for param in MHz hactive hfront hpulse hback vactive vfront vpulse vback hsync vsync hmm vmm hborder vborder; do | |
eval "local $param=\"$1\""; (( $# )) && shift | |
done | |
local interlaced=0 | |
[[ -n ${vactive//[^i]} ]] && interlaced=1 | |
vactive=${vactive//[^0-9]} | |
[[ "$hsync" = '+' ]] && hsync=1 || hsync=0 | |
[[ "$vsync" = '+' ]] && vsync=1 || vsync=0 | |
local MHzint=$(( (10#$(printf "0%s.000" $MHz | sed -E "s/^([0-9]*)\.([0-9])\.*([0-9])\.*([0-9]).*/\1\2\3\4/g") + 5) / 10)) | |
local hblank=$((hfront + hpulse + hback)) | |
local vblank=$((vfront + vpulse + vback)) | |
printf "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \ | |
$((MHzint & 255)) $((MHzint >> 8)) \ | |
$((hactive & 255)) \ | |
$((hblank & 255)) \ | |
$(((hactive >> 8 << 4) + (hblank >> 8))) \ | |
$((vactive & 255)) \ | |
$((vblank & 255)) \ | |
$(((vactive >> 8 << 4) + (vblank >> 8))) \ | |
$((hfront & 255)) \ | |
$((hpulse & 255)) \ | |
$((((vfront & 15) << 4) + (vpulse & 15))) \ | |
$(((hfront >> 8 << 6) + (hpulse >> 8 << 4) + (vfront >> 4 << 2) + (vpulse >> 4))) \ | |
$((${hmm:=0} & 255)) \ | |
$((${vmm:=0} & 255)) \ | |
$(((${hmm:=0} >> 8 << 4) + (${vmm:=0} >> 8))) \ | |
${hborder:=0} \ | |
${vborder:=0} \ | |
$(((interlaced << 7) + 0x18 + (vsync << 2) + (hsync << 1))) | |
} | |
createtype1timingdescriptor () { | |
# currently supports only "Normal Display" with "Digital Separate Sync" | |
# no 3D, no interlaced | |
local param="" | |
for param in MHz hactive hfront hpulse hback vactive vfront vpulse vback hsync vsync preferred; do | |
eval "local $param=\"$1\""; (( $# )) && shift | |
done | |
local interlaced=0 | |
[[ -n ${vactive//[^i]} ]] && interlaced=1 | |
vactive=${vactive//[^0-9]} | |
[[ "$hsync" = '+' ]] && hsync=1 || hsync=0 | |
[[ "$vsync" = '+' ]] && vsync=1 || vsync=0 | |
local MHzint=$(((10#$(printf "0%s.000" $MHz | sed -E "s/^([0-9]*)\.([0-9])\.*([0-9])\.*([0-9]).*/\1\2\3\4/g") + 5) / 10)) | |
local hblank=$((hfront + hpulse + hback)) | |
local vblank=$((vfront + vpulse + vback)) | |
local aspect=8 | |
case $(((hactive * 10000 / vactive + 5)/10)) in | |
1000) aspect=0 ;; | |
1250) aspect=1 ;; | |
1333) aspect=2 ;; | |
1667) aspect=3 ;; | |
1778) aspect=4 ;; | |
1600) aspect=5 ;; | |
2370) aspect=6 ;; | |
1896) aspect=7 ;; | |
esac | |
((MHzint-=1)) | |
((hactive-=1)) | |
((hblank-=1)) | |
((hfront-=1)) | |
((hpulse-=1)) | |
((vactive-=1)) | |
((vblank-=1)) | |
((vfront-=1)) | |
((vpulse-=1)) | |
printf "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \ | |
$((MHzint & 255)) $(((MHzint >> 8) & 255)) $((MHzint >> 16)) \ | |
$(((${preferred:=0} << 7) + (interlaced << 4) + aspect)) \ | |
$((hactive & 255)) $((hactive >> 8)) \ | |
$((hblank & 255)) $((hblank >> 8)) \ | |
$((hfront & 255)) $((((hfront >> 8) & 127) + (hsync << 7))) \ | |
$((hpulse & 255)) $((hpulse >> 8)) \ | |
$((vactive & 255)) $((vactive >> 8)) \ | |
$((vblank & 255)) $((vblank >> 8)) \ | |
$((vfront & 255)) $((((vfront >> 8) & 127) + (vsync << 7))) \ | |
$((vpulse & 255)) $((vpulse >> 8)) | |
} | |
createtype1timingblock () { | |
local type1timingblock="" | |
while (( $# )); do | |
type1timingblock+=$1 | |
shift | |
done | |
printf "0300%02x%s" $((${#type1timingblock}/2)) "$type1timingblock" | |
} | |
addchromasubsampling () { | |
processedid do422 dodeletectablocktype 70f # = YCbCr 4:2:0 capability map | |
repairchecksums | |
} | |
removechromasubsampling () { | |
processedid doRGB dodeletectablocktype 70f # = YCbCr 4:2:0 capability map | |
repairchecksums | |
} | |
set8bpcmax () { | |
processedid do8bpc dodeletectablocktype 706 707 # = HDR static metadata, HDR dynamic metadata | |
repairchecksums | |
} | |
deletectablock () { | |
processedid dodeletectablock "$1" | |
repairchecksums | |
} | |
deletectablocktype () { | |
processedid dodeletectablocktype "$1" | |
repairchecksums | |
} | |
addctablock () { | |
processedid doaddctablock "$@" | |
repairchecksums | |
} | |
deletedescriptor () { | |
processedid dodeletedescriptor "$1" | |
repairchecksums | |
} | |
addctadescriptor () { | |
processedid doaddctadescriptor "$@" | |
repairchecksums | |
} | |
replacedescriptor () { | |
processedid doreplacedescriptor "$1" "$2" | |
repairchecksums | |
} | |
cleardate () { | |
processedid docleardate | |
repairchecksums | |
} | |
clearserialnumber () { | |
processedid doclearserialnumber | |
repairchecksums | |
} | |
setpreferredisnative () { | |
processedid dosetpreferredisnative "$1" | |
repairchecksums | |
} | |
clearchromaticity () { | |
processedid doclearchromaticity | |
repairchecksums | |
} | |
clearestablishedtimings () { | |
processedid doclearestablishedtimings | |
repairchecksums | |
} | |
clearmanufacturerstimings () { | |
processedid doclearmanufacturerstimings | |
repairchecksums | |
} | |
clearstandardtimings () { | |
processedid doclearstandardtimings | |
repairchecksums | |
} | |
deletedisplayidblock () { | |
processedid dodeletedisplayidblock "$1" | |
repairchecksums | |
} | |
deletedisplayidblocktype () { | |
processedid dodeletedisplayidblockblocktype "$1" | |
repairchecksums | |
} | |
adddisplayidblock () { | |
processedid doadddisplayidblock "$@" | |
repairchecksums | |
} | |
dumpedid () { | |
processedid dodump | |
} | |
dumpedidall () { | |
local dostdout=0 | |
local thefolderpath="." | |
while (( $# )); do | |
case "$1" in | |
"-s") dostdout=1 ;; | |
*) thefolderpath="$1" ;; | |
esac | |
shift | |
done | |
local saveedid="$theedid" | |
local i="" | |
for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
useedidnum "$i" | |
if ((dostdout)); then | |
echo "==========================================================================================" | |
echo "$i)" | |
echo "${paths[i+arrstart-1]}" | |
echo "==================" | |
dumpedid | |
else | |
dumpedid > "${thefolderpath:=.}/${thefilenamebase}_dumpedid.txt" | |
fi | |
done | |
useedidstring "$saveedid" | |
} | |
createdummydescriptor () { | |
echo -n "000000100000000000000000000000000000" | |
} | |
createemptydescriptor () { | |
echo -n "000000000000000000000000000000000000" | |
} | |
#========================================================================================= | |
# Use EDID | |
clearedidinfo () { | |
tileTimings="" | |
tileSizeH="" | |
tileSizeV="" | |
tileRefresh="" | |
maxresH=0 | |
maxresV=0 | |
} | |
useedidstring () { | |
clearedidinfo | |
(( debug )) && echo ": useedidstring $1" 1>&2 | |
theedid="$1" | |
processedid | |
} | |
useedidnum () { | |
clearedidinfo | |
theedid=${edids[arrstart - 1 + $1]} | |
processedid | |
thefilenamebase="${thefilenamebase}_$i" | |
} | |
#========================================================================================= | |
# Get EDID | |
clearedids () { | |
edids=() | |
paths=() | |
} | |
clearedids | |
applypatches () { | |
thefilename="$1" | |
sourcename="$2" | |
if [[ -f "${thefilename}" ]]; then | |
if /usr/libexec/PlistBuddy -c 'Print :edid-patches' "${thefilename}" > /dev/null 2>&1; then | |
local index=0 | |
while [ -n "$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:offset" "${thefilename}" 2> /dev/null)" ]; do | |
local theoffset="" | |
local thedata="" | |
theoffset=$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:offset" "${thefilename}") | |
thedata=$(/usr/libexec/PlistBuddy -c "Print :edid-patches:$index:data" "${thefilename}" | xxd -p -c 99999) | |
thedata=${thedata%0a} # remove extra linefeed that was added by PlistBuddy | |
theedid=${theedid:0:$theoffset*2}${thedata}${theedid:$theoffset*2 + ${#thedata}} | |
((index+=1)) | |
done | |
if (( ${#theedid} % 256 == 0 )); then | |
if [[ -n $sourcename ]]; then | |
addedid "${thefilename}:edid-patches of ($sourcename)" "$theedid" | |
else | |
addedid "${thefilename}:edid-patches" "$theedid" | |
fi | |
else | |
theedid="" | |
fi | |
fi | |
fi | |
} | |
addedid () { | |
local saveedid="$theedid" | |
local thepath="$1" | |
useedidstring "$2" | |
local isnew=1 | |
local i="" | |
for ((i = 0 ; i < ${#edids[@]} ; i++)); do | |
if [[ $theedid = "${edids[i+arrstart]}" ]]; then | |
if [[ ! "$(printf "_\n%s\n_" "${paths[i+arrstart]}")" =~ $(printf ".*\n%s\n.*" "${thepath}") ]]; then | |
paths[i+arrstart]="$(printf "%s\n%s" "${paths[i+arrstart]}" "$thepath")" | |
fi | |
isnew=0 | |
break | |
fi | |
done | |
if (( isnew )); then | |
(( debug )) && echo ": isnew" 1>&2 | |
edids+=("$theedid") | |
paths+=("$thepath") | |
local newndx=${#edids[@]} | |
local thefilename="" | |
local thedir="" | |
for thedir in "/System" ""; do | |
local fulldir="$thedir/Library/Displays/Contents/Resources/Overrides" | |
for thefilename in "$fulldir/$themanufacturefile" "$fulldir/$theproductfile"; do | |
(( debug )) && echo ": checking $thefilename" 1>&2 | |
if [[ -f "${thefilename}" ]]; then | |
loadoverridefile "${thefilename}" | |
[[ -n $lastoverrideedid ]] && theedid=$lastoverrideedid | |
applypatches "${thefilename}" "$newndx" | |
fi | |
done | |
for thefilename in "$fulldir/${themanufacturefile}.mtdd" "$fulldir/${theproductfile}.mtdd"; do | |
(( debug )) && echo ": checking $thefilename" 1>&2 | |
if [[ -f "${thefilename}" ]]; then | |
loadmtddfile "${thefilename}" | |
fi | |
done | |
done | |
fi | |
useedidstring "$saveedid" | |
} | |
loadoverridefile () { | |
while (( $# )); do | |
local thefilename="" | |
thefilename="$1" | |
shift | |
# lastoverrideedid is not local | |
lastoverrideedid=$(/usr/libexec/PlistBuddy -c 'Print :IODisplayEDID' "${thefilename}" 2> /dev/null | xxd -p -c 99999) | |
lastoverrideedid=${lastoverrideedid%0a} # remove extra linefeed that was added by PlistBuddy | |
[[ -n $lastoverrideedid ]] && addedid "${thefilename}:IODisplayEDID" "$lastoverrideedid" | |
local theedid="" | |
theedid=$(/usr/libexec/PlistBuddy -c 'Print :"SwitchResX backuped settings":IODisplayEDID' "${thefilename}" 2> /dev/null | xxd -p -c 99999) | |
theedid=${theedid%0a} # remove extra linefeed that was added by PlistBuddy | |
[[ -n $theedid ]] && addedid "${thefilename}:SwitchResX backuped settings:IODisplayEDID" "$theedid" | |
done | |
} | |
loadmtddfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
local theedid="" | |
theedid=$(/usr/libexec/PlistBuddy -c 'Print :display:overlay' "${thefilename}" 2> /dev/null | xxd -p -c 99999) | |
theedid=${theedid%0a} # remove extra linefeed that was added by PlistBuddy | |
[[ -n $theedid ]] && addedid "${thefilename}:overlay" "$theedid" | |
done | |
} | |
loadswitchresxfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
local theedid="" | |
theedid=$(sed -n -E -e $'/^ *<([0-9A-F \t]+)[ \t]*>/s//\\1/p' "${thefilename}" | xxd -r -p | xxd -p -c 99999) | |
addedid "${thefilename}:switchresx" "$theedid" | |
done | |
} | |
loadmamhexfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
local theedid="" | |
theedid=$(sed -n -E -e "/^:[0-9A-F]{8}([0-9A-F]{64})[0-9A-F]{2}.?$/s//\1/p" "${thefilename}" | xxd -p -r | xxd -p -c 99999) | |
addedid "${thefilename}:Monitor Asset Manager.hex" "$theedid" | |
done | |
} | |
loadmaminffile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
local theedid="" | |
theedid=$(sed -n -E -e '/^HKR,EDID_OVERRIDE,".+",0x01((,0x[0-9A-F]{2})*).?$/s//\1/;s/,0x//gp' "${thefilename}" | xxd -p -r | xxd -p -c 99999) | |
addedid "${thefilename}:Monitor Asset Manager.hex" "$theedid" | |
done | |
} | |
loadmamdatfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
local theedid="" | |
theedid=$(sed -n -E -e '/^[0-9A-F]{2,4} \|(( [0-9A-F]{2})*).?$/s//\1/p' "${thefilename}" | xxd -r -p | xxd -p -c 99999) | |
addedid "${thefilename}:Monitor Asset Manager.dat" "$theedid" | |
done | |
} | |
numstrings=0 | |
loadstring () { | |
local theedid="$1" | |
local sourcename="$2" | |
theedid=$(echo ${theedid//0x/} | tr -d ' \n\r\t,$') | |
if [[ -z $sourcename ]]; then | |
((numstrings++)) | |
sourcename="string:$numstrings" | |
fi | |
addedid "$sourcename" "$(tr 'A-F' 'a-f' <<< "$theedid")" | |
} | |
loadcurrentedid () { | |
loadstring "$theedid" "$1" | |
} | |
loadbinfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
addedid "$thefilename" "$(xxd -p -c 99999 < "$thefilename")" | |
done | |
} | |
loadhexfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
addedid "$thefilename" "$( | |
if [[ -z $(tr -d 'a-fA-F0-9 \n\t' < "$thefilename") ]]; then | |
xxd -p -r < "$thefilename" | |
else | |
xxd -r < "$thefilename" | |
fi | xxd -p -c 99999 | |
)" | |
done | |
} | |
loadoneediditem () { | |
local ediditem="$1" | |
local thepath="${ediditem% = <*}" | |
local theedid="${ediditem##* = <}" | |
local theedid="${theedid%>}" | |
(( debug )) && echo ":" addedid "${thepath}" "${theedid}" 1>&2 | |
addedid "${thepath}" "${theedid}" | |
} | |
loadagdcfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
IFS=$'\n' | |
local ediditem="" | |
for ediditem in $( | |
perl -e ' | |
$thepath=""; while (<>) { | |
if ( m|^(?:\[\d+\] )?IOService:(/.*)| ) { $thepath = $1 } | |
if ( /^[#|]* ?EDID Dump (Port \d+) - Start( ##)?/ ) { $theport = $1; $edid = "" } | |
if ( m|^( )?/\*? ...: \*?/ ?(.*)| ) { $edid .= $2 } | |
if ( /^(## )?EDID Dump (Port \d+) - End( ##)?/ and length $edid > 0 ) { $edid =~ s/0x(..)(, *)?/$1/g; print "'"${thefilename}"':" . $thepath . "/" . $theport . " = <" . $edid . ">\n" } | |
} | |
' < "${thefilename}" | |
); do | |
(( debug )) && echo ":" loadoneediditem "${ediditem}" 1>&2 | |
loadoneediditem "${ediditem}" | |
done | |
done | |
} | |
loadmamfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
IFS=$'\n' | |
local ediditem="" | |
for ediditem in $( | |
perl -e ' | |
$thepath=""; while (<>) { | |
if ( m|^(Monitor( #.*\S)?)\s*$| ) { $thepath = $1 } | |
if ( /^Raw data.?$/ ) { $edid = "" } | |
if ( m| *(([0-9A-F]{2},){31}[0-9A-F]{2},).?$| ) { $edid .= $1 } | |
if ( m| *(([0-9A-F]{2},){31}[0-9A-F]{2})[^,]*$| ) { $edid .= $1 . ","; $edid =~ s/(..),/$1/g; print "'"${thefilename}"':" . $thepath . " = <" . lc $edid . ">\n" } | |
} | |
' < "${thefilename}" | |
); do | |
(( debug )) && echo ":" loadoneediditem "${ediditem}" 1>&2 | |
loadoneediditem "${ediditem}" | |
done | |
done | |
} | |
loadagdc () { | |
local thefilename="$1" | |
if [[ -z $thefilename ]]; then | |
thefilename=$(mktemp /tmp/local_AGDCDiagnose.XXXXXX) || exit 1 | |
fi | |
/System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/AGDCDiagnose -a > "$thefilename" 2>&1 | |
loadagdcfile "$thefilename" | |
} | |
loadioregfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
IFS=$'\n' | |
local ediditem="" | |
for ediditem in $( | |
perl -e ' | |
$thepath=""; while (<>) { | |
if ( /^([ |]*)\+\-o (.+) </ ) { $indent = (length $1) / 2; $name = $2; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| } | |
if ( /^[ |]*"([^"]+)" = <(00ffffffffffff00[0-9a-f]*)>/i ) { print "'"${thefilename}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" } | |
} | |
' < "${thefilename}" | |
); do | |
loadoneediditem "${ediditem}" | |
done | |
done | |
} | |
loadallrezfile () { | |
while (( $# )); do | |
local thefilename="$1" | |
shift | |
IFS=$'\n' | |
local ediditem="" | |
for ediditem in $( | |
perl -e ' | |
$agdcpath=""; | |
$thepath=""; while (<>) { | |
if ( /^( *)(\w.*?) += \{$/ ) { $indent = (length $1) / 4; $name = $2 =~ s|/|•|gr; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| } | |
elsif ( /^( *)\}.*$/ ) { $indent = (length $1) / 4; $thepath =~ s|^((/[^/]*){$indent}).*|$1| } | |
if ( /^ *(\w.*?) += (00ffffffffffff00[0-9a-f]*)/i ) { $var = $1; $edid = $2; print "'"${thefilename}"'" . ":" . ($thepath =~ s|•|/|gr) . "/" . $var . " = <" . $edid . ">\n" } | |
if ( m|^(?:\[\d+\] )?IOService:(/.*)| ) { $agdcpath = $1 } | |
if ( /^[#|]* ?EDID Dump (Port \d+) - Start( ##)?/ ) { $theport = $1; $edid = "" } | |
if ( m|^( )?/\*? ...: \*?/ ?(.*)| ) { $edid .= $2 } | |
if ( /^(## )?EDID Dump (Port \d+) - End( ##)?/ and length $edid > 0 ) { $edid =~ s/0x(..)(, *)?/$1/g; print "'"${thefilename}"':" . $agdcpath . "/" . $theport . " = <" . $edid . ">\n" } | |
} | |
' < "${thefilename}" | |
); do | |
loadoneediditem "${ediditem}" | |
done | |
done | |
} | |
loadioreg () { | |
local tmpfilename="" | |
tmpfilename=$(mktemp /tmp/local_ioreg.XXXXXX) || exit 1 | |
ioreg -lw0 > "$tmpfilename" | |
loadioregfile "$tmpfilename" | |
} | |
listedids () { | |
local saveedid="$theedid" | |
local i="" | |
for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
useedidnum "$i" | |
echo "$i)" | |
echo "vendor:$thevendorid ($thevendorcode) product:$theproductid ID:${thevendorcode}$(printf "%04X" $theproductid)/$(printf "$theedid" | md5 | sed -E 's/(.{12}).*/\1/' | tr 'abcdef' 'ABCDEF')" | |
echo "override product name:$theproductfile" | |
echo "override date name:$themanufacturefile" | |
echo "strings:$(echo -n "$theedid" | xxd -p -r | strings - | tr -s '\n\t ' ' ' | sed '/ *$/s///' )" | |
echo "theedid=$theedid" | |
echo "sources:" | |
echo "${paths[i+arrstart-1]}" | |
echo | |
done | |
useedidstring "$saveedid" | |
} | |
#========================================================================================= | |
# Files from EDID | |
edidbin () { | |
echo -n "$theedid" | xxd -p -r | |
} | |
edidbinall () { | |
local saveedid="$theedid" | |
local thefolderpath="$1" | |
local i="" | |
for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
useedidnum "$i" | |
edidbin > "${thefolderpath:=.}/${thefilenamebase}.bin" | |
done | |
useedidstring "$saveedid" | |
} | |
decode () { | |
local tmpfilename="" | |
tmpfilename=$(mktemp /tmp/edidbin.XXXXXX) || exit 1 | |
edidbin > "$tmpfilename" | |
"$edid_decode" -cC --skip-sha "$tmpfilename" 2>&1 | |
} | |
decodeall () { | |
local dostdout=0 | |
local thefolderpath="." | |
while (( $# )); do | |
case "$1" in | |
"-s") dostdout=1 ;; | |
*) thefolderpath="$1" ;; | |
esac | |
shift | |
done | |
local saveedid="$theedid" | |
local i="" | |
for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
useedidnum "$i" | |
if ((dostdout)); then | |
echo "==========================================================================================" | |
echo "$i)" | |
echo "${paths[i+arrstart-1]}" | |
echo "==================" | |
decode | |
else | |
decode > "${thefolderpath:=.}/${thefilenamebase}_edid-decode.txt" | |
fi | |
done | |
useedidstring "$saveedid" | |
} | |
agdcdevicedump () { | |
# Indent the Device Dump section of a AGDCDiagnose file. | |
# Note that some DICT sizes seem to be greater than the number of lines included in the dump. | |
local thefilename="$1" | |
perl -e ' | |
sub dict { | |
my $indent = $_[0]; | |
my $lines = $_[1]; | |
while ($lines-- != 0) { | |
my $theline = ""; | |
do { | |
$theline = <>; | |
} until ($theline !~ /^$/); | |
die if ($theline =~ /\-\-END Device Dump\-\-/); | |
print $indent.$theline; | |
dict($indent."\t", $1) if ($theline =~ /^.*[\t ]DICT[\t ]+(\d+)\n$/); | |
} | |
} | |
while (<>) { if ( /\-\-BEGIN Device Dump\-\-/ ) { print "----------------\n"; eval { dict("", -1); } } } | |
' < "$thefilename" | |
} | |
updateoverride () { | |
local thefilename="$1" | |
plutil -replace IODisplayEDID -data "$(echo -n "$theedid" | xxd -p -r | base64)" "${thefilename}" | |
plutil -replace DisplayProductID -integer $((0x${theedid:22:2}${theedid:20:2})) "${thefilename}" | |
plutil -replace DisplayVendorID -integer $((0x${theedid:16:4})) "${thefilename}" | |
} | |
makeoverride () { | |
local noedid=0 | |
if [[ $1 == '-noedid' ]]; then | |
noedid=1 | |
shift | |
fi | |
local thefilename="${theproductfile}" | |
if [[ $1 == '-m' ]]; then | |
thefilename="$themanufacturefile" | |
shift | |
fi | |
if [[ -z $1 ]]; then | |
[[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
else | |
thefilename="$1" | |
fi | |
[[ -f "${thefilename}" ]] && rm "${thefilename}" | |
/usr/libexec/PlistBuddy \ | |
-c "Add :DisplayProductID integer ${theproductid}" \ | |
-c "Add :DisplayVendorID integer ${thevendorid}" \ | |
-c 'Add :IODisplayEDID data ""' \ | |
-c 'Add :DisplayPixelDimensions data ""' \ | |
"${thefilename}" > /dev/null | |
plutil -replace 'IODisplayEDID' -data "$(echo -n "$theedid" | xxd -p -r | base64)" "${thefilename}" | |
# the hex is big endian | |
plutil -replace 'DisplayPixelDimensions' -data "$(printf "%08x%08x" $maxresH $maxresV | xxd -p -r | base64)" "${thefilename}" | |
if (( noedid )); then | |
plutil -remove 'IODisplayEDID' "${thefilename}" | |
fi | |
# // IOGraphicsLibInternal.h flags for IOGFlags in override file | |
# enum { | |
# // disable any use of scaled modes, | |
# kOvrFlagDisableScaling = 0x00000001, | |
# // remove driver modes, | |
# kOvrFlagDisableNonScaled = 0x00000002, | |
# // disable scaled modes made up by the system (just use the override list) | |
# kOvrFlagDisableGenerated = 0x00000004 | |
# }; | |
} | |
installoverride () { | |
local thedir="/System" | |
local dstfile="$theproductfile" | |
local thefilename="" | |
while (( $# )); do | |
if [[ $1 == '-l' ]]; then | |
thedir="" | |
elif [[ $1 == '-m' ]]; then | |
dstfile="$themanufacturefile" | |
else | |
thefilename="$1" | |
fi | |
shift | |
done | |
[[ -z $thefilename ]] && thefilename="${dstfile}" | |
echo "# source file: ${thefilename}" | |
mount | grep ' on / ' | grep -q 'read-only' && sudo mount -uw / | |
local fulldir="$thedir/Library/Displays/Contents/Resources/Overrides" | |
[[ -d "${fulldir}/${thevendordir}" ]] || sudo mkdir -p "${fulldir}/${thevendordir}" | |
sudo cp "${thefilename}" "${fulldir}/${dstfile}" | |
echo "# Installed file: ${fulldir}/${dstfile}" | |
if [[ "$(basename "${dstfile}")" =~ DisplayProductID-.* ]]; then | |
[[ -f ${fulldir}/$themanufacturefile ]] && echo "# Warning: alternative override exists at ${fulldir}/$themanufacturefile" | |
else | |
[[ -f ${fulldir}/$theproductfile ]] && echo "# Warning: alternative override exists at ${fulldir}/$theproductfile" | |
fi | |
} | |
makemtdd () { | |
local donew=0 | |
local thefilename="" | |
while (( $# )); do | |
case "$1" in | |
"-n") donew=1 ;; | |
*) thefilename="$1" ;; | |
esac | |
shift | |
done | |
if [[ -z ${thefilename} ]]; then | |
[[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
thefilename="${theproductfile}.mtdd" | |
fi | |
[[ -f "${thefilename}" ]] && rm "${thefilename}" | |
/usr/libexec/PlistBuddy \ | |
-c 'Add display dict' \ | |
-c 'Add :display:linkmode string multi-cable' \ | |
-c 'Add :display:overlay data ""' \ | |
-c "Add :display:streamcount integer $((tileCountH * tileCountV))" \ | |
-c "Add :display:tileinfo string ($tileCountH,$tileCountV)" \ | |
-c "Add :productid string $(printf "0x%04x" $theproductid)" \ | |
-c "Add :serial integer 1" \ | |
-c "Add :vendorid string $(printf "0x%04x" $thevendorid)" \ | |
-c "Add :version string 1.3" \ | |
"${thefilename}" > /dev/null | |
if (( donew )); then | |
plutil -insert 'display.backendtiming' -xml "<array>$(echo "$tileTimings" | perl -pe's/^[0-9]+ ([0-9]+)x([0-9]+)@([0-9.]+)Hz [0-9.]+kHz ([0-9]+)\.([0-9]+)MHz h\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\) v\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\).*/"<string>$1,$6,$7,".($1+$6+$7+$8)."x$2,$10,$11,".($2+$10+$11+$12)."\@$4${5}0000,$9$13<\/string>"/e')<string>*</string></array>" "${thefilename}" | |
plutil -insert 'display.frontendtiming' -xml "<array>$(echo "$tileTimings" | perl -pe's/^[0-9]+ ([0-9]+)x([0-9]+)@([0-9.]+)Hz [0-9.]+kHz ([0-9]+)\.([0-9]+)MHz h\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\) v\(([0-9]+) ([0-9]+) ([0-9]+) ([-+])\).*/"<string>".($1*2).",".($6*2).",".($7*2).",".($1+$6+$7+$8)."x$2,$10,$11,".($2+$10+$11+$12)."\@".("$4${5}0000" * 2).",$9$13<\/string>"/e')</array>" "${thefilename}" | |
else | |
plutil -insert 'display.backendtiming' -string "${tileSizeH}x${tileSizeV}@${tileRefresh}" "${thefilename}" | |
plutil -insert 'display.frontendtiming' -string "$((tileSizeH * 2))x${tileSizeV}@${tileRefresh}" "${thefilename}" | |
fi | |
plutil -replace 'display.overlay' -data "$(echo -n "$theedid" | xxd -p -r | base64)" "${thefilename}" | |
if (( HasAudio )); then | |
plutil -insert 'display.audio' -string '(1,1)' "${thefilename}" | |
fi | |
echo "# Created mtdd file: ${thefilename}" | |
} | |
makepatch () { | |
local thefilename="$1" | |
if [[ -z ${thefilename} ]]; then | |
[[ -d "${thevendordir}" ]] || mkdir "${thevendordir}" | |
thefilename="${theproductfile}" | |
fi | |
[[ -f "${thefilename}" ]] && rm "${thefilename}" | |
/usr/libexec/PlistBuddy \ | |
-c "Add :DisplayProductID integer ${theproductid}" \ | |
-c "Add :DisplayVendorID integer ${thevendorid}" \ | |
-c 'Add edid-patches array' \ | |
"${thefilename}" > /dev/null | |
local i="" | |
for ((i = 0 ; i < ${#patches[@]} ; i++)); do | |
local thepatch="${patches[i+arrstart]}" | |
local offset=$((${thepatch%:*})) | |
local data=${thepatch#*:} | |
plutil -insert "edid-patches.$i" -xml "<dict> | |
<key>offset</key> | |
<integer>$offset</integer> | |
<key>data</key> | |
<data>$(echo -n "$data" | xxd -p -r | base64)</data> | |
</dict>" "${thefilename}" | |
done | |
echo "# Created patch file: ${thefilename}" | |
} | |
makemtddall () { | |
local saveedid="$theedid" | |
local thefolderpath="$1" | |
local i="" | |
for ((i = 1 ; i <= ${#edids[@]} ; i++)); do | |
useedidnum "$i" | |
[[ -d "${thefolderpath:=.}/${thevendordir}" ]] || mkdir "${thefolderpath}/${thevendordir}" | |
makemtdd "${thefolderpath}/$theproductfile".mtdd | |
done | |
useedidstring "$saveedid" | |
} | |
translateoui () { | |
local oui=$1 | |
if [[ $oui =~ ([0-9]+)-([0-9]+)-([0-9]+) ]]; then | |
oui="$(eval "$(echo -n "$oui" | sed -E '/([0-9]+)-([0-9]+)-([0-9]+)/s//printf "%02X%02X%02X" \1 \2 \3/')")" | |
else | |
oui=${oui//-/} | |
oui=${oui//0x/} | |
oui=$(printf "%06X" $((0x$oui))) | |
fi | |
local thetype="" | |
for thetype in oui cid; do | |
[[ -f ~/${thetype}.txt ]] || curl -s "http://standards-oui.ieee.org/${thetype}/${thetype}.txt" > ~/${thetype}.txt | |
local thetranslate="" | |
thetranslate="$(sed -nE '/^'"$oui"'[ ]+\(base 16\)*(.*)/s//\1/p' ~/${thetype}.txt | tr -d '\t\r')" | |
[[ -n $thetranslate ]] && echo "$thetype: 0x$oui = $thetranslate" | |
done | |
} | |
translatevendor () { | |
local thevendorid=$1 | |
local vendorascii="" | |
local pnppattern="^[A-Za-z]{3}$" | |
if [[ $thevendorid =~ $pnppattern ]]; then | |
thevendorid=$(printf "%s" "$thevendorid" | xxd -p) | |
thevendorid=$(( (0x${thevendorid:0:2} & 31) << 10 + (0x${thevendorid:2:2} & 31) << 5 + (0x${thevendorid:4:2} & 31) )) | |
else | |
thevendorid=$((thevendorid)) | |
fi | |
vendorascii=$(printf "%06x" $((((thevendorid&0x7c00)<<6)+((thevendorid&0x3e0)<<3)+(thevendorid&0x1f)+0x404040)) | xxd -p -r) | |
if [[ ! -f ~/pnp_id_list.txt ]]; then | |
curl -s "https://uefi.org/uefi-pnp-export" | \ | |
tidy -wrap 0 -raw -utf8 -q | \ | |
perl -0777 -nE ' | |
s!(?: )*(</td>)!\1!g; | |
s!&!&!g; | |
while ( | |
m! | |
^<tr\ class=\"(?:odd|even)">\n | |
<td>(.*)</td>\n | |
<td>(...)</td>\n | |
<td>(../../....)</td>\n | |
</tr> | |
!xmg | |
) { | |
print $3 . " " . $2 . " " . $1 . "\n" | |
} | |
' | \ | |
sort -f -k 3 -k 2 -k 1 > ~/pnp_id_list.txt | |
fi | |
printf "0x%04x = %s = %s\n" $thevendorid "$vendorascii" "$(sed -nE '/[0-9/]+ '"$vendorascii"' (.*)/s//\1/p' ~/pnp_id_list.txt)" | |
# $(sed -nE '/^'$oui'[ ]+\(base 16\)*(.*)/s//\1/p' ~/oui.txt | tr -d '\t\r') | |
} | |
#========================================================================================= | |
edidhelp () { | |
cat << edidhelp_done | |
Commands: | |
Get EDID | |
loadagdc [dstfilepath] | |
loadioreg | |
loadstring hexstring [sourcename] | |
loadcurrentedid [sourcename] | |
loadbinfile filepath... | |
loadhexfile filepath... # reverses xxd | |
loadagdcfile filepath... | |
loadioregfile filepath... | |
loadoverridefile filepath... | |
loadmtddfile filepath... | |
loadswitchresxfile filepath... | |
loadmamfile filepath... # Monitor Asset Manager.txt | |
loadmamhexfile filepath... # Monitor Asset Manager.hex | |
loadmaminffile filepath... # Monitor Asset Manager.inf | |
loadmamdatfile filepath... # Monitor Asset Manager.dat | |
listedids | |
clearedids | |
Use EDID | |
useedidnum numberfromlist | |
useedidstring lowercasehexstring | |
Modify EDID | |
repairchecksums | |
replacebytes bytepos lowercasehexstring [numbytestoreplace] | |
deleteextensionblock blocknumber | |
deleteextensionblocktype blocktype(decimal or 0xhex) | |
adddisplayidextensionblock version producttype | |
addctaextensionblock version [YCbCrSupportbyte offset] | |
addchromasubsampling | |
removechromasubsampling | |
set8bpcmax | |
cleardate | |
clearserialnumber | |
setpreferredisnative preferredvalue(0/1) | |
clearchromaticity | |
clearestablishedtimings | |
clearmanufacturerstimings | |
clearstandardtimings | |
deletectablock offset | |
deletectablocktype ctablocktype(1, 2, ..., 6, 700, 701, ..., 70f, 710, ..., hex for extended) | |
addctablock offset newctablock... # -1 = insert before first CTA block; 0 = insert after last CTA block | |
deletedescriptor offset | |
addctadescriptor offset newctadescriptor... # -1 = insert before first CTA block; 0 = insert after last CTA block | |
createdetailedtiming MHz hactive hfront hpulse hback vactive[i=interlaced] vfront vpulse vback hsync(+/-) vsync(+/-) [hmm] [vmm] [hborder] [vborder] | |
createdummydescriptor | |
createemptydescriptor | |
replacedescriptor offset replacementdescriptor | |
createtype1timingdescriptor MHz hactive hfront hpulse hback vactive[i=interlaced] vfront vpulse vback hsync(+/-) vsync(+/-) [preferred(1/0)] | |
createtype1timingblock descriptors... | |
dumptype1timingdescriptor 20_byte_descriptor | |
deletedisplayidblock offset | |
deletedisplayidblocktype displayidblocktype(decimal or 0xhex) | |
adddisplayidblock offset newdisplayidblock... # -1 = insert before first DisplayID block; 0 = insert after last DisplayID block | |
applypatches filepath [sourcename] | |
Files from EDID | |
makemtdd [-n] [filepath] | |
The edid needs to be manually edited. | |
clearserialnumber | |
cleardate | |
#clearchromaticity | |
#clearestablishedtimings | |
#clearstandardtimings | |
#clearmanufacturerstimings | |
#removechromasubsampling | |
deletedisplayidblocktype 3 # remove type 1 DisplayID timings | |
deletedisplayidblocktype 0x12 # remove tile topology | |
# Add the deleted type 1 DisplayID timings, except replace the tile timing (backend timing) with a full size timing (frontend timing). | |
# Frontend timing in this example doubles the MHz and horizontal numbers of the backend timing. | |
adddisplayidblock 0 \$(createtype1timingblock 9aa00104ff0ea0002f8021006f083e0003000500 \$(createtype1timingdescriptor 1264.02 3840 96 64 20 2160 2 4 20 + - 1 )) | |
makepatch [filepath] | |
Creates an override file with edid-patches array from shell array variable called patches. e.g. patches=("94:0818900a" "304:7f00080000000000000000") | |
makemtddall [folderpath] | |
makeoverride [-noedid] [-m | dstfilepath] | |
updateoverride filepath | |
installoverride [-l] [-m] [srcfilepath] | |
decode | |
decodeall [-s | folderpath] | |
edidbin | |
edidbinall [folderpath] | |
dumpedid | |
dumpedidall [-s | folderpath] | |
agdcdevicedump filepath | |
Miscellaneous functions | |
translateoui oui # AGDCDiagnose DisplayPort registers and EDIDs may have an oui. | |
translatevendor pnp # a 15 bit value or a 3 character ASCII code. | |
Variables | |
dodump # Set to 1 to dump the EDID while changes are made to the EDID. | |
debug # Set to 1 to output debugging info (uses stderr) | |
DisplayIDblocks # An array of DisplayID blocks that are used in the EDID. Save these using something like saveDisplayIDblocks=(\$DisplayIDblocks) before modifying the EDID. | |
ctablocks # An array of CTA data blocks that are used in the EDID. Save these using something like savectablocks=(\$ctablocks) before modifying the EDID. | |
ctadescriptors # An array of CTA descriptors that are used in the EDID. Save these using something like savectadescriptors=(\$ctadescriptors) before modifying the EDID. | |
thetimings # a list of detailed timings (printf "%s\\n" "\${thetimings[@]}") | |
theproductfile # the name of an override file in the form of DisplayVendorID-%x/DisplayProductID-%x | |
themanufacturefile # the name of an override file in the form of DisplayVendorID-%x/DisplayYearManufacture-%d-DisplayWeekManufacture-%d | |
... | |
Help | |
edidhelp | |
edidhelp_done | |
} | |
#edidhelp | |
#========================================================================================= |
jaffacake
commented
Sep 13, 2021
via email
The problem is Apple Silicon Macs don't store EDID in ioreg and AGDCDiagnose doesn't read EDID on Apple Silicon Macs.
If you look at the EDIDUtil.sh script, you see that the loadagdc command uses something like this command:
/System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/AGDCDiagnose -a > /tmp/local_AGDCDiagnose.XXXXXX 2>&1
If you do that command manually and view the resulting file:
open /tmp/local_AGDCDiagnose.XXXXXX
You'll see that there's not much info (no EDID) produced for Apple Silicon Macs compared to Intel Macs.
This command is used to look at ioreg:
ioreg -lw0 > /tmp/local_ioreg.XXXXXX
For Apple Silicon Macs, it doesn't contain any EDIDs. An EDID is a binary blob that begins with 00ffffffffffff00
and is a multiple of 128 bytes in size.
My AllRez command also doesn't get EDID for Apple Silicon Macs. It needs an update for that but I don't have an Apple Silicon Mac to experiment with.
I think SwitchResX was updated to get EDID on Apple Silicon Macs? In that case, you can use the loadswitchresxfile
command from EDIDUtil.sh.
But even if you can get the EDID and create an override file, I don't think Apple Silicon Macs load EDID overrides from override files stored in /Library/Displays/Contents/Resources/Overrides
. I believe Apple Silicon Macs can load other info from override files stored in that location such as display name or scaled resolutions (both of which you can change using SwitchResX).
We don't know in what situation (if any) Apple Silicon Macs will load EDID overrides. Maybe the EDID override needs to be in /System/Library/Displays/Contents/Resources/Overrides
or maybe the name of the override file needs to use the DisplayProductID-####
format instead of the DisplayYearManufacture-####-DisplayWeekManufacture-####
format that SwitchResX uses.
I suppose one could grep
all the frameworks and daemons to see which access the Overrides path.
sudo grep -R "Resources/Overrides" /System/Library/Frameworks /Library/Frameworks /usr/libexec
However, in recent macOS versions, the framework binaries are removed and a cache is used instead so it makes finding the frameworks that access the overrides more difficult - unless you can find a way to split the cache into separate frameworks.
https://github.com/keith/dyld-shared-cache-extractor
On Intel Macs, displaypolicyd
and CoreDisplay.framework
read different info from override files. displaypolicyd
will read DisplayPort and DSC info while CoreDisplay.framework
reads scaled resolutions and display name EDID override and other stuff. There's also IOKit.framework
but I don't think that's used in modern macOS versions. displaypolicyd
won't read from /Library
though - it only reads from /System/Library
. It can read .mtdd file from /var/db/displaypolicyd/
https://forums.macrumors.com/threads/studio-display-natural-resolution.2338616/post-30974390
Anyway, if we can find the arm64e
code (used by Apple Silicon Macs) that accesses the overrides, then a disassembler such as Hopper.app can be used to maybe discover if IODisplayEDID
ever gets accessed and under what conditions it gets accessed.
I spoiled the EEPROM in the LCD display of my old map (late 2011)
When I do ioreg -lw0 > /tmp/local_ioreg.XXXXXX and search for EDID, I see the first three bytes are damaged. Is there a way to rewrite the EDID programmatically - may be through ioreg? (I know the right bytes sequence) to get my mbp back to work? (right now I have black screen, but I can connect via Remote Desktop)
I spoiled the EEPROM in the LCD display of my old map (late 2011)
When I do ioreg -lw0 > /tmp/local_ioreg.XXXXXX and search for EDID, I see the first three bytes are damaged. Is there a way to rewrite the EDID programmatically - may be through ioreg? (I know the right bytes sequence) to get my mbp back to work? (right now I have black screen, but I can connect via Remote Desktop)
How did the bytes get damaged in the first place? I don't know how to change them. You could try making an EDID override to see if that helps.
The first 8 bytes of an EDID are always 00ffffffffffff00
.
How did the bytes get damaged in the first place?
I did it because of my own stupidity, with my own piece of code leveraging IOKit.i2c - it finds the right i2c device on a good mbp, but it doesn't on the damaged one, with framebuffer returning zero (which means a fault).
I looked through EDID override technics, but as far as I understood, it is a temporary fix until the next reboot (i.e. I won't see boot loader, I won't be able to chose the disk, etc). But most importantly, I didn't get how do I use it - I got the only screen with a damaged header in its EDID, and I don't know how do I find the right one among all those folders inside override directory.
I suppose you can't override the EDID if it won't load properly. Does SwitchResX show a display?
Can you attach zipped output from AllRez? It has code to read EDID.
What macOS version are you using?
I was using Catalina when I damaged it, now I can boot from El Capitan (which has Remote Desktop enabled). If you're curious about edid, it's now
ioreg -lw0 -r -c "IODisplayConnect" -d 2 +-o display0 <class IODisplayConnect, id 0x10000068e, registered, matched, active, busy 0 (0 ms), retain 6> | { | } | +-o AppleBacklightDisplay <class AppleBacklightDisplay, id 0x10000068f, registered, matched, active, busy 0 (0 ms), retain 9> { "IOClass" = "AppleBacklightDisplay" "CFBundleIdentifier" = "com.apple.iokit.IOGraphicsFamily" "IOProviderClass" = "IODisplayConnect" "DisplayProductID" = 40100 "IODisplayEDID" = <be0000ffffffff000610a49c0000000016130103802115780ae585a3544f9c260e505400000001010101010101010101010101010101ab22a0a050841a30302036004bcf10000019000000010006103000000000000000000a20000000fe004c544e31353442543038000a20000000fc00436f6c6f72204c43440a20202000ac> "IODisplayPrefsKey" = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/IGPU@2/AppleIntelFramebuffer@0/display0/AppleBacklightDisplay-610-9ca4" "IODisplayGUID" = 436849163854938112 "IOProbeScore" = 3000 "IODisplayParameters" = {"commit"={"reg"=0},"dsyp"={"min"=0,"max"=2,"value"=2},"fade-time3"={"min"=0,"max"=10000,"value"=500},"brightness"={"max"=1024,"min"=0,"value"=769},"brightness-fade"={"min"=0,"max"=1023,"value"=0},"fade-time2"={"min"=0,"max"=10000,"value"=4000},"fade-time1"={"min"=0,"max"=10000,"value"=500},"usable-linear-brightness"={"min"=452,"max"=1808,"value"=1162},"fade-style"={"min"=0,"max"=10,"value"=0},"linear-brightness"={"min"=0,"max"=1808,"value"=1162}} "IOPowerManagement" = {"DevicePowerState"=3,"CapabilityFlags"=49152,"CurrentPowerState"=3,"MaxPowerState"=3} "IOMatchCategory" = "IODefaultMatchCategory" "IODisplayConnectFlags" = <00080000> "DisplayVendorID" = 1552 "DisplayParameterHandlerUsesCharPtr" = Yes "DisplaySerialNumber" = 0 }
So, it's a wrong header.
Right now I've soldered two wires to the pull-up resistor next to LVDS connector, and my logic analyzer proved the problem: I can see the communication. So now I don't have an i2c programmer at hand (I'm about to order one), and I'm not sure if my programming intervention will be safe for SN74LV4066A, which switches the signal from the PCh(U1800) or that U1800 itself?
Right now, because of those wires, I cannot install and run AllRez.
But even if I cope with overriding EDID, I won't be able to launch from my Catalina disk (which doesn't have Remote Desktop)?
It looks like a valid EDID except the first 3 bytes. The checksum is correct when the first 3 bytes are corrected.
macOS has built-in screen sharing but it needs to be enabled in the Sharing preferences panel before it can be used. Then you can screen share from a different Mac. I think the built in screen sharing may also work as VNC?
If you can't do screen sharing, then you can try connecting an external display.
Another option is to boot into Target Disk Mode but I don't know if that works without the built-in display? When the Mac is in Target Disk Mode, you can connect it to another Mac to use it as a hard drive which you can boot from.
Another option is to remove the hard drive and use it to boot a different Mac.
The display LTN154BT08 or APP9CA4 (vendor:Apple product:9ca4) appears to be this one or similar:
https://www.panelook.com/LTN154BT08-R06_Samsung_15.4_LCM_overview_17497.html
Thanks, I knew all that. I can't connect the external display because of disabled (because faulty) dGPU.
Solved the problem by solderin thin wires directly to the panel cable connector and flashing the EDID.