a2cloud/setup/cppo.txt
T. Joseph Carter c2deff81ae Re-indent the scripts a bit to match expectations
Notably this may cause issues diffing adtpro.sh.txt against the
upstreadm adtpro.sh script.  Ours had mixed coding styles, and I figured
having to diff with whitespace ignored was a better solution than
dealing with mixed styles either in the file or in the tree for the
majority of people who aren't likely using the same editor I am.
2015-10-30 05:52:22 -07:00

884 lines
30 KiB
Python

#! /usr/bin/env python
# vim: set tabstop=4 shiftwidth=4 expandtab filetype=python:
"""cppo: Copy or catalog one or all files from a ProDOS raw disk image.
copy all files:
cppo [-ad|-e] imagefile target_directory
copy one file:
cppo [-ad|-e] imagefile /FULL/PRODOS/FILE/PATH target_path
catalog image:
cppo -cat imagefile
-ad : Create AppleDouble header files and preserve resource forks.
-e : Append ProDOS type and auxtype to filenames, and copy resource
forks, for adding to ShrinkIt archives with Nulib2
using its -e option.
Wildcard matching/globbing (*) is not supported.
No verification or validation of the disk image is performed.
(Compatible with Python 2.6 and later, including 3.x.)
"""
# cppo by Ivan X, ivan@ivanx.com, ivanx.com/appleii
# If anyone's looking at this, and feels it's not sufficiently Pythonic,
# I know that. It's pretty much a line-for-line conversion of the original
# Bash script. I did start a beautiful from-the-ground-up object-oriented
# version, then realized it would be faster to translate it ugly and quick.
# imports for python 3 code compatibility
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import
from __future__ import division
import sys
import os
import time
import datetime
# Intentially fails on pre-2.6 so user can see what's wrong
b'ERROR: cppo requires Python 2.6 or later, including 3.x.'
class Globals(object):
pass
g = Globals()
g.imageData = b''
g.outFileData = bytearray(b'')
g.adFileData = bytearray(b'')
g.exFileData = bytearray(b'')
g.activeDirBlock = None
g.activeFileName = None
g.activeFileSize = None
g.activeFileBytesCopied = 0
g.resourceFork = 0
g.PDOSPATH = []
g.PDOSPATH_INDEX = 0
g.PDOSPATH_SEGMENT = None
g.DIRPATH = ""
g.targetName = None
g.targetDir = ""
g.ADdir = None
g.imageFile = None
g.AD = 0
g.EX = 0
g.DIR = 0
g.silent = 0
# functions
def pdosDateToUnixDate(arg1):
# input: ProDOS date/time bit sequence string in format:
# "yyyyyyymmmmddddd000hhhhh00mmmmmm" (ustr)
# output: seconds since Unix epoch (1-Jan-1970),
# or current date/time if no ProDOS date
year = (binToDec(slyce(arg1,0,7)) + 1900)
if (year < 1940): year += 100
month = binToDec(slyce(arg1,7,4))
day = binToDec(slyce(arg1,11,5))
hour = binToDec(slyce(arg1,19,5))
minute = binToDec(slyce(arg1,26,6))
# print(year, month, day, hour, minute)
td = (datetime.datetime(year, month, day, hour, minute) -
datetime.datetime(1970,1,1))
unixDate_naive = (td.days*24*60*60 + td.seconds)
td2 = (datetime.datetime.fromtimestamp(unixDate_naive) -
datetime.datetime.utcfromtimestamp(unixDate_naive))
utcoffset = (td2.days*24*60*60 + td2.seconds)
# print(unixDate_naive - utcoffset)
return (unixDate_naive - utcoffset) # local time zone with DST
def unixDateToADDate(arg1):
# 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 hex-ustr (big endian)
adDate = (arg1 - 946684800)
if (adDate < 0 ):
adDate += 4294967296 # to get negative hex number
adDateHex = to_hex(adDate).zfill(8).upper()
# print(arg1, adDate, adDateHex)
return adDateHex
# cppo support routines:
# arg1: directory block
# arg2: file index (if applicable)
# arg3: directory chunk # (if applicable)
#most of these not tested yet in Python
# returns byte position in disk image file
def getStartPos(arg1, arg2):
return ( (arg1 * 512) +
(39 * ((arg2 + (arg2 > 11)) % 13)) +
(4 if (arg2 > 11) else 43) )
def getStorageType(arg1, arg2):
start = getStartPos(arg1, arg2)
firstByte = readcharDec(g.imageData, start)
return (firstByte//16)
def getFileName(arg1, arg2):
start = getStartPos(arg1, arg2)
firstByte = readcharDec(g.imageData, start)
entryType = (firstByte//16)
nameLength = (firstByte - entryType*16)
return readchars(g.imageData, start+1, nameLength)
def getFileType(arg1, arg2):
start = getStartPos(arg1, arg2)
return readcharHex(g.imageData, start+16)
def getKeyPointer(arg1, arg2):
start = getStartPos(arg1, arg2)
return (readcharDec(g.imageData, start+17) +
readcharDec(g.imageData, start+18)*256)
def getFileLength(arg1, arg2):
start = getStartPos(arg1, arg2)
return (readcharDec(g.imageData, start+21) +
readcharDec(g.imageData, start+22)*256 +
readcharDec(g.imageData, start+23)*65536)
def getAuxType(arg1, arg2):
start = getStartPos(arg1, arg2)
return (readcharHex(g.imageData, start+32) +
readcharHex(g.imageData, start+31))
def getCreationDate(arg1, arg2):
#outputs prodos creation date/time as Unix time
# (seconds since Jan 1 1970 GMT)
#or None if there is none
start = getStartPos(arg1, arg2)
pdosDate = (hexToBin(readcharHex(g.imageData, start+25)) +
hexToBin(readcharHex(g.imageData, start+24)) +
hexToBin(readcharHex(g.imageData, start+27)) +
hexToBin(readcharHex(g.imageData, start+26)))
try:
rVal = pdosDateToUnixDate(pdosDate)
except Exception:
rVal = None
return rVal
def getModifiedDate(arg1, arg2):
#outputs prodos modified date/time as Unix time
# (seconds since Jan 1 1970 GMT)
#or None if there is none
start = getStartPos(arg1, arg2)
pdosDate = (hexToBin(readcharHex(g.imageData, start+34)) +
hexToBin(readcharHex(g.imageData, start+33)) +
hexToBin(readcharHex(g.imageData, start+36)) +
hexToBin(readcharHex(g.imageData, start+35)))
try:
rVal = pdosDateToUnixDate(pdosDate)
except Exception:
rVal = None
return rVal
def getVolumeName():
return getWorkingDirName(2)
def getWorkingDirName(arg1):
start = ( arg1 * 512 )
firstByte = readcharDec(g.imageData, start+4)
entryType = (firstByte//16)
nameLength = (firstByte - entryType*16)
return readchars(g.imageData, start+5, nameLength)
def getDirEntryCount(arg1):
start = ( arg1 * 512 )
return (readcharDec(g.imageData, start+37) +
readcharDec(g.imageData, start+38)*256)
def getDirNextChunkPointer(arg1):
start = ( arg1 * 512 )
return (readcharDec(g.imageData, start+2) +
readcharDec(g.imageData, start+3)*256)
# -- script begins in earnest here
def copyFile(arg1, arg2):
g.outFileData = bytearray(b'')
g.exFileData = bytearray(b'')
g.activeFileBytesCopied = 0
storageType = getStorageType(arg1, arg2)
keyPointer = getKeyPointer(arg1, arg2)
fileLen = getFileLength(arg1, arg2)
if (storageType == 1): #seedling
copyBlock(keyPointer, fileLen)
elif (storageType == 2): #sapling
processIndexBlock(keyPointer)
elif (storageType == 3): #tree
processMasterIndexBlock(keyPointer)
elif (storageType == 5): #extended (forked)
processForkedFile(keyPointer)
def copyBlock(arg1, arg2):
#arg1: block to copy
#arg2: bytes to write (should be 512,
# unless final block with less than 512 bytes)
#print(arg1 + " " + arg2 + " " + g.activeFileBytesCopied)
if (arg1 == 0):
outBytes = (b'\x00' * arg2)
else:
outBytes = slyce(g.imageData, arg1*512, arg2)
if (g.resourceFork > 0):
if g.AD:
g.adFileData[g.activeFileBytesCopied+741:
(g.activeFileBytesCopied+741 + arg2)] = outBytes
if g.EX:
g.exFileData[g.activeFileBytesCopied:
(g.activeFileBytesCopied + arg2)] = outBytes
else:
g.outFileData[g.activeFileBytesCopied:
(g.activeFileBytesCopied + arg2)] = outBytes
g.activeFileBytesCopied += arg2
def processDir(arg1, arg2=None, arg3=None, arg4=None, arg5=None):
# arg1: dirBlock
# arg2/3/4/5: for non-key chunks: entryCount, entry#,
# workingDirName, processedEntryCount
entryCount = None
e = None
pe = None
workingDirName = None
if arg2:
entryCount = arg2
e = arg3
workingDirName = arg4
pe = arg5
else:
e = 0
pe = 0
entryCount = getDirEntryCount(arg1)
workingDirName = getWorkingDirName(arg1).decode("L1")
g.DIRPATH = (g.DIRPATH + "/" + workingDirName)
if g.PDOSPATH_INDEX:
if (g.PDOSPATH_INDEX == 1):
if (("/" + g.PDOSPATH_SEGMENT) != g.DIRPATH):
print("ProDOS volume name does not match disk image.")
sys.exit(2)
else:
g.PDOSPATH_INDEX += 1
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
else:
print(g.DIRPATH)
while (pe < entryCount):
if (getStorageType(arg1, e) > 0):
processEntry(arg1, e)
pe += 1
e += 1
if not ((e + ( e>11 ) ) % 13):
processDir(getDirNextChunkPointer(arg1),
entryCount,
e,
workingDirName,
pe)
break
def processEntry(arg1, arg2):
'''
print(getFileName(arg1, arg2), getStorageType(arg1, arg2),
getFileType(arg1, arg2), getKeyPointer(arg1, arg2),
getFileLength(arg1, arg2), getAuxType(arg1, arg2),
getCreationDate(arg1, arg2), getModifiedDate(arg1, arg2))
'''
g.activeFileName = getFileName(arg1 ,arg2).decode("L1")
g.activeFileSize = getFileLength(arg1, arg2)
if ((not g.PDOSPATH_INDEX) or (g.activeFileName == g.PDOSPATH_SEGMENT)):
if (getStorageType(arg1, arg2) == 13): # if ProDOS directory
if not g.PDOSPATH_INDEX:
g.targetDir = (g.targetDir + "/" + g.activeFileName)
g.ADdir = (g.targetDir + "/.AppleDouble")
if not (g.DIR or os.path.isdir(g.targetDir)):
makedirs(g.targetDir)
if not (g.DIR or (not g.AD) or os.path.isdir(g.ADdir)):
makedirs(g.ADdir)
if g.PDOSPATH_SEGMENT:
g.PDOSPATH_INDEX += 1
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
processDir(getKeyPointer(arg1, arg2))
g.DIRPATH = g.DIRPATH.rsplit("/", 1)[0]
if not g.PDOSPATH_INDEX:
g.targetDir = g.targetDir.rsplit("/", 1)[0]
g.ADdir = (g.targetDir + "/.AppleDouble")
else: # if ProDOS file
if not g.PDOSPATH_INDEX:
print(" " + g.activeFileName)
if g.DIR:
return
if not g.targetName:
g.targetName = g.activeFileName
if g.EX:
eTargetName = (g.targetName + "#" +
getFileType(arg1, arg2).lower() +
getAuxType(arg1, arg2).lower())
touch(g.targetDir + "/" + g.targetName)
if g.AD: makeADfile()
copyFile(arg1, arg2)
saveFile((g.targetDir + "/" + g.targetName), g.outFileData)
creationDate = getCreationDate(arg1, arg2)
modifiedDate = getModifiedDate(arg1, arg2)
if (creationDate is None and modifiedDate is not None):
creationDate = modifiedDate
elif (creationDate is not None and modifiedDate is None):
modifiedDate = creationDate
elif (creationDate is None and modifiedDate is None):
creationDate = (datetime.datetime.today() -
datetime.datetime(1970,1,1)).days*24*60*60
modifiedDate = creationDate
if g.AD: # AppleDouble
# set dates
ADfilePath = (g.ADdir + "/" + g.targetName)
writecharsHex(g.adFileData,
637,
(unixDateToADDate(creationDate) +
unixDateToADDate(modifiedDate)))
writecharHex(g.adFileData, 645, "80")
writecharHex(g.adFileData, 649, "80")
#set type/creator
writechars(g.adFileData, 653, b'p')
writecharsHex(g.adFileData,
654,
(getFileType(arg1, arg2) +
getAuxType(arg1, arg2)))
writechars(g.adFileData, 657, b'pdos')
saveFile(ADfilePath, g.adFileData)
touch((g.targetDir + "/" + g.targetName), modifiedDate)
if g.EX: # extended name
os.rename((g.targetDir + "/" + g.targetName),
(g.targetDir + "/" + eTargetName))
if (len(g.exFileData) > 0):
saveFile((g.targetDir + "/" + eTargetName + "r"),
g.exFileData)
touch((g.targetDir + "/" + eTargetName + "r"),
modifiedDate)
if g.PDOSPATH_SEGMENT:
syncExit()
g.targetName = None
#else:
#print(g.activeFileName + " doesn't match " + g.PDOSPATH_SEGMENT)
def processForkedFile(arg1):
# finder info except type/creator
fInfoA_entryType = readcharDec(g.imageData, 9)
fInfoB_entryType = readcharDec(g.imageData, 27)
if (fInfoA_entryType == 1):
writechars(g.imageData, 661, readchars(g.imageData, 18, 8))
elif (fInfoA_entryType == 2):
writechars(g.imageData, 669, readchars(g.imageData, 10, 16))
if (fInfoB_entryType == 1):
writechars(g.imageData, 661, readchars(g.imageData, 36, 8))
elif (fInfoB_entryType == 2):
writechars(g.imageData, 669, readchars(g.imageData, 28, 16))
for f in [0, 256]:
g.resourceFork = f
g.activeFileBytesCopied = 0
forkStart = (arg1 * 512) # start of Forked File key block
# print("--" + forkStart)
forkStorageType = readcharDec(g.imageData, forkStart+f+0)
forkKeyPointer = (readcharDec(g.imageData, forkStart+f+1) +
readcharDec(g.imageData, forkStart+f+2)*256)
forkFileLen = (readcharDec(g.imageData, forkStart+f+5) +
readcharDec(g.imageData, forkStart+f+6)*256 +
readcharDec(g.imageData, forkStart+f+7)*256*256)
g.activeFileSize = forkFileLen
if (g.resourceFork > 0):
rsrcForkLenHex = (readcharHex(g.imageData, forkStart+f+7) +
readcharHex(g.imageData, forkStart+f+6) +
readcharHex(g.imageData, forkStart+f+5))
# print(">>>" + rsrcForkLenHex)
print(" [resource fork]")
if g.AD:
writecharsHex(g.adFileData, 35, rsrcForkLenHex)
else:
print(" [data fork]")
if (forkStorageType == 1): #seedling
copyBlock(forkKeyPointer, forkFileLen)
elif (forkStorageType == 2): #sapling
processIndexBlock(forkKeyPointer)
elif (forkStorageType == 3): #tree
processMasterIndexBlock(forkKeyPointer)
# print()
g.resourceFork = 0
def processMasterIndexBlock(arg1):
processIndexBlock(arg1, True)
def processIndexBlock(arg1, arg2=False):
#arg1: indexBlock
#arg2: if True, it's a Master Index Block
pos = 0
bytesRemaining = g.activeFileSize
while (g.activeFileBytesCopied < g.activeFileSize):
targetBlock = (readcharDec(g.imageData, arg1*512+pos) +
readcharDec(g.imageData, arg1*512+(pos+256))*256)
if arg2:
processIndexBlock(targetBlock)
else:
bytesRemaining = (g.activeFileSize - g.activeFileBytesCopied)
bs = (bytesRemaining if (bytesRemaining < 512) else 512)
copyBlock(targetBlock, bs)
pos += 1
if (pos > 255):
break # go to next entry in Master Index Block (tree)
def makeADfile():
if not g.AD: return
touch(g.ADdir + "/" + g.targetName)
g.adFileData = bytearray(b'\x00' * 741)
# ADv2 header
writecharsHex(g.adFileData, hexToDec("00"), "0005160700020000")
# number of entries
writecharsHex(g.adFileData, hexToDec("18"), "000D")
# Resource Fork
writecharsHex(g.adFileData, hexToDec("1A"), "00000002000002E500000000")
# Real Name
writecharsHex(g.adFileData, hexToDec("26"), "00000003000000B600000000")
# Comment
writecharsHex(g.adFileData, hexToDec("32"), "00000004000001B500000000")
# Dates Info
writecharsHex(g.adFileData, hexToDec("3E"), "000000080000027D00000010")
# Finder Info
writecharsHex(g.adFileData, hexToDec("4A"), "000000090000028D00000020")
# ProDOS file info
writecharsHex(g.adFileData, hexToDec("56"), "0000000B000002C100000008")
# AFP short name
writecharsHex(g.adFileData, hexToDec("62"), "0000000D000002B500000000")
# AFP File Info
writecharsHex(g.adFileData, hexToDec("6E"), "0000000E000002B100000004")
# AFP Directory ID
writecharsHex(g.adFileData, hexToDec("7A"), "0000000F000002AD00000004")
# dbd (second time) will create DEV, INO, SYN, SV~
def syncExit():
if (not g.silent and g.AD and os.path.isdir("/usr/local/etc/netatalk")):
print("File(s) have been copied to the target directory. " +
"If the directory")
print("is shared by Netatalk, please type 'afpsync' now.")
# saveFile(g.imageFile, g.imageData)
sys.exit(0)
def usage():
print(sys.modules[__name__].__doc__)
sys.exit(1)
# --- ID bashbyter functions (adapted)
def decToHex(arg1):
# converts single-byte decimal value to hexadecimal equivalent
# arg: decimal value from 0-255
# out: two-digit hex string from 00-FF
#exit: 21=invalid arg
if (arg1<0 or arg1>255): sys.exit(21)
return to_hex(arg1).upper()
def hexToDec(arg1):
# converts single-byte hexadecimal value to decimal equivalent
# arg: two-digit hex value from 00-FF
# out: decimal value
#exit: 21=invalid arg
if (len(arg1) != 2): return 21
return to_dec(arg1)
def hexToBin(arg1):
# converts single-byte hexadecimal value to binary string
# arg: two-digit hex value from 00-FF
# out: binary string value
#exit: 21=invalid arg
if (len(arg1) != 2): return 21
return to_bin(arg1).zfill(8)
def binToDec(arg1):
# converts single-byte binary string (8 bits) value to decimal
# warning: no error checking
# arg: binary string up to 8 bits
# out: decimal value
return to_dec([arg1])
def binToHex(arg1):
# converts single-byte binary string (8 bits) value to hex
# warning: no error checking
# arg: binary string up to 8 bits
# out: hex value
return to_hex(arg1).upper()
def charToDec(arg1):
# converts single char (of type bytes) to corresponding decimal value
# arg: one char (of type bytes)
# out: decimal value from 0-255
#exit: 21=invalid arg
if (len(arg1) != 1): return 21
return to_dec(arg1)
def charToHex(arg1):
# converts single char (of type bytes) to corresponding hex value
# arg: one char (of type bytes)
# out: hexadecimal value from 00-FF
#exit: 21=invalid arg
if (len(arg1) != 1): return 21
return to_hex(arg1).upper()
def decToChar(arg1):
# converts single-byte decimal value to equivalent char (of type bytes)
# arg: decimal number from 0-255
# out: one character
#exit: 21=invalid arg
if (arg1<0 or arg1>255): sys.exit(21)
return to_bytes(arg1)
def hexToChar(arg1):
# converts single-byte hex value to corresponding char (of type bytes)
# arg: two-digit hexadecimal number from 00-FF
# out: one character
#exit: 21=invalid arg
if (len(arg1) != 2): return 21
return to_bytes(arg1)
def readchars(arg1, arg2=0, arg3=0):
# read one or more characters from a bytes variable
# arg1: bytes or bytearray variable
# arg2: (optional) offset (# of bytes to skip before reading)
# arg3: (optional) # of chars to read (default is to end of bytes var)
# out: sequence of characters (bytes or bytearray)
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
sys.exit(21)
if (arg2<0): sys.exit(22)
if (arg3<0): sys.exit(23)
if (arg3 == 0):
arg3 = len(arg1)
return slyce(arg1, arg2, arg3)
def readcharDec(arg1, arg2=0):
# read one character from bytes var & convert to equivalent dec value
# arg1: bytes var
# arg2: (optional) offset (# of bytes to skip before reading)
# out: decimal value from 0-255
# exit: 21=invalid arg1, 22=invalid arg2
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
sys.exit(21)
if (arg2<0): sys.exit(22)
return to_dec(slyce(arg1, arg2, 1))
def readcharHex(arg1, arg2=0):
# read one character from bytes var & convert to corresponding hex value
# arg1: bytes var
# arg2: (optional) offset (# of bytes to skip before reading)
# out: two-digit hex value from 00-FF
# exit: 21=invalid arg1, 22=invalid arg2
if not (isinstance(arg1, bytes) or isinstance(arg1, bytearray)):
sys.exit(21)
if (arg2<0): sys.exit(22)
return to_hex(slyce(arg1, arg2, 1))
def writechars(arg1, arg2, arg3):
# write one or more characters (bytes) to bytearray
# arg1: bytearray variable
# arg2: offset (# of bytes to skip before writing)
# arg3: sequence of bytes (or bytearray)
# out: nothing
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
if not isinstance(arg1, bytearray): sys.exit(21)
if (arg2<0): sys.exit(22)
if not (isinstance(arg3, bytes) or isinstance(arg3, bytearray)):
sys.exit(23)
arg1[arg2:arg2+len(arg3)] = arg3
def writecharDec(arg1, arg2, arg3):
# write corresponding char of single-byte decimal value into bytearray
# arg1: bytearray
# arg2: offset (# of bytes to skip before writing)
# arg3: decimal number from 0-255
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
# out: nothing
if not isinstance(arg1, bytearray): sys.exit(21)
if (arg2<0): sys.exit(22)
if not isnumber(arg3): sys.exit(23)
arg1[arg2:arg2+1] = to_bytes(arg3)
def writecharHex(arg1, arg2, arg3):
# write corresponding character of single-byte hex value into bytearray
# arg1: bytearray
# arg2: offset (# of bytes to skip before writing)
# arg3: two-digit hexadecimal number from 00-FF
# out: nothing
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
if not isinstance(arg1, bytearray): sys.exit(21)
if (arg2<0): sys.exit(22)
if not isinstance(arg3, type("".encode().decode())): sys.exit(23)
arg1[arg2:arg2+1] = to_bytes(arg3)
def writecharsHex(arg1, arg2, arg3):
# write corresponding characters of hex values into bytearray
# arg1: bytearray
# arg2: offset (# of bytes to skip before writing)
# arg3: string of two-digit hexadecimal numbers from 00-FF
# out: nothing
# exit: 21=invalid arg1, 22=invalid arg2, 23=invalid arg3
if not isinstance(arg1, bytearray): sys.exit(21)
if (arg2<0): sys.exit(22)
if not isinstance(arg3, type("".encode().decode())): sys.exit(23)
arg1[arg2:arg2+len(to_bytes(arg3))] = to_bytes(arg3)
#---- IvanX general purpose functions ----#
def slyce(val, start_pos=0, length=1, reverse=False):
"""returns slice of object (but not a slice object)
allows specifying length, and 3.x "bytes" consistency"""
the_slyce = val[start_pos:start_pos+length]
return (the_slyce[::-1] if reverse else the_slyce)
def to_hex(val):
"""convert bytes, decimal number, or [bin-ustr] to two-digit hex values
unlike hex(), accepts bytes; has no leading 0x or trailing L"""
from binascii import b2a_hex
if isinstance(val, list): # [bin-ustr]
val = int(val[0], 2)
if isinstance(val, bytes): # bytes
return b2a_hex(val).decode("L1")
elif isnumber(val):
# .encode().decode() always returns unicode in both P2 and P3
return (hex(val)[2:].split("L")[0]).encode("L1").decode("L1")
else:
raise Exception("to_hex() requires bytes, int/long, or [bin-ustr]")
def hex_slyce(val, start_pos=0, length=1, little_endian=False):
"""returns bytes slyce as hex-ustr"""
return to_hex(slyce(val, start_pos, length, little_endian))
def dec_slyce(val, start_pos=0, length=1, little_endian=False):
"""returns bytes slyce converted to decimal int/long"""
return to_dec(hex_slyce(val, start_pos, length, little_endian))
def bin_slyce(val, start_pos=0, length=1, little_endian=False):
"""returns bytes slyce converted to bin-ustr"""
return to_bin(hex_slyce(val, start_pos, length, little_endian))
def to_dec(val):
"""convert bytes, hex-ustr or [bin-ustr] to decimal int/long"""
if isinstance(val, list): # [bin-ustr]
return int(val[0], 2)
elif isinstance(val, bytes): # bytes
return int(to_hex(val), 16)
elif isinstance(val, type("".encode().decode())): # hex-ustr
return int(val, 16)
else:
raise Exception("to_dec() requires bytes, hex-ustr or [bin-ustr]")
def to_bin(val):
"""convert bytes, hex-ustr, or int/long to bin-ustr"""
if isinstance(val, bytes): # bytes
return (bin(to_dec(to_hex(val))))[2:].encode("L1").decode("L1")
elif isinstance(val, type("".encode().decode())): # hex-ustr
return (bin(int(val, 16)))[2:].encode("L1").decode("L1")
elif isnumber(val): # int/long
return (bin(val))[2:].encode("L1").decode("L1")
else:
raise Exception("to_bin() requires bytes, hex-ustr, or int/long")
def to_bytes(val):
"""converts hex-ustr, int/long, or [bin-ustr] to bytes"""
from binascii import a2b_hex
if isinstance(val, list): # [bin-ustr]
val = to_hex(val[0])
if isnumber(val): # int/long
val = to_hex(val)
if isinstance(val, type("".encode().decode())): # hex-ustr
return a2b_hex(bytes(val.encode("L1"))) # works on both P2 and P3
else:
raise Exception(
"to_bytes() requires hex-ustr, int/long, or [bin-ustr]")
def shift(items):
"""Shift list items to left, losing the first item.
in : list
out: list
"""
for i in range(0, (len(items)-1)):
items[i] = items[i+1]
del items[-1]
return items
def s(string):
"""Perform local variable substution, e.g. 'total: {num} items'"""
# http://stackoverflow.com/questions/2960772/
# putting-a-variable-inside-a-string-python
# http://stackoverflow.com/questions/6618795/
# get-locals-from-calling-namespace-in-python
import inspect
frame = inspect.currentframe()
try:
rVal = string.format(**frame.f_back.f_locals)
finally:
del frame
return rVal
def get_object_names(cls, include_subclasses=True):
object_names = []
for (this_object_name, this_object_id) in list(globals().items()):
if include_subclasses:
if isinstance(this_object_id, cls):
object_names.append(this_object_name)
else:
if type(this_object_id) is cls:
object_names.append(this_object_name)
return object_names
def touch(filePath, modTime=None):
# http://stackoverflow.com/questions/1158076/implement-touch-using-python
import os
if (os.name == "nt"):
if filePath[-1] == ".": filePath += "-"
filePath = filePath.replace("./", ".-/")
with open(filePath, "ab"):
os.utime(filePath, (None if (modTime is None) else (modTime, modTime)))
def mkdir(dirPath):
import os
if (os.name == "nt"):
if dirPath[-1] == ".": dirPath += "-"
dirPath = dirPath.replace("./", ".-/")
try:
os.mkdir(dirPath)
except FileExistsError:
pass
def makedirs(dirPath):
import os
if (os.name == "nt"):
if dirPath[-1] == ".": dirPath += "-"
dirPath = dirPath.replace("./", ".-/")
try:
os.makedirs(dirPath)
except FileExistsError:
pass
def loadFile(filePath):
import os
if (os.name == "nt"):
if filePath[-1] == ".": filePath += "-"
filePath = filePath.replace("./", ".-/")
with open(filePath, "rb") as imageHandle:
return imageHandle.read()
def saveFile(filePath, fileData):
import os
if (os.name == "nt"):
if filePath[-1] == ".": filePath += "-"
filePath = filePath.replace("./", ".-/")
with open(filePath, "wb") as imageHandle:
imageHandle.write(fileData)
def isnumber(number):
try: # make sure it's not a string
len(number)
return False
except TypeError:
pass
try:
int(number)
except ValueError:
return False
return True
#---- end IvanX general purpose functions ----#
# --- start
args = sys.argv
if (len(args) == 1):
usage()
if (args[1] == "-s"):
g.silent=1
args = args[1:] #shift
if (args[1] == "-ad"):
g.AD = 1
args = args[1:] #shift
if (args[1] == "-e"):
if g.AD: usage()
g.EX = 1
args = args[1:] #shift
if (args[1] == "-cat"):
g.DIR = 1
args = args[1:]
if not ((g.DIR and len(args) >= 2) or (len(args) >= 3)):
usage()
if ((len(args) == 4) and
(slyce(args[2],0,1) != "/") and
(slyce(args[2],0,1) != ":")):
usage()
g.imageFile = args[1]
if not os.path.isfile(g.imageFile):
print("Source " + g.imageFile + " was not found.")
sys.exit(2)
g.imageData = loadFile(g.imageFile)
if (len(args) == 4):
g.PDOSPATH = args[2].upper()
targetPath = args[3]
if os.path.isdir(targetPath):
g.targetDir = targetPath
else:
g.targetDir = targetPath.rsplit("/", 1)[0]
g.targetName = targetPath.rsplit("/", 1)[1]
if not os.path.isdir(g.targetDir):
print("Target directory not found.")
sys.exit(2)
else:
if not g.DIR:
if not os.path.isdir(args[2]):
print("Target directory not found.")
sys.exit(2)
g.activeDirBlock = 0
g.activeFileName = ""
g.activeFileSize = 0
g.activeFileBytesCopied = 0
g.resourceFork = 0
g.PDOSPATH_INDEX = 0
if (len(args) == 4):
g.PDOSPATH = g.PDOSPATH.replace(':', '/').split('/')
if not g.PDOSPATH[0]:
g.PDOSPATH_INDEX += 1
g.PDOSPATH_SEGMENT = g.PDOSPATH[g.PDOSPATH_INDEX]
g.ADdir = (g.targetDir + "/.AppleDouble")
if not ((not g.AD) or os.path.isdir(g.ADdir)):
mkdir(g.ADdir)
processDir(2)
print("ProDOS file not found within image file.")
sys.exit(2)
else:
if not g.DIR:
# print(args[0], args[1], args[2])
g.targetDir = (args[2] + "/" + getVolumeName().decode("L1"))
g.ADdir = (g.targetDir + "/.AppleDouble")
if not os.path.isdir(g.targetDir):
makedirs(g.targetDir)
if not ((not g.AD) or os.path.isdir(g.ADdir)):
makedirs(g.ADdir)
processDir(2)
if not g.DIR:
syncExit()