a2server/scripts/tools/afptype
T. Joseph Carter 45c00cb920 Merge branch 'ivanx-1.5.x'
Ivan added support to build everything on Debian/Raspbian stretch, added
some alternate URLs, and updated the "just how the hell do you install
Marinetti right now exactly?" dance.

This was more frustrating to merge than it honestly should've been and
I'm kinda responsible for some of that, but it's never gonna get easier
to maintain unless we break from the single-developer monolithic blocks.

Hopefully this will be the last time we do things this way.
2018-04-09 18:51:26 -07:00

499 lines
16 KiB
Bash
Executable File

#! /bin/bash
# vim: set tabstop=4 shiftwidth=4 noexpandtab filetype=sh:
# 2-25-11: tested on 10.6.5 and Ubuntu 10.10. Final.
# to do: allow hex offsets
# These bash functions perform single-byte dec-hex-character conversions
# and file read/write operations, and hopefully work identically across
# different platforms. Each can operate by itself without the presence
# of the others. They have been tested on Mac OS X 10.6 and
# Ubuntu Linux 10.10. Your mileage may vary.
# You provide parameters to the functions as arguments, or alternatively
# standard in (for the functions which accept characters). Examples:
# Write hex byte with value "F0" to offset 23 in file "myFile":
# writecharHex myFile 23 F0
# Write "ABCDE" to the beginning of file "myFile"
# echo "ABCDE" | writechars myFile
# For functions which output something (all but the write operations),
# you can call the functions with command substitution if you want to
# assign the output to a variable rather than display it. Examples:
# Convert decimal value 65 to its hexadecimal equivalent:
# val=$(decToHex 65)
# Get decimal value of the character/byte at offset 215 in "myFile":
# val=$(readcharDec "myFile" 215)
# For functions which convert to or from a character, 0-127 will be
# ASCII/UTF-8, while 128-255 will be system/locale/codepage dependent.
# In this context, a character is effectively the same as a byte.
# The functions return a non-zero exit status for invalid or missing
# arguments. If you don't need these checks, remove the lines
# above the comment "args are valid" (or as otherwise noted).
# The exit statuses are, generally:
# 0 = no error
# 8 = extraneous argument
# 9 = standard input is invalid
# 1x = missing required argument (e.g. 11 for missing argument 1)
# 2x = argument is invalid (e.g. 22 for invalid argument 2)
# any other exit status will originate from the final command in the
# function (e.g. dd, printf)
# For the functions which output chars (readchars, decToChar, and
# hexToChar), be aware that NUL (0) and trailing LF (10/0x0A) chars will
# be stripped when assigned to a variable, and cannot appear in an
# argument. To preserve them, pipe the output elsewhere, such as into
# charToDec, charToHex, writechars, or a command. (readcharDec and
# readcharHex handle these characters correctly.)
# questions/comments to ivan@ivanx.com
decToHex () {
# converts single-byte decimal value to hexadecimal equivalent
# arg: decimal value from 0-255
# out: two-digit hex value from 00-FF
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
[[ $1 ]] || return 11
[[ $2 ]] && return 8
[[ ( $(printf %d "$1" 2> /dev/null) == $1 ) \
&& ( $1 -ge 0 ) && ( $1 -le 255 ) ]] || return 21
# args are valid
printf %02X "$1"
}
hexToDec () {
# converts single-byte hexadecimal value to decimal equivalent
# arg: two-digit hex value from 00-FF
# out: decimal value
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
[[ $1 ]] || return 11
[[ $2 ]] && return 8
[[ ${#1} -eq 2 ]] || return 21
[[ $(printf %02X "0x$1" 2> /dev/null) == \
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 21
# args are valid
printf %d "0x$1"
}
charToDec () {
# converts single character to corresponding decimal value
# stdin OR arg: one character
# [arg overrides stdin; stdin is required for NUL (0) or LF (0x0A)]
# out: decimal value from 0-255
#exit: 8=extraneous arg, 9=invalid stdin,
# 11=missing stdin/arg, 21=invalid arg
[[ ( ! -t 0 ) && $1 ]] && { cat > /dev/null; return 8; }
[[ ( -t 0 ) ]] && { [[ $2 ]] && return 8; [[ $1 ]] || return 11; }
# arg/stdin is potentially valid (additional check below)
charX="$1X"; [[ $1 ]] || charX="$(cat; echo -n 'X';)"
[[ ${#charX} -le 2 ]] || return $(( $([[ $1 ]]; echo $?) ? 9 : 21 ))
# above line verifies that arg/stdin is valid
[[ ${#charX} -ne 2 ]] && { echo -n 0; return 0; }
echo -n "${charX:0:1}" | od -t u1 | \
head -1 | sed 's/[0\ ]*//' | tr -d ' \n'
}
charToHex () {
# converts single character to corresponding hexadecimal value
# stdin OR arg: one character
# [arg overrides stdin; stdin is required for NUL (0) or LF (0x0A)]
# out: decimal value from 0-255
#exit: 8=extraneous arg, 9=invalid stdin,
# 11=missing stdin/arg, 21=invalid arg
[[ ( ! -t 0 ) && $1 ]] && { cat > /dev/null; return 8; }
[[ ( -t 0 ) ]] && { [[ $2 ]] && return 8; [[ $1 ]] || return 11; }
# arg/stdin is potentially valid (additional check below)
charX="$1X"; [[ $1 ]] || charX="$(cat; echo -n 'X';)"
[[ ${#charX} -le 2 ]] || return $(( $([[ $1 ]]; echo $?) ? 9 : 21 ))
# above line verifies that stdin/arg is valid
[[ ${#charX} -ne 2 ]] && { echo -n "00"; return 0; }
printf %02X $(echo -n "${charX:0:1}" | od -t u1 | \
head -1 | sed 's/[0\ ]*//' | tr -d ' \n')
}
decToChar () {
# converts single-byte decimal value to equivalent character
# arg: decimal number from 0-255
# out: one character
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
[[ $1 ]] || return 11
[[ $2 ]] && return 8
[[ ( $(printf %d "$1" 2> /dev/null ) == $1 ) \
&& ( $1 -ge 0 ) && ( $1 -le 255 ) ]] || return 21
# args are valid
echo -n -e "\x$(printf %02X "$1")"
}
hexToChar () {
# converts single-byte hexadecimal value to corresponding character
# arg: two-digit hexadecimal number from 00-FF
# out: one character
#exit: 8=extraneous arg, 11=missing arg, 21=invalid arg
[[ $1 ]] || return 11
[[ $2 ]] && return 8
[[ ${#1} -eq 2 ]] || return 21
[[ $(printf %02X "0x$1" 2> /dev/null) == \
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 21
# args are valid
echo -n -e "\x$1"
}
readchars () {
# read one or more characters from a file
# arg1: filename
# arg2: (optional) offset (# of bytes to skip before reading)
# arg3: (optional) # of chars to read (default is until end of file)
# out: sequence of characters
# exit: 8=extraneous arg, 11=missing arg1,
# 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
[[ $1 ]] || return 11
[[ $4 ]] && return 8
[[ -f $1 ]] || return 21
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
&& ( $2 -ge 0 ) ]] || return 22; }
[[ $3 ]] && { [[ ( $(printf %d "$3" 2> /dev/null) == $3 ) \
&& ( $3 -ge 0 ) ]] || return 23; }
# args are valid
dd if="$1" bs=1 skip=$(($2)) $([[ $3 ]] && echo -n "count=$3") \
2> /dev/null | tr -d ' \0'
}
readcharDec () {
# read one character from file & convert to equivalent decimal value
# arg1: filename
# arg2: (optional) offset (# of bytes to skip before reading)
# out: decimal value from 0-255
# exit: 8=extraneous arg, 11=missing arg1,
# 21=invalid arg1, 22=invalid arg2
[[ $1 ]] || return 11
[[ $3 ]] && return 8
[[ -f $1 ]] || return 21
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
&& ( $2 -ge 0 ) ]] || return 22; }
# args are valid
charX="$(dd if="$1" bs=1 skip=$(($2)) \
count=1 2> /dev/null | tr -d '\0'; echo -n X)"
[[ ${#charX} -gt 1 ]] || { echo -n 0; return 0; }
echo -n "${charX:0:1}" | od -t u1 | \
head -1 | sed 's/[0\ ]*//' | tr -d ' \n'
}
readcharHex () {
# read one character from file & convert to corresponding hex value
# arg1: filename
# arg2: (optional) offset (# of bytes to skip before reading)
# out: two-digit hex value from 00-FF
# exit: 8=extraneous arg, 11=missing arg1,
# 21=invalid arg1, 22=invalid arg2
[[ $1 ]] || return 11
[[ $3 ]] && return 8
[[ -f $1 ]] || return 21
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
&& ( $2 -ge 0 ) ]] || return 22; }
# args are valid
charX="$(dd if="$1" bs=1 skip=$(($2)) \
count=1 2> /dev/null | tr -d '\0'; echo -n X)"
[[ ${#charX} -gt 1 ]] || { echo -n "00"; return 0; }
printf %02X $(echo -n "${charX:0:1}" | od -t u1 | \
head -1 | sed 's/[0\ ]*//' | tr -d ' \n')
}
### 2-15-11 above tested on OS X and Linux
writechars () {
# write one or more characters (bytes) to file
# arg1: filename
# arg2: (optional) offset (# of bytes to skip before writing)
# arg3 OR stdin: sequence of characters
# [stdin required if writing NUL (0) or trailing LF (0x0A) chars]
# out: nothing
# exit: 8=extraneous arg, 11=missing arg1,
# 13=missing stdin/arg3, 22=invalid arg2
[[ $1 ]] || { [[ -t 0 ]] || cat > /dev/null; return 11; }
[[ $2 ]] && { [[ ( $(printf %d "$2" 2> /dev/null) == $2 ) && \
( $2 -ge 0 ) ]] || { [[ -t 0 ]] || cat > /dev/null; return 22; } }
[[ ( ! -t 0 ) && $3 ]] && { cat > /dev/null; return 8; }
[[ ( -t 0 ) ]] && { [[ $4 ]] && return 8; [[ $3 ]] || return 13; }
# args are valid
if [[ -t 0 ]]; then
echo -n "$3" | \
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
else
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
fi
}
writecharDec () {
# write corresponding character of single-byte decimal value into file
# arg1: filename
# arg2: offset (# of bytes to skip before writing)
# arg3: decimal number from 0-255
# exit: 8=extraneous arg, 11=missing arg1, 12=missing arg2,
# 13=missing arg3, 22=invalid arg2, 23=invalid arg3
# out: nothing
[[ $1 ]] || return 11; [[ $2 ]] || return 12; [[ $3 ]] || return 13
[[ $4 ]] && return 8
[[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
&& ( $2 -ge 0 ) ]] || return 22
[[ ( $(printf %d "$3" 2> /dev/null) == $3 ) \
&& ( $3 -ge 0 ) && ( $3 -lt 255 ) ]] || return 23
# args are valid
echo -n -e "\x$(printf %02X "$3")" | \
dd of="$1" bs=1 seek=$(($2)) conv=notrunc 2> /dev/null
}
writecharHex () {
# write corresponding character of single-byte hex value into file
# arg1: filename
# arg2: offset (# of bytes to skip before writing)
# arg3: two-digit hexadecimal number from 00-FF
# out: nothing
# exit: 8=extraneous arg, 11=missing arg1, 12=missing arg2,
# 13=missing arg3, 22=invalid arg2, 23=invalid arg3
[[ $1 ]] || return 11; [[ $2 ]] || return 12; [[ $3 ]] || return 13
[[ $4 ]] && return 8
[[ ( $(printf %d "$2" 2> /dev/null) == $2 ) \
&& ( $2 -ge 0 ) ]] || return 22
[[ $(printf %02X "0x$3" 2> /dev/null) == \
$(echo -n "$3" | tr [a-z] [A-Z]) ]] || return 23
# args are valid
echo -n -e "\x$3" | \
dd of="$1" bs=1 seek=$2 conv=notrunc 2> /dev/null
}
# --- afptype is below this line
isHexByte () {
[[ $(printf %02X "0x$1" 2> /dev/null) == \
$(echo -n "$1" | tr [a-z] [A-Z]) ]] || return 1
}
# support 00 and 0A as filetype chars?
debug=1
ptypes="04:TXT 06:BIN B3:S16 E0:SHK F9:P16 FA:INT FC:BAS FF:SYS"
quit () {
if [[ $2 && $debug ]]; then
echo "$1" "$2"
else
echo -e "Error: $1"
fi
exit_usage
}
exit_usage () {
echo "Usage:"
echo
echo "show types: afptype filename"
echo "set Mac OS: afptype [-t 'type'] [-c 'creator'] [-q] filename"
echo "set ProDOS: afptype [-p type] [-a auxtype] [-q] filename"
echo "Mac OS type or creator must be four characters; use \x plus"
echo " two hex digits for untypeables (note: use '\xZZ' for 00)."
echo "ProDOS type should be two hex digits, and auxtype should be four;"
echo " type can alternatively be BAS, BIN, INT, P16, S16, SHK, SYS, TXT."
echo "-q skips recheck of file (show types) after setting"
echo
exit 1
}
lookupPdosType () {
# looks up ProDOS hex type from code in list 'ptypes'
# arg: three-character code
# out: two-digit hex value
#exit: 0=type found, 1=error, 2=type not found
ptypes="04:TXT 06:BIN B3:S16 E0:SHK F9:P16 FA:INT FC:BAS FF:SYS"
[[ $1 ]] || quit "lookupPdosType:" "no argument supplied ($1)"
[[ ${#1} -eq 3 ]] || return 1
arg=$(echo -n "$1" | tr [a-z] [A-Z])
for ptype in $ptypes; do
if [[ ${ptype:3:3} == $arg ]]; then
echo -n "${ptype:0:2}"
return 0
fi
done
echo "$1"
return 1
}
verifyTC () {
[[ $1 ]] || return 1
tcX="$(echo -e -n "$1"X)"
[[ ${#tcX} -eq 5 ]] || return 1
echo "$tcX"
}
while [[ $1 && ( "${1:0:1}" == '-' ) ]]; do
if [[ $1 == "-p" ]]; then
[[ $p ]] && exit_usage
shift
p="$1"
shift
continue
elif [[ $1 == "-a" ]]; then
[[ $a ]] && exit_usage
shift
a="$1"
shift
continue
elif [[ $1 == "-t" ]]; then
[[ $t ]] && exit_usage
shift
t="$1"
shift
continue
elif [[ $1 == "-c" ]]; then
[[ $c ]] && exit_usage
shift
c="$1"
shift
continue
elif [[ $1 == "-q" ]]; then
[[ $q ]] && exit_usage
q=1
shift
continue
else
exit_usage
break
fi
done
if [[ ( ( $p || $a ) && ( $t || $c ) ) || ( -z $1 ) ]]; then
exit_usage
fi
#filename="$1"
#shift
for filename in $@; do
[[ ${#@} -gt 1 ]] && linestart="($filename) "
if [[ ! -f $filename ]]; then
echo "${linestart}Not found."
continue
fi
adname="$(dirname "$filename")/.AppleDouble/$(basename "$filename")"
[[ -f $adname ]] && filename=$adname
ADversion=$(readcharDec "$filename" 5)
if [[ ( ( $ADversion -ne 1 ) && ( $ADversion -ne 2 ) ) \
|| ( "$(readchars "$filename" 1 3)" != "$(echo -e -n "\x05\x16\x07")" ) \
|| ( $(readcharDec "$filename" 0) -ne 0 ) \
|| ( $(readcharDec "$filename" 4) -ne 0 ) \
|| ( $(readcharDec "$filename" 6) -ne 0 ) \
|| ( $(readcharDec "$filename" 7) -ne 0 ) ]]; then
echo "${linestart}Not an AppleDouble file."
continue
fi
entrycount=`readcharDec "$filename" 25`
entry=1
offset=29
while [[ $(readcharDec "$filename" $offset) -ne 9 ]]; do
(( entry = entry + 1 ))
if (( entry > entrycount )); then
echo "${linestart}Finder Info entry not found in AppleDouble file."
break
fi
(( offset = (entry * 12 + 29) - 12 ))
done
(( entry > entrycount )) && continue
(( offset = offset + 3 ))
(( tposHi = $(readcharDec "$filename" $offset) * 256 ))
(( offset = offset + 1 ))
(( tpos = $(readcharDec "$filename" $offset) + tposHi ))
(( cpos = tpos + 4 ))
(( ppos = tpos + 1 ))
(( apos = tpos + 2 ))
if [[ $p || $a || $t || $c ]]; then # set
if [[ $p || $a ]]; then
if [[ $p ]]; then
[[ ${#p} -eq 3 ]] && { p=$(lookupPdosType $p) || quit "Invalid ProDOS type name ($p)."; }
isHexByte "$p" || quit "Invalid ProDOS file type ($p)."
writecharHex "$filename" $ppos "$p"
fi
if [[ $a ]]; then
isHexByte "${a:0:2}" && isHexByte "${a:2:2}" || quit "Invalid ProDOS aux type ($a)."
writecharHex "$filename" $apos "${a:0:2}"
(( apos=apos+1 ))
writecharHex "$filename" $apos "${a:2:2}"
fi
writechars "$filename" $tpos "p"
writechars "$filename" $cpos "pdos"
elif [[ $t || $c ]]; then
if [[ $t ]]; then
type=$(verifyTC "$t") || quit "$(echo -n "Invalid Mac file type ($t)."; [[ $t == *x* ]] && echo -n " Try quotes."; echo)"
writechars "$filename" $tpos "${type:0:4}"
fi
if [[ $c ]]; then
creator=$(verifyTC "$c") || quit "$(echo -n "Invalid Mac file creator ($c)."; [[ $c == *x* ]] && echo -n " Try quotes."; echo)"
writechars "$filename" $cpos "${creator:0:4}"
fi
fi
[[ $q ]] || { echo -n "${linestart}File changed: "; "$0" "$filename"; }
else # show
[[ $q ]] && quit "Can only use -q when changing type."
type="$(readchars "$filename" $tpos 4)"
creator="$(readchars "$filename" $cpos 4)"
echo -n "$linestart"
if [[ $creator != "pdos" || ( ( $type != "TEXT" ) \
&& ( $type != "PSYS" ) && ( ${type:0:1} != "p" ) ) ]]; then
if [[ $creator || $type ]]; then
echo "Mac file. Type:$type Creator:$creator"
else
echo "This file has no Mac or ProDOS file type information."
fi
continue
fi
if [[ $type == "TEXT" ]]; then
pdosType="\$04 [TXT]"
pdosAuxType='$0000'
elif [[ $type == "PSYS" ]]; then
pdosType="\$FF [SYS]"
pdosAuxType='$0000'
else
(( tpos=tpos+1 ))
pdosType=$(readcharHex "$filename" $tpos)
for ptype in $ptypes; do
if [[ "${ptype:0:2}" == "$pdosType" ]]; then
pdosType="$pdosType [${ptype:3:3}]"
break
fi
done
(( tpos=tpos+1 ))
auxTypeHi=$(readcharHex "$filename" $tpos)
(( tpos=tpos+1 ))
auxTypeLo=$(readcharHex "$filename" $tpos)
pdosAuxType=$auxTypeHi$auxTypeLo
fi
echo "ProDOS file. Type:\$$pdosType AuxType:\$$pdosAuxType"
fi
done
# 7-19-11
# quick ProDOS testing/fixing on Linux, needs more
# Mac Type testing not done yet, nor testing on a Mac
# test on lunix
# test inside and outside of AD directory, and from other dirs (both cases)
# finish conversion writebyte/readchar library
# consider return 2 for missing parameters for subroutines