Skip to content

Instantly share code, notes, and snippets.

@fhoech
Forked from resoau/patch-edid-mod.rb
Last active April 13, 2022 08:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fhoech/2995242043cd937b5fcb4657d6167c2f to your computer and use it in GitHub Desktop.
Save fhoech/2995242043cd937b5fcb4657d6167c2f to your computer and use it in GitHub Desktop.
#!/usr/bin/ruby
# Create display override file to force Mac OS X to use RGB mode for Display
# see http://embdev.net/topic/284710
require 'base64'
def replacebytes (edid, pos, thebytes, newlen)
bytepos = pos * 2
thelen = thebytes.length
if newlen.to_s. != ''
thelen = newlen * 2
end
edid = edid[0, bytepos] + thebytes + edid[(bytepos + thelen)..-1]
return edid
end
def repairchecksums (edid)
blockoffset = 0
bytes = edid.scan(/../).map{|x|Integer("0x#{x}")}.flatten
while blockoffset * 2 < edid.length do
checksum = (0x100-(bytes[blockoffset,126].reduce(:+) % 256)) % 256
edid = replacebytes(edid, blockoffset + 127, sprintf("%02x", checksum), "")
blockoffset += 128
end
return edid
end
def deleteblock (edid, blockoffset)
if blockoffset * 2 < edid.length
edid = replacebytes(edid, blockoffset, "", 128)
edid = replacebytes(edid, 126, sprintf("%02x", Integer(edid[252,2], 16) - 1), "")
else
puts "Block at #{blockoffset} doesn't exist"
end
return edid
end
def deleteblocktype (edid, blocktype)
blockoffset = 128
while blockoffset * 2 < edid.length do
blocktag=Integer(edid[blockoffset*2, 2], 16)
if blocktag == blocktype
edid = deleteblock(edid, blockoffset)
else
blockoffset += 128
end
end
end
data=`ioreg -l -d0 -w 0 -r -c AppleDisplay`
edids=data.scan(/IODisplayedid.*?<([a-z0-9]+)>/i).flatten
vendorids=data.scan(/DisplayVendorID.*?([0-9]+)/i).flatten
productids=data.scan(/DisplayProductID.*?([0-9]+)/i).flatten
displays = []
edids.each_with_index do |edid, i|
disp = { "edid_hex"=>edid, "vendorid"=>vendorids[i].to_i, "productid"=>productids[i].to_i }
displays.push(disp)
end
# Process all displays
if displays.length > 1
puts "Found %d displays! You should only install the override file for the one which" % displays.length
puts "is giving you problems.","\n"
end
displays.each do |disp|
# Retrieve monitor model from edid data
monitor_name=[disp["edid_hex"].match(/000000fc00(.*?)0a/){|m|m[1]}.to_s].pack("H*")
if monitor_name.empty?
monitor_name = "Display"
end
puts "found display '#{monitor_name}': vendorid #{disp["vendorid"]}, productid #{disp["productid"]}, edid:\n#{disp["edid_hex"]}"
bytes = disp["edid_hex"].scan(/../).map{|x|Integer("0x#{x}")}.flatten
edid = disp["edid_hex"]
edidversion = Float(Integer(edid[0x12 * 2, 2], 16).to_s + "." + Integer(edid[0x13 * 2, 2], 16).to_s) # 1.3 or 1.4
isdigital = Integer(edid[0x14 * 2, 1], 16) > 7 # 0 or 1
puts "edid #{edidversion}"
puts "Is Digital #{isdigital}"
featuresupportbyte = Integer(edid[0x18 * 2, 2], 16)
featuresupport = (featuresupportbyte >> 3) & 3
puts "Featuresupport #{featuresupport}"
if edidversion < 1.4
case featuresupport
when 0
puts " Monochrome or Grayscale"
when 1
puts " RGB color"
when 2
puts " Non-RGB color"
when 3
puts " Undefined"
end
else
case (isdigital ? "1" : "0") + featuresupport.to_s
when "00"
puts " Monochrome or Grayscale"
when "01"
puts " RGB color"
when "02"
puts " Non-RGB color"
when "03"
puts " Undefined"
when "10"
puts " RGB 4:4:4"
when "11"
puts " RGB 4:4:4 + YCrCb 4:4:4"
when "12"
puts " RGB 4:4:4 + YCrCb 4:2:2"
when "13"
puts " RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2"
end
newfeaturesupportbyte = (featuresupportbyte & ~0x18) | (0 << 3)
edid = replacebytes(edid, 0x18, sprintf("%02x", newfeaturesupportbyte), "")
puts ' changed to "Monochrome or Grayscale"'
end
blockoffset = 128
while blockoffset * 2 < edid.length do
theblock = edid[blockoffset * 2, 254]
blocktag = Integer(theblock[0, 2], 16)
if blocktag == 2
ctaversion = Integer(theblock[2, 2], 16)
if ctaversion > 01
puts "cta-861 extension block with new version #{ctaversion} at #{blockoffset}"
yCbCrSupportbyte = Integer(theblock[6, 2], 16)
yCbCrSupport = (yCbCrSupportbyte >> 4) & 3
case yCbCrSupport
when 0
puts " no yCbCr support"
when 1
puts " yCbCr 4:2:2"
when 2
puts " yCbCr 4:4:4"
when 3
puts " yCbCr 4:4:4, yCbCr 4:2:2"
end
newyCbCrSupportbyte = (yCbCrSupportbyte & ~0x30) | (0 << 4)
edid = replacebytes(edid, blockoffset + 0x03, sprintf("%02x", newyCbCrSupportbyte), "")
puts ' changed to "no yCbCr support"'
if ctaversion > 02
detailedTimingDescriptorsOffset = Integer(theblock[4, 2], 16)
ctadatablockoffset = 4
while ctadatablockoffset < detailedTimingDescriptorsOffset do
ctadatablocklength = (Integer(theblock[ctadatablockoffset * 2, 2], 16) & 0x1f) + 1
ctadatablock = theblock[ctadatablockoffset * 2, ctadatablocklength * 2]
ctatagcode = (Integer(ctadatablock[0, 2], 16) >> 5) & 0x1f
if ctatagcode == 7
ctatagcode = "e" + Integer(ctadatablock[2, 2], 16).to_s
end
puts "#{ctadatablockoffset}: tag:#{ctatagcode} length:#{ctadatablocklength} block:#{ctadatablock}"
case ctatagcode
when 3
IEEEOUI = ctadatablock[6, 2] + ctadatablock[4, 2] + ctadatablock[2, 2]
puts "Vendor Specific IEEEOUI:"
case IEEEOUI
when "000c03"
puts " HDMI Licensing, LLC -> h14b VSDB"
h14bChunk = ctadatablock[12, 2]
if h14bChunk.length == 2
h14bByte = Integer(h14bChunk, 16)
h14b = (h14bByte >> 3) & 1
case h14b
when 0
puts " Support yCbCr 4:4:4 - No"
when 1
puts " Support yCbCr 4:4:4 - Yes"
end
newh14bByte = (h14bByte & ~0x08) | (0 << 3) # 0: No (RGB only), 1: Yes (yCbCr 4:4:4)
edid = replacebytes(edid, blockoffset + ctadatablockoffset + 6, sprintf("%02x", newh14bByte), "")
puts " changed to No"
else
puts " Length 0, skipping"
end
when "c45dd8"
puts " HDMI Forum -> hf-VSDB"
hfByte = Integer(ctadatablock[14, 2], 16)
hf = (hfByte >> 0) & 7
case hf
when 0
puts " 4:2:0 10/12/16 bpc - No"
when 1
puts " 4:2:0 10 bpc - Yes"
when 2
puts " 4:2:0 12 bpc - Yes"
when 3
puts " 4:2:0 10/12 bpc - Yes"
when 4
puts " 4:2:0 16 bpc - Yes"
when 5
puts " 4:2:0 10/16 bpc - Yes"
when 6
puts " 4:2:0 12/16 bpc - Yes"
when 7
puts " 4:2:0 10/12/16 bpc - Yes"
end
newhfByte = (hfByte & ~0x07) | (0 << 0)
edid = replacebytes(edid, blockoffset + ctadatablockoffset + 7, sprintf("%02x", newhfByte), "")
puts " changed to No"
else
echo " Unknown OUI"
end
when "e14"
puts "yCbCr 4:2:0 Video Data Block"
when "e15"
puts "yCbCr 4:2:0 Capability Map Data Block at #{blockoffset+ctadatablockoffset} : #{ctadatablock}"
ctadatablock = ctadatablock[0, 4] + ((ctadatablock[4..-1]).gsub! '.' '0')
edid = replacebytes(edid, blockoffset + ctadatablockoffset, ctadatablock)
puts " Changed to ctadatablock"
end
ctadatablockoffset += ctadatablocklength
end
end
else
puts "cta-861 extension block with old version #{ctaversion} at #{blockoffset}"
end
else
puts "Extension block at #{blockoffset}: type:#{blocktag}"
end
blockoffset += 128
end
edid = repairchecksums(edid)
bytes = edid.scan(/../).map{|x|Integer("0x#{x}")}.flatten
puts "new edid:\n#{bytes.map{|b|"%02X"%b}.join}"
Dir.mkdir("DisplayVendorID-%x" % disp["vendorid"]) rescue nil
f = File.open("DisplayVendorID-%x/DisplayProductID-%x" % [disp["vendorid"], disp["productid"]], 'w')
f.write '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">'
f.write "
<dict>
<key>DisplayProductName</key>
<string>#{monitor_name} - forced RGB mode (edid override)</string>
<key>IODisplayedid</key>
<data>#{Base64.encode64(bytes.pack('C*'))}</data>
<key>DisplayVendorID</key>
<integer>#{disp["vendorid"]}</integer>
<key>DisplayProductID</key>
<integer>#{disp["productid"]}</integer>
</dict>
</plist>"
f.close
puts "\n"
end # displays.each
@radianttap
Copy link

