#!/bin/bash
# $Id: mach_mypal,v 1.55 2017/11/18 02:04:59 bsittler Exp $
#
# NOTE: This script DOES NOT WORK on Mac OS X version 10.5 or above
# and DOES NOT WORK on 64-bit kernels.
#
# WARNING: This script modifies your running operating system kernel.
# This is potentially very risky, as the kernel is what allows everything
# else to run. Although I have made every attempt to ensure it is safe
# (and believe it is), I can't guarantee it or be held liable if things
# go wrong. in particular:
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHOR OR ANY OTHER CONTRIBUTOR BE LIABLE FOR
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# If you're looking for a way to change the colors used by the
# Terminal.app terminal emulator which runs under the Mac OS X Quartz
# windowing system, see TerminalColors instead:
#
#    http://www.culater.net/software/TerminalColors/TerminalColors.php
#
# This script changes the Darwin/Mac OS X Console's color palette for
# use with the MyMan video game. To run it, save this to a file called
# 'mach_mypal' and type:
#
#    sudo /bin/bash mach_mypal --install
#
# Get the MyMan video game here:
#
#    http://myman.sf.net/
#
# Get terminfo for TERM=xnuppc here:
#
#    http://xent.com/~bsittler/xnuppc.ti
#
# On Intel and PowerPC platforms, Apple's Darwin operating system and
# Mac OS X use a full-screen system console derived from a NetBSD
# framebuffer console. It is an ANSI-style terminal, and is not really
# VT100 compatible.
#
# Under Mac OS X (both PowerPC and Intel), this is the system console
# driver used while in single-user mode [reachable by holding down
# Command-S during the boot process] and when logged in using console
# mode [reachable by typing ">console" at the graphical login prompt.]
#
# NOTE: Under Mac OS X version 10.4, you'll need to enable the
# password dialog inside the System Preferences | Accounts | Login
# Options panel by disabling the "Automatically log in as" option and
# setting the "Display login window as" option to "Name and password"
#
# NOTE: Under Mac OS X version 10.4 (and possibly other versions) the
# console is only usable when the "Apple Remote Desktop" service is
# disabled in System Preferences | Sharing | Services.
#
# NOTE: Alternate boot loaders such as rEFIt may interfere with the
# boot arguments needed to enable /dev/kmem -- if you have problems
# consider disabling or reconfiguring the alternate boot loader to
# send the boot-args kmem=1 to the Darwin kernel.
#
# NOTE: This script requires access to kernel memory. By default it
# enables the built-in /dev/kmem by adding the kmem=1 option to the
# nvram boot-args -- however, it may be possible to use it with Amit
# Singh's alternate implementation (not tested, though.) For more
# information, see:
#
#    http://www.osxbook.com/book/bonus/chapter8/kma/
#
# NOTE: This script needs the 'nm' utility; get it from Xcode or
# odcctools

export KERNEL_ARCH="$(test -x /usr/bin/uname && /usr/bin/uname -m || uname -m)"

export KERNEL_FILE="$(test -f /System/Library/Kernels/kernel && echo /System/Library/Kernels/kernel || ( test -f /mach_kernel && echo /mach_kernel || echo /mach ) )"

export KERNEL_MEMORY="/dev/kmem"

export KERNEL_CLUT_SYMBOL="_appleClut8"

export KERNEL_FONT_SYMBOL="_iso_font"
export KERNEL_FONT_SIZE=$((256 * 16))

export KERNEL_PALETTE_SYMBOL="${KERNEL_FONT_SYMBOL}"
export KERNEL_PALETTE_OFFSET=$((${KERNEL_FONT_SIZE} + $( [ :"$(test -x /usr/bin/uname && /usr/bin/uname -p || uname -p)" = :powerpc ] && echo 44 || echo 32) ))
export KERNEL_PALETTE_101010=0

if [ :"$(test -x /usr/bin/uname && /usr/bin/uname -r || uname -r)" = :10.8.0 ]
then
    KERNEL_PALETTE_SYMBOL='_apple_protect_pager_ops'
    KERNEL_PALETTE_OFFSET=48000
    KERNEL_PALETTE_101010=1
fi

# format is R G B or R1 G1 B1  R2 G2 B2 (in the latter case the average
# of the two triples will be used); scale is 0 ... 1000 (inclusive)

