#!/bin/bash # need to test: # does both -e and -ad work as expected? what if you do both? #--ID-bashbyter routines 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" } hexToBin () { # converts single-byte hexadecimal value to binary string # arg: two-digit hex value from 00-FF # out: binary string 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 for n in 0 1; do if [[ ${1:n:1} == "0" ]]; then b="0000" elif [[ ${1:n:1} == "1" ]]; then b="0001" elif [[ ${1:n:1} == "2" ]]; then b="0010" elif [[ ${1:n:1} == "3" ]]; then b="0011" elif [[ ${1:n:1} == "4" ]]; then b="0100" elif [[ ${1:n:1} == "5" ]]; then b="0101" elif [[ ${1:n:1} == "6" ]]; then b="0110" elif [[ ${1:n:1} == "7" ]]; then b="0111" elif [[ ${1:n:1} == "8" ]]; then b="1000" elif [[ ${1:n:1} == "9" ]]; then b="1001" elif [[ ${1:n:1} == "A" ]]; then b="1010" elif [[ ${1:n:1} == "B" ]]; then b="1011" elif [[ ${1:n:1} == "C" ]]; then b="1100" elif [[ ${1:n:1} == "D" ]]; then b="1101" elif [[ ${1:n:1} == "E" ]]; then b="1110" elif [[ ${1:n:1} == "F" ]]; then b="1111" fi echo -n $b done } binToDec () { # converts single-byte binary string (8 bits) value to decimal # warning: no error checking # arg: binary string up to 8 bits # out: decimal value dec=0 bits=$1 while (( ${#bits} < 8 )); do bits="0$bits" done for n in {0..7}; do (( dec+=( ${bits:$n:1} * ( 2**(7-$n) ) ) )) done echo -n $dec } binToHex () { # converts single-byte binary string (8 bits) value to hex # warning: no error checking # arg: binary string up to 8 bits # out: hex value echo $(decToHex $(binToDec $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 } 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; 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; 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 } writecharsHex () { # write corresponding characters of hex values into file # arg1: filename # arg2: offset (# of bytes to skip before writing) # arg3: string of two-digit hexadecimal numbers from 00-FF, period delimited (not checked!) # 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 p=0 offset=$2 len=${#3} while (( p < len )); do outByte=${3:$p:2} [[ $(printf %02X "0x$outByte" 2> /dev/null) == \ $(echo -n "$outByte" | tr [a-z] [A-Z]) ]] || return 23 # args are valid echo -n -e "\x$outByte" | \ dd of="$1" bs=1 seek=$offset conv=notrunc 2> /dev/null (( p += 3 )) (( offset++ )) done } # --- pdosDateToUnixDate () { # input: ProDOS date/time bit sequence string in format: # yyyyyyymmmmddddd000hhhhh00mmmmmm # output: seconds since Unix epoch (1-Jan-1970), or current date/time if no ProDOS date year=$(( $(binToDec ${1:0:7}) + 1900 )) (( $year < 1940 )) && (( year+=100 )) month=$(binToDec ${1:7:4}) day=$(binToDec ${1:11:5}) hour=$(binToDec ${1:19:5}) minute=$(binToDec ${1:26:6}) date -d "$year-$month-$day $hour:$minute:00" "+%s" 2> /dev/null } unixDateToADDate () { # input: seconds since Unix epoch (1-Jan-1970 00:00:00 GMT) # output: seconds since Netatalk epoch (1-Jan-2000 00:00:00 GMT), # in four period-delimited hex bytes (big endian) adDate=$(( $1 - 946684800 )); if (( $adDate < 0 )); then (( adDate+=4294967296 )) # to get negative hex number fi adDateHex=$(printf %08X $adDate) echo "${adDateHex:0:2}.${adDateHex:2:2}.${adDateHex:4:2}.${adDateHex:6:2}" } # cppo support routines: # arg1: directory block # arg2: file index (if applicable) # arg3: directory chunk # (if applicable) # returns byte position in disk image file getStartPos () { echo $(( ($1 * 512) + (39 * ( ($2 + ($2>11) ) % 13) ) + ( ($2>11) ? 4 : 43) )) } getStorageType () { start=$(getStartPos $1 $2) firstByte=$(readcharDec "$image" $start) echo $(($firstByte/16)) } getFileName () { start=$(getStartPos $1 $2) firstByte=$(readcharDec "$image" $start) entryType=$(($firstByte/16)) nameLength=$(($firstByte-$entryType*16)) echo $(readchars "$image" $(($start+1)) $nameLength) } getFileType () { start=$(getStartPos $1 $2) echo $(readcharHex "$image" $(($start+16)) ) } getKeyPointer () { start=$(getStartPos $1 $2) echo $(( $(readcharDec "$image" $(($start+17)) ) + \ $(readcharDec "$image" $(($start+18)) ) * 256 )) } getFileLength () { start=$(getStartPos $1 $2) echo $(( $(readcharDec "$image" $(($start+21)) ) + \ $(readcharDec "$image" $(($start + 22)) ) * 256 + \ $(readcharDec "$image" $(($start + 23)) ) * 65536 )) } getAuxType () { start=$(getStartPos $1 $2) echo $(readcharHex "$image" $(($start+32)) ).$(readcharHex "$image" $(($start+31)) ) } getCreationDate () { #outputs prodos creation date/time as Unix time (seconds since Jan 1 1970 GMT) #or "NONE" if there is none start=$(getStartPos $1 $2) pdosDate=\ $( hexToBin $(readcharHex "$image" $(($start+25)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+24)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+27)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+26)) ) ) pdosDateToUnixDate $pdosDate || echo "NONE" } getModifiedDate () { #outputs prodos modified date/time as Unix time (seconds since Jan 1 1970 GMT) start=$(getStartPos $1 $2) pdosDate=\ $( hexToBin $(readcharHex "$image" $(($start+34)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+33)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+36)) ) )\ $( hexToBin $(readcharHex "$image" $(($start+35)) ) ) pdosDateToUnixDate $pdosDate || echo "NONE" } #isLocked () { # #returns 1 (meaning locked) if bit 7, 6, or 1 are clear; otherwise returns 0 # start=$(getStartPos $1 $2) # access=$( $hexToBin $(readcharHex "$image" $(($start+30)) ) ) # if [[ ${access:0:1} != "1" || ${access:1:1} != "1" || ${access:7:1} != "1" ]]; then # echo 1 # else # echo 0 # fi #} getVolumeName () { echo $(getWorkingDirName 2) } getWorkingDirName () { start=$(( $1 * 512 )) firstByte=$(readcharDec "$image" $(($start+4)) ) entryType=$(($firstByte/16)) nameLength=$(($firstByte-$entryType*16)) echo $(readchars "$image" $(($start+5)) $nameLength) } getDirEntryCount () { start=$(( $1 * 512 )) echo $(( $(readcharDec "$image" $(($start+37)) ) + \ $(readcharDec "$image" $(($start+38)) ) * 256 )) } getDirNextChunkPointer () { start=$(( $1 * 512 )) echo $(( $(readcharDec "$image" $(($start+2)) ) + \ $(readcharDec "$image" $(($start+3)) ) * 256 )) } # -- script begins in earnest here copyFile () { activeFileBytesCopied=0 storageType=$(getStorageType $1 $2) keyPointer=$(getKeyPointer $1 $2) fileLen=$(getFileLength $1 $2) if (( $storageType == 1 )); then #seedling copyBlock $keyPointer $fileLen elif (( $storageType == 2 )); then #sapling processIndexBlock $keyPointer elif (( $storageType == 3 )); then #tree processMasterIndexBlock $keyPointer elif (( $storageType == 5)); then #forked fileLen processForkedFile $keyPointer fi } copyBlock () { #arg1: block to copy #arg2: bytes to write (should be 512, unless final block with less than 512 bytes) #echo $1 $2 $activeFileBytesCopied (( $1 == 0 )) && blockSource=/dev/zero || blockSource="$image" if (( $resourceFork > 0 )); then [[ $AD ]] && dd if="$blockSource" of="$ADdir/$targetName" bs=1 count=$2 skip=$(($1*512)) seek=$(($activeFileBytesCopied + 741)) 2> /dev/null [[ $EX ]] && dd if="$blockSource" of="$targetDir/${eTargetName}r" bs=1 count=$2 skip=$(($1*512)) seek=$activeFileBytesCopied 2> /dev/null else dd if="$blockSource" of="$targetDir/$targetName" bs=1 count=$2 skip=$(($1*512)) seek="$activeFileBytesCopied" 2> /dev/null fi activeFileBytesCopied=$(( $activeFileBytesCopied + $2 )) } processDir () { # arg1: dirBlock # arg2/3/4/5: for non-key chunks: entryCount, entry#, # workingDirName, processedEntryCount local entryCount local e local pe local workingDirName if [[ $2 ]]; then entryCount=$2 e=$3 workingDirName=$4 pe=$5 else e=0 pe=0 entryCount=$(getDirEntryCount $1) workingDirName=$(getWorkingDirName $1) DIRPATH="$DIRPATH/$workingDirName" if [[ $PDOSPATH_INDEX ]]; then if (( $PDOSPATH_INDEX == 1 )); then if [[ "/$PDOSPATH_SEGMENT" != "$DIRPATH" ]]; then echo "ProDOS volume name does not match disk image." exit 2 else (( PDOSPATH_INDEX++ )) PDOSPATH_SEGMENT=${PDOSPATH[PDOSPATH_INDEX]} fi fi else echo $DIRPATH fi fi while (( $pe < $entryCount )); do if (( $(getStorageType $1 $e) > 0 )); then processEntry $1 $e (( pe++ )) fi (( e++ )) (( ($e + ( $e>11 ) ) % 13 )) || { processDir $(getDirNextChunkPointer $1) $entryCount $e $workingDirName $pe; break; } done } processEntry () { #echo $(getFileName $1 $2) $(getStorageType $1 $2) $(getFileType $1 $2) $(getKeyPointer $1 $2) $(getFileLength $1 $2) $(getAuxType $1 $2) $(getCreationDate $1 $2) $(getModifiedDate $1 $2) activeFileName=$(getFileName $1 $2) activeFileSize=$(getFileLength $1 $2) [[ $PDOSPATH_INDEX ]] || echo " $activeFileName" if [[ ( ! $PDOSPATH_INDEX ) || ( $activeFileName == $PDOSPATH_SEGMENT ) ]]; then if (( $(getStorageType $1 $2) == 13 )); then [[ $PDOSPATH_INDEX ]] || targetDir="$targetDir/$activeFileName" ADdir="$targetDir/.AppleDouble" [[ $DIR || -d $targetDir ]] || mkdir -p $targetDir [[ $DIR || ! $AD || -d $ADdir ]] || mkdir -p $ADdir if [[ $PDOSPATH_SEGMENT ]]; then (( PDOSPATH_INDEX++ )) PDOSPATH_SEGMENT=${PDOSPATH[PDOSPATH_INDEX]} fi processDir $(getKeyPointer $1 $2) DIRPATH=${DIRPATH%/*} [[ $PDOSPATH_INDEX ]] || targetDir="$targetDir/.." ADdir="$targetDir/.AppleDouble" else [[ $DIR ]] && return [[ $targetName ]] || targetName=$activeFileName [[ $EX ]] && eTargetName="$targetName#$(getFileType $1 $2 | tr [:upper:] [:lower:])$(getAuxType $1 $2 | sed 's/\.//' | tr [:upper:] [:lower:])" touch "$targetDir/$targetName" makeADfile copyFile $1 $2 creationDate=$(getCreationDate $1 $2); modifiedDate=$(getModifiedDate $1 $2); if [[ $creationDate == "NONE" && $modifiedDate != "NONE" ]]; then creationDate=$modifiedDate elif [[ $creationDate != "NONE" && $modifiedDate == "NONE" ]]; then modifiedDate=$creationDate elif [[ $creationDate == "NONE" && $modifiedDate == "NONE" ]]; then creationDate=$(date "+%s") modifiedDate=$creationDate fi if [[ $AD ]]; then # AppleDouble # set dates ADfilePath="$ADdir/$targetName" writecharsHex "$ADfilePath" 637 $(unixDateToADDate $creationDate).$(unixDateToADDate $modifiedDate) writecharHex "$ADfilePath" 645 80 writecharHex "$ADfilePath" 649 80 #set type/creator writechars "$ADfilePath" 653 "p" writecharsHex "$ADfilePath" 654 "$(getFileType $1 $2).$(getAuxType $1 $2)" writechars "$ADfilePath" 657 "pdos" fi touch -d @$modifiedDate "$targetDir/$targetName" if [[ $EX ]]; then # extended name mv "$targetDir/$targetName" "$targetDir/$eTargetName" [[ -f $targetDir/${eTargetName}r ]] && touch -d @$modifiedDate "$targetDir/${eTargetName}r" fi [[ $PDOSPATH_SEGMENT ]] && syncExit targetName= fi #else #echo "$activeFileName doesn't match $PDOSPATH_SEGMENT" fi } processForkedFile () { # finder info except type/creator fInfoA_entryType=$(readcharDec "$image" 9) fInfoB_entryType=$(readcharDec "$image" 27) if (( $fInfoA_entryType==1 )); then readchars "$image" 18 8 | writechars "$image" 661 elif (( $fInfoA_entryType==2 )); then readchars "$image" 10 16 | writechars "$image" 669 fi if (( $fInfoB_entryType==1 )); then readchars "$image" 36 8 | writechars "$image" 661 elif (( $fInfoB_entryType==2 )); then readchars "$image" 28 16 | writechars "$image" 669 fi for f in 0 256; do resourceFork=$f activeFileBytesCopied=0 forkStart=$(( ($1 * 512) )) # start of Forked File key block # echo --$forkStart forkStorageType=$(readcharDec "$image" $(($forkStart+$f+0)) ) forkKeyPointer=$(( $(readcharDec "$image" $(($forkStart+$f+1)) ) + \ $(readcharDec "$image" $(($forkStart+$f+2)) ) * 256 )) forkFileLen=$(( $(readcharDec "$image" $(($forkStart+$f+5)) ) + \ $(readcharDec "$image" $(($forkStart+$f+6)) ) * 256 + \ $(readcharDec "$image" $(($forkStart+$f+7)) ) * 65536 )) activeFileSize=$forkFileLen if (( $resourceFork > 0 )); then rsrcForkLenHex=$(readcharHex "$image" $(($forkStart+$f+7)) ).\ $(readcharHex "$image" $(($forkStart+$f+6)) ).\ $(readcharHex "$image" $(($forkStart+$f+5)) ) # echo ">>>$rsrcForkLenHex" echo " [resource fork]" [[ $AD ]] && writecharsHex "$ADdir/$targetName" 35 "$rsrcForkLenHex" else echo " [data fork]" fi if (( $forkStorageType == 1 )); then #seedling copyBlock $forkKeyPointer $forkFileLen elif (( $forkStorageType == 2 )); then #sapling processIndexBlock $forkKeyPointer elif (( $forkStorageType == 3 )); then #tree processMasterIndexBlock $forkKeyPointer fi done # echo resourceFork=0 } processMasterIndexBlock() { processIndexBlock $1 1 } processIndexBlock () { #arg1: indexBlock #arg2: if set, it's a Master Index Block local pos=0 local bytesRemaining while (( $activeFileBytesCopied < $activeFileSize )); do targetBlock=$(( $(readcharDec $image $(($1*512+$pos)) ) + $(readcharDec $image $(($1*512+($pos+256) )) )*256 )) if [[ $2 ]]; then processIndexBlock $targetBlock else bytesRemaining=$(($activeFileSize - $activeFileBytesCopied)) bs=$(( $bytesRemaining<512 ? $bytesRemaining : 512 )) copyBlock $targetBlock $bs fi (( pos++ )) (( $pos > 255 )) && break # go to next entry in Master Index Block (tree) done } makeADfile () { ADfilePath="$ADdir/$targetName" [[ ! $AD ]] && return dd if=/dev/zero of="$ADfilePath" bs=741 count=1 2> /dev/null writecharsHex "$ADfilePath" $(hexToDec 00) "00.05.16.07.00.02.00.00" # ADv2 header writecharsHex "$ADfilePath" $(hexToDec 18) "00.0D" # number of entries writecharsHex "$ADfilePath" $(hexToDec 1A) "00.00.00.02.00.00.02.E5.00.00.00.00" # Resource Fork writecharsHex "$ADfilePath" $(hexToDec 26) "00.00.00.03.00.00.00.B6.00.00.00.00" # Real Name writecharsHex "$ADfilePath" $(hexToDec 32) "00.00.00.04.00.00.01.B5.00.00.00.00" # Comment writecharsHex "$ADfilePath" $(hexToDec 3E) "00.00.00.08.00.00.02.7D.00.00.00.10" # Dates Info writecharsHex "$ADfilePath" $(hexToDec 4A) "00.00.00.09.00.00.02.8D.00.00.00.20" # Finder Info writecharsHex "$ADfilePath" $(hexToDec 56) "00.00.00.0B.00.00.02.C1.00.00.00.08" # ProDOS file info writecharsHex "$ADfilePath" $(hexToDec 62) "00.00.00.0D.00.00.02.B5.00.00.00.00" # AFP short name writecharsHex "$ADfilePath" $(hexToDec 6E) "00.00.00.0E.00.00.02.B1.00.00.00.04" # AFP File Info writecharsHex "$ADfilePath" $(hexToDec 7A) "00.00.00.0F.00.00.02.AD.00.00.00.04" # AFP Directory ID # dbd (second time) will create DEV, INO, SYN, SV~ } syncExit () { if [[ -d /usr/local/etc/netatalk && $AD ]]; then echo "File(s) have been copied to the target directory. If the directory" 1>&2 echo "is shared by Netatalk, please use 'afpsync' now." 1>&2 fi exit 0 } usage () { echo "usage:" echo "copy all files: cppo [-ad|-e] imagefile targetDirectory" echo "copy one file : cppo [-ad|-e] imagefile /FULL/PRODOS/FILE/PATH targetPath" echo "catalog image : cppo -cat imagefile" echo echo "cppo copies either one file or all files from a ProDOS raw disk image" echo "to a folder shared by Netatalk. -cat displays all files on the image." echo "No verification or validation of the disk image is performed." echo echo "-ad enables creating AppleDouble header files and copying resource forks." echo "-e appends the ProDOS type and auxtype to filenames, and copies resource" echo " forks, so they can be preserved when added to ShrinkIt archives by" echo " nulib2 (using its -e option)." echo echo "Wildcard matching (*) is not supported." exit 1 } # --- start if [[ $1 == "-ad" ]]; then AD=1 shift fi if [[ $1 == "-e" ]]; then [[ $AD ]] && usage EX=1 shift fi if [[ $1 == "-cat" ]]; then DIR=1 shift fi [[ ( $DIR && $1 ) || ( $1 && $2 ) ]] || usage [[ $3 && ( ${2:0:1} != "/" ) && ( ${2:0:1} != ":" ) ]] && usage image="$1" [[ -f "$image" ]] || { echo "Source image not found."; exit 2; } if [[ $3 ]]; then pdospath=$(echo $2 | tr [:lower:] [:upper:]) targetPath=$3 if [[ -d $targetPath ]]; then targetDir=$targetPath else targetDir="${targetPath%/*}" targetName="${targetPath##*/}" fi [[ -d $targetDir ]] || { echo "Target directory not found."; exit 2; } else if [[ ! $DIR ]]; then [[ -d "$2" ]] || { echo "Target directory not found."; exit 2; } fi fi activeDirBlock=0 activeFileName="" activeFileSize=0 activeFileBytesCopied=0 resourceFork=0 if [[ $3 ]]; then IFS='/:' PDOSPATH=($pdospath) unset IFS [[ ! ${PDOSPATH[0]} ]] && (( PDOSPATH_INDEX++ )) PDOSPATH_SEGMENT=${PDOSPATH[PDOSPATH_INDEX]} ADdir="$targetDir/.AppleDouble" [[ ! $AD || -d $ADdir ]] || mkdir $ADdir processDir 2 echo "ProDOS file not found within image file." exit 2 else if [[ ! $DIR ]]; then targetDir="$2/$(getVolumeName)" ADdir="$targetDir/.AppleDouble" [[ -d $targetDir ]] || mkdir -p $targetDir [[ ! $AD || -d $ADdir ]] || mkdir -p $ADdir fi processDir 2 [[ $DIR ]] || syncExit fi