T. Joseph Carter 9601ed3e14 Remove trailing whitespace
An unusual quirk of git is that it tends not to like trailing whitespace
at the end of lines, and trailing blank lines at the end of a file.  It
messes with the word diffs a bit I think.  Anyway, it's easily removed,
so I've removed it.
2015-10-05 23:52:43 -07:00

882 lines
30 KiB
Python
Executable File

#!/usr/bin/env 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()