export KERNEL_PALETTE_0="   0    0    0                "
export KERNEL_PALETTE_1=" 860  580  270  1000    0    0"
export KERNEL_PALETTE_2="   0  710    0   580 1000    0"
export KERNEL_PALETTE_3="1000  710  270  1000 1000    0"
export KERNEL_PALETTE_4="   0    0  860   120  120  860"
export KERNEL_PALETTE_5="1000  120  580   980  700  840"
export KERNEL_PALETTE_6="   0  860  860     0 1000  860"
export KERNEL_PALETTE_7="1000  710  580   860  860  860"

export PATH=/sbin:/bin:/usr/sbin:/usr/bin

if test -r /etc/defaults/mach_console
then
    . /etc/defaults/mach_console
fi

on_console=0
if test -t
then
    if test :"${TTY:-`tty 2>/dev/null`}" = :"/dev/console"
    then
        on_console=1
    fi
fi
if test :"$*" = :"--test"
then
    ret=0
    if test $on_console != 1
    then
        echo "Error: Not connected to tty /dev/console" >&2
        ret=1
    fi
    if test ! -x /usr/sbin/mach_mypal
    then
        echo "Error: /usr/sbin/mach_mypal is not executable" >&2
        ret=1
    fi
    if test ! -e /dev/kmem
    then
        echo "Error: /dev/kmem does not exist" >&2
        ret=1
    fi
    sgr ()
    {
        printf '\x1b[%dm' "$1"
    }
    setaf ()
    {
        sgr $(( 30 + $1 ))
    }
    setaf_7="$(setaf 7)"
    setab ()
    {
        sgr $(( 40 + $1 ))
    }
    setab_0="$(setab 0)"
    sgr0="$(sgr 0)"
    tput_setaf_7="$(tput setaf 7 2>/dev/null)"
    tput_setab_0="$(tput setab 0 2>/dev/null)"
    tput_sgr0="$(tput sgr0 2>/dev/null)"
    if test \
        :"${tput_setaf_7/${setaf_7}/}" = :"${tput_setaf_7}" -o \
        :"${tput_setab_0/${setab_0}/}" = :"${tput_setab_0}"
    then
        echo "Error: terminfo does not know how to use color for TERM=${TERM}" >&2
        ret=1
    fi
    fg=0
    bg=7
    echo ""
    echo -n " ANSI color #"
    echo -n " Color Sample"
    echo -n "  R1   G1   B1    R2   G2   B2 "
    echo ""
    echo -n " ------------"
    echo -n " ------------"
    echo -n " ---- ---- ----  ---- ---- ----"
    echo ""
    while [ $fg -le 7 ]
    do
      sgr 0
      echo -n " ANSI color $fg"
      echo -n " "
      setaf $fg
      setab $bg
      echo -n " .:*#@"
      setaf $bg
      setab $fg
      echo -n "@#*:. "
      sgr 0
      KERNEL_PALETTE_fg="KERNEL_PALETTE_${fg}"
      echo -n " ${!KERNEL_PALETTE_fg}"
      echo ""
      fg=$(( $fg + 1 ))
      bg=0
    done
    echo ""
    echo "You should see $fg color samples corresponding to the mille-scale RGB values given."
    echo "When a color has two RGB triples, the perceived color should be their average."
    echo ""
    exit $ret
fi
test :"$(uname -s)" = :"Darwin" || {
    echo "$0: this is not Darwin, exiting" >&2
    exit 1
}
if test :"$*" = :"--uninstall"
then
    chmod 644 /usr/sbin/mach_mypal || exit $?
    echo "" >&2
    echo "After the next reboot your Darwin console should" >&2
    echo "use the default color palette." >&2
    echo "" >&2
    echo "You may optionally remove the /usr/sbin/mach_mypal line from" >&2
    echo "/etc/rc.local by hand." >&2
    echo "" >&2
    exit 0
fi
test -x "$(type -p nm 2>/dev/null || echo /usr/bin/nm)" || {
    echo "$0: you need to install Xcode or odcctools, exiting" >&2
    exit 1
}
if test :"$*" = :"--install"
then
    if test :"$0" != :/usr/sbin/mach_mypal; then
        cp "$0" /usr/sbin/mach_mypal || exit $?
    fi
    chmod 755 /usr/sbin/mach_mypal || exit $?
    nvram boot-args="$(echo "kmem=1 $(nvram boot-args 2>/dev/null | cut -f 2- | sed 's/^kmem=1$//;s/^kmem=1 //;s/ kmem=1 //;s/ kmem=1$//')" | sed '$ s/  *$//')" || exit $?
    fgrep /usr/sbin/mach_mypal /etc/rc.local >/dev/null 2>&1 || echo "test -x /usr/sbin/mach_mypal && /usr/sbin/mach_mypal" >> /etc/rc.local || exit $?
    echo "" >&2
    echo "After the next reboot your Darwin console should" >&2
    echo "use a custom color palette." >&2
    if test -d /System/Library/CoreServices/loginwindow.app -a $on_console != 1; then
        echo "" >&2
        echo "Type \">console\" at the Mac OS X login window to get to the Darwin console." >&2
        echo "" >&2
        echo "NOTE: Under Mac OS X version 10.4, you'll need to enable the" >&2
        echo "password dialog inside the System Preferences | Accounts | Login" >&2
        echo "Options panel by disabling the \"Automatically log in as\" option and" >&2
        echo "setting the \"Display login window as\" option to \"Name and password\"" >&2
        echo "" >&2
        echo "NOTE: Under Mac OS X version 10.4 (and possibly other versions) the" >&2
        echo "console is only usable when the \"Apple Remote Desktop\" service is" >&2
        echo "disabled in System Preferences | Sharing | Services." >&2
    fi
    if test -d /efi/refit -a ! -e /dev/kmem; then
        echo "" >&2
        echo "NOTE: Alternate boot loaders such as rEFIt may interfere with the" >&2
        echo "boot arguments needed to enable /dev/kmem -- if you have problems" >&2
        echo "consider disabling or reconfiguring the alternate boot loader to" >&2
        echo "send the boot-args kmem=1 to the Darwin kernel." >&2
    fi
    if ! TERM=xnuppc infocmp 2>/dev/null > /dev/null
    then
        echo "" >&2
        echo "Get terminfo for TERM=xnuppc here:" >&2
        echo "    http://xent.com/~bsittler/xnuppc.ti" >&2
    fi
    echo "" >&2
    echo "To test, run the following on the console:" >&2
    echo "   /bin/bash /usr/sbin/mach_mypal --test" >&2
    echo "" >&2
    echo "To uninstall, type the following command:" >&2
    echo "   sudo /bin/bash /usr/sbin/mach_mypal --uninstall" >&2
    echo "" >&2
    exit 0
fi
if test :"$(echo -n $'\x43\x21' | od -h | head -1 | awk '{print $2}')" = :"4321"
then
    swab4 () {
        echo -n "$1$2$3$4"
    }
else
    swab4 () {
        echo -n "$4$3$2$1"
    }