Minor issue: on line 208, replace echo with puts

@aleskrejci
Copy link

aleskrejci commented May 30, 2021

Hi @fhoech, thank you for sharing the script. It seems to be much more thorough than other EDID patches so I hope it will actually fix my issue. However, I'm having trouble getting it to work. Would you mind taking a look?

Last 5 lines are the error, the rest is just for context. Is there anything I can change to make it work? Thanks!

38: tag:3 length:8 block:67d85dc401788003
patch-edid-mod.rb:161: warning: already initialized constant IEEEOUI
patch-edid-mod.rb:161: warning: previous definition of IEEEOUI was here
Vendor Specific IEEEOUI:
 HDMI Forum -> hf-VSDB
    4:2:0 10/12 bpc - Yes
        changed to No
46: tag:e15 length:3 block:e20f81
yCbCr 4:2:0 Capability Map Data Block at 174 : e20f81
Traceback (most recent call last):
	3: from patch-edid-mod.rb:67:in `<main>'
	2: from patch-edid-mod.rb:67:in `each'
	1: from patch-edid-mod.rb:214:in `block in <main>'
patch-edid-mod.rb:214:in `+': no implicit conversion of Enumerator into String (TypeError)

@aleskrejci
Copy link

My colleague was able to help me. It's the first script that actually fixed the colors while maintaining the max resolution! Even though it forces 30Hz instead of 60Hz, it's still progress.

If anyone wants to try, the updated version is here: https://gist.github.com/aleskrejci/05c903f7b245bf9dd6f95b08a2afcaa1

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