Skip to content

Instantly share code, notes, and snippets.

@joevt
Last active July 23, 2024 03:03
Show Gist options
  • Save joevt/32e5efffe3459958759fb702579b9529 to your computer and use it in GitHub Desktop.
Save joevt/32e5efffe3459958759fb702579b9529 to your computer and use it in GitHub Desktop.
A set of shell functions used to view and edit EDIDs.
#!/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!(?:&nbsp;)*(</td>)!\1!g;
s!&amp;!&!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
Copy link

jaffacake commented Sep 13, 2021 via email

@conor888
Copy link

conor888 commented Jan 9, 2023

Tried to follow the steps from the first step to remove chroma subsampling, but finding that no EDIDs are loaded after loadagdc and loadioreg:

image

Running macOS 13.1 on a MacBook Pro 16" M1 Pro. Is this script no longer working under 13?

@joevt
Copy link
Author

joevt commented Jan 10, 2023

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.

@thirstyone
Copy link

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)

@joevt
Copy link
Author

joevt commented Jun 3, 2023

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.

@thirstyone
Copy link

thirstyone commented Jun 3, 2023

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.

@joevt
Copy link
Author

joevt commented Jun 3, 2023

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?

@thirstyone
Copy link

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)?

@joevt
Copy link
Author

joevt commented Jun 4, 2023

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

@thirstyone
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment