mirror of
https://github.com/RasppleII/a2cloud.git
synced 2024-12-02 05:49:29 +00:00
762 lines
26 KiB
Bash
762 lines
26 KiB
Bash
#!/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
|