fi
long () {
    swab4 $(printf \\\\\%o\ \\\\\%o\ \\\\\%o\ \\\\\%o $(($1>>24)) $((($1>>16)&255)) $((($1>>8)&255)) $(($1&255)))
}
shortx2 () {
    long $((($1*65536)+$1))
}
bytex4 () {
    shortx2 $((($1*256)+$1))
}
rgb101010 () {
    long $(((($1*1023/1000)*1024+($2*1023/1000))*1024+($3*1023/1000)))
}
rgb888 () {
    long $(((($1*255/1000)*256+($2*255/1000))*256+($3*255/1000)))
}
rgb555 () {
    shortx2 $(((($1*31/1000)*32+($2*31/1000))*32+($3*31/1000)))
}
rgb () {
    rgb555 "$@"
    rgb888 "$@"
    if [ :"${KERNEL_PALETTE_101010}" = :1 ]
    then
        rgb101010 "$@"
    fi
}
pal () {
    bytex4 "$@"
}
clut="$(
clut=
dd if="${KERNEL_MEMORY}" bs=1 skip=$( printf %lu $(( 0x$(nm -arch "$KERNEL_ARCH" "${KERNEL_FILE}"  | grep ' '"${KERNEL_CLUT_SYMBOL}"'$' | awk '{print $1}') )) ) count=$((256*3)) 2>/dev/null |
od -vtx1 |
fgrep ' ' |
sed 's/  */ /g' |
cut -d ' ' -f 2- |
tr ' a-f' '\nA-F'
)"
clut="${clut:-FF
FF
FF
00
00
00}"
rgbclut () {
    echo "$clut" |
    {
        local i=0 distance r g b best_distance=$((1000*1000*3+1)) best=0
        while read r && read g && read b
        do
          distance=$((($1*255/1000-0x$r)*($1*255/1000-0x$r)+($2*255/1000-0x$g)*($2*255/1000-0x$g)+($3*255/1000-0x$b)*($3*255/1000-0x$b)))
          if test $distance -le $best_distance
          then
              best_distance=$distance
              best=$i
          fi
          i=$(($i+1))
        done
        echo $best
    }
}
rgbpal () {
    pal $(rgbclut "$@")
    rgb "$@"
}
blend () {
    if test $# = 3
    then
        set "$@" "$@"
    fi
    rgbpal $((($1+$4)/2)) $((($2+$5)/2)) $((($3+$6)/2))
}
export szpalrgb="$(( $(printf "$(pal 0xff)""$(rgb 1000 1000    0)" | LC_ALL=C wc -c ) ))"
test $# = 0 &&
cmp <(
    printf \
"$(pal 0xFF)""$(rgb    0    0    0)"\
"$(pal 0x23)""$(rgb 1000    0    0)"\
"$(pal 0xB9)""$(rgb    0 1000    0)"\
"$(pal 0x05)""$(rgb 1000 1000    0)"\
"$(pal 0xD2)""$(rgb    0    0 1000)"\
"$(pal 0x18)""$(rgb 1000    0 1000)"\
"$(pal 0xB4)""$(rgb    0 1000 1000)"\
"$(pal 0x00)""$(rgb 1000 1000 1000)"\
''
) <(
    dd if="${KERNEL_MEMORY}" bs=1 skip=$( printf %lu $(( 0x$(nm -arch "$KERNEL_ARCH" "${KERNEL_FILE}"  | grep ' '"${KERNEL_PALETTE_SYMBOL}"'$' | awk '{print $1}') + ${KERNEL_PALETTE_OFFSET})) ) count=$((8*${szpalrgb})) 2>/dev/null
) >/dev/null 2>&1 &&
dd if=<(
   printf \
"$(blend ${KERNEL_PALETTE_0})"\
"$(blend ${KERNEL_PALETTE_1})"\
"$(blend ${KERNEL_PALETTE_2})"\
"$(blend ${KERNEL_PALETTE_3})"\
"$(blend ${KERNEL_PALETTE_4})"\
"$(blend ${KERNEL_PALETTE_5})"\
"$(blend ${KERNEL_PALETTE_6})"\
"$(blend ${KERNEL_PALETTE_7})"\
''
) of="${KERNEL_MEMORY}" bs=1 seek=$( printf %lu $(( 0x$(nm -arch "$KERNEL_ARCH" "${KERNEL_FILE}"  | grep ' '"${KERNEL_PALETTE_SYMBOL}"'$' | awk '{print $1}') + ${KERNEL_PALETTE_OFFSET})) ) count=$((8*${szpalrgb})) conv=notrunc || {
    echo "To install this, type the following command:" >&2
    echo "   sudo /bin/bash $0 --install" >&2
    echo "To uninstall, type the following command:" >&2
    echo "   sudo /bin/bash $0 --uninstall" >&2
    echo "To test, run the following on the console:" >&2
    echo "   /bin/bash $0 --test" >&2
    if ! TERM=xnuppc infocmp 2>/dev/null > /dev/null
    then
        echo "Get terminfo for TERM=xnuppc here:" >&2
        echo "    http://xent.com/~bsittler/xnuppc.ti" >&2
    fi
    exit 1
}
exit 0

#
# $Log: mach_mypal,v $
# Revision 1.55  2017/11/18 02:04:59  bsittler
# *** empty log message ***
#
# Revision 1.54  2012/01/13 03:28:57  bsittler
# Fixed kernel architecture test. Added note at the beginning about
# incompatibilities.
#
# Revision 1.53  2011/12/27 01:29:29  bsittler
# 30-bit color (RGB101010)
#
# Revision 1.52  2011/12/27 00:23:43  bsittler
# *** empty log message ***
#
# Revision 1.51  2011/12/27 00:21:21  bsittler
# *** empty log message ***
#
# Revision 1.50  2011/12/26 22:35:28  bsittler
# kernel arch
#
# Revision 1.49  2010/06/03 17:41:46  bsittler
# allow --test even in foreign OSes and on foreign terminals
#
# Revision 1.48  2009/05/15 16:59:52  bsittler
# better gradient bar
#
# Revision 1.47  2009/05/14 18:37:23  bsittler
# another blank line
#
# Revision 1.46  2009/05/14 18:36:13  bsittler
# use installed version for --test example
#
# Revision 1.45  2009/05/14 18:11:22  bsittler
# add another blank line
#
# Revision 1.44  2009/05/14 18:11:13  bsittler
# fix sample
#
# Revision 1.43  2009/05/14 17:57:16  bsittler
# fixed a stray mach_acs -> mach_mypal
#
# Revision 1.42  2009/05/14 17:55:32  bsittler
# link to terminfo when installing too
#
# Revision 1.41  2009/05/14 17:53:47  bsittler
# link to terminfo if it's not installed
#
# Revision 1.40  2009/05/14 17:45:01  bsittler
# link to terminfo
#
# Revision 1.39  2009/05/14 17:21:39  bsittler
# link to myman
#
# Revision 1.38  2009/05/14 17:14:32  bsittler
# updates from mach_acs
#
# Revision 1.37  2009/04/15 18:20:50  bsittler
# newer mach kernels are /mach_kernel with no /mach
#
# Revision 1.36  2008/03/01 20:20:31  bsittler
# works on ppc now
#
# Revision 1.36  2008/03/01 20:19:08  bsittler
# works on ppc now
#
# Revision 1.35  2008/01/24 04:30:57  bsittler
# more whitespace
#
# Revision 1.34  2008/01/24 04:28:22  bsittler
# linked to TerminalColors
#
# Revision 1.33  2008/01/17 01:07:20  bsittler
# fix typo that overwrote the palette with random stuff
#
# Revision 1.32  2008/01/17 00:59:17  bsittler
# extra uninstall note
#
# Revision 1.31  2008/01/17 00:57:09  bsittler
# /mach rather than /mach_kernel
#
# Revision 1.30  2008/01/17 00:55:28  bsittler
# palette can now be overridden in /etc/defaults/mach_console
#
# Revision 1.29  2008/01/17 00:46:48  bsittler
# more parameterized
#
# Revision 1.28  2008/01/17 00:40:45  bsittler
# stray arguments = do nothing
#
# Revision 1.27  2008/01/17 00:29:00  bsittler
# allow overriding using /etc/defaults/mach_console; add a dumb monochrome fallback CLUT
#
# Revision 1.26  2008/01/17 00:10:53  bsittler
# only read the clut once
#
# Revision 1.25  2008/01/17 00:00:14  bsittler
# take the last best match rather than the first one
#
# Revision 1.24  2008/01/16 23:26:02  bsittler
# oops! clut is stored as packed 24-bit rgb, not 32-bit words
#
# Revision 1.23  2008/01/16 19:49:56  bsittler
# actually use the kernel's color lookup table for the 8-bit part
#
# Revision 1.22  2008/01/16 16:43:15  bsittler
# the new palette is now calculated as the average of the bright and dim
# entries; someday this should be a gamma-corrected average, but bash
# makes that extremely painful to calculate
#
# Revision 1.21  2008/01/16 16:28:47  bsittler
# now the palette is given in rgb + open firmware color palette indexes (i guess?)
#
# Revision 1.20  2008/01/12 20:54:01  bsittler
# ascii
#
# Revision 1.19  2008/01/12 20:35:59  bsittler
# oops
#
# Revision 1.18  2008/01/12 20:34:45  bsittler
# big fat disclaimer
#
# Revision 1.17  2008/01/12 19:31:50  bsittler
# *** empty log message ***
#
# Revision 1.16  2008/01/12 19:30:39  bsittler
# *** empty log message ***
#
# Revision 1.15  2008/01/12 19:11:56  bsittler
# added an install-time note on enabling the console
#
# Revision 1.14  2008/01/12 19:09:01  bsittler
# added a note on Amit Singh's /dev/kmem
#
# Revision 1.13  2008/01/12 18:57:30  bsittler
# explicit path to bash
#
# Revision 1.12  2008/01/12 18:55:57  bsittler
# *** empty log message ***
#
# Revision 1.11  2008/01/12 18:44:09  bsittler
# minor cleanup, and better permissions-tweaking logic
#
# Revision 1.10  2008/01/12 17:11:12  bsittler
# *** empty log message ***
#
#
