Module to merge 'cfrg' resources (to create fat ppc/cfm68k programs
and shared libraries).
diff --git a/Mac/scripts/cfmfile.py b/Mac/scripts/cfmfile.py
new file mode 100644
index 0000000..b39d1bc
--- /dev/null
+++ b/Mac/scripts/cfmfile.py
@@ -0,0 +1,235 @@
+"""cfmfile - Interface to code fragments on file"""
+import struct
+import Res
+import macfs
+import string
+
+Error = 'cfmfile.Error'
+
+READ = 1
+WRITE = 2
+smAllScripts = -3
+BUFSIZE = 0x100000
+
+class FragmentInfo:
+	"""Information on a single fragment"""
+	def __init__(self):
+		self.arch = 'pwpc'
+		self.current_version = 0
+		self.oldest_version = 0
+		self.stacksize = 0
+		self.libdir = 0
+		self.fragtype = 1
+		self.location = 1
+		self.offset = 0
+		self.length = 0
+		self.res_0 = 0
+		self.res_1 = 0
+		self.name = ''
+		self.ifp = None
+		
+	def load(self, data):
+		if len(data) < 43:
+			raise Error, 'Not enough data in cfrg resource'
+		self.arch = data[:4]
+		self.update, self.current_version, self.oldest_version, \
+			self.stacksize, self.libdir, self.fragtype, self.location, \
+			self.offset, self.length, self.res_0, self.res_1, length = \
+			struct.unpack("llllhbbllllh", data[4:42])
+		namelen = ord(data[42])
+		self.name = data[43:43+namelen]
+		if len(self.name) != namelen:
+			raise Error, 'Not enough data in cfrg resource'
+		return length
+		
+	def save(self):
+		length = (43+len(self.name)+3) & ~3
+		data = self.arch + struct.pack("llllhbbllllh", self.update, \
+			self.current_version, self.oldest_version, self.stacksize, \
+			self.libdir, self.fragtype, self.location, self.offset, \
+			self.length, self.res_0, self.res_1, length)
+		data = data + chr(len(self.name)) + self.name
+		data = data + ('\0'*(length-len(data)))
+		return data
+		
+	def copydata(self, ofp):
+		"""Copy fragment data to a new file, updating self.offset"""
+		if self.location != 1:
+			raise Error, 'Can only copy kOnDiskFlat (data fork) fragments'
+		if not self.ifp:
+			raise Error, 'No source file for fragment'
+		# Find out real length (if zero)
+		if self.length == 0:
+			self.ifp.seek(0, 2)
+			self.length = self.ifp.tell()
+		# Position input file and record new offset from output file
+		self.ifp.seek(self.offset)
+		self.offset = ofp.tell()
+		l = self.length
+		while l:
+			if l > BUFSIZE:
+				ofp.write(self.ifp.read(BUFSIZE))
+				l = l - BUFSIZE
+			else:
+				ofp.write(self.ifp.read(l))
+				l = 0
+		self.ifp = ofp
+		
+	def setfile(self, ifp):
+		self.ifp = ifp
+		
+class FragmentResource:
+
+	def __init__(self, data):
+		self.load(data)
+
+	def load(self, data):
+		r0, r1, version, r3, r4, r5, r6, nfrag = struct.unpack("llllllll", data[:32])
+		if version != 1:
+			raise Error, 'Unsupported cfrg version number %d'%version
+		data = data[32:]
+		self.fragments = []
+		for i in range(nfrag):
+			f = FragmentInfo()
+			len = f.load(data)
+			data = data[len:]
+			self.fragments.append(f)
+		if data:
+			raise Error, 'Spurious data after fragment descriptions'
+			
+	def save(self):
+		data = struct.pack("llllllll", 0, 0, 1, 0, 0, 0, 0, len(self.fragments))
+		for f in self.fragments:
+			data = data+f.save()
+		return data
+			
+	def setfile(self, ifp):
+		for f in self.fragments:
+			f.setfile(ifp)
+			
+	def copydata(self, ofp):
+		for f in self.fragments:
+			f.copydata(ofp)
+			
+	def getfragments(self):
+		return self.fragments
+		
+	def addfragments(self, fragments):
+		self.fragments = self.fragments + fragments
+
+class ResourceCollection:
+	def __init__(self, fhandle):
+		self.reslist = []
+		self.fhandle = fhandle
+		oldresfile = Res.CurResFile()
+		Res.UseResFile(fhandle)
+		Res.SetResLoad(0)
+		ntypes = Res.Count1Types()
+		for itype in range(1, 1+ntypes):
+			type = Res.Get1IndType(itype)
+			nresources = Res.Count1Resources(type)
+			for ires in range(1, 1+nresources):
+				res = Res.Get1IndResource(type, ires)
+				id, type, name = res.GetResInfo()
+				self.reslist.append((type, id))
+		Res.SetResLoad(1)
+		Res.UseResFile(oldresfile)
+			
+	def contains(self, type, id):
+		return (type, id) in self.reslist
+		
+	def getresource(self, type, id):
+		oldresfile = Res.CurResFile()
+		Res.UseResFile(self.fhandle)
+		Res.SetResLoad(1)
+		resource = Res.Get1Resource(type, id)
+		Res.UseResFile(oldresfile)
+		return resource
+		
+	def saveresto(self, type, id, fhandle):
+		oldresfile = Res.CurResFile()
+		resource = self.getresource(type, id)
+		id, type, name = resource.GetResInfo()
+		resource.DetachResource()
+		Res.UseResFile(fhandle)
+		resource.AddResource(type, id, name)
+		Res.UseResFile(oldresfile)
+		
+	def getreslist(self):
+		return self.reslist
+		
+class CfmFile(ResourceCollection, FragmentResource):
+	
+	def __init__(self, fsspec):
+		rfork = Res.FSpOpenResFile(fsspec, READ)
+		dfork = open(fsspec.as_pathname(), 'rb')
+		ResourceCollection.__init__(self, rfork)
+		cfrg_resource = self.getresource('cfrg', 0)
+		FragmentResource.__init__(self, cfrg_resource.data)
+		self.setfile(dfork)
+
+def mergecfmfiles(inputs, output):
+	# Convert inputs/outputs to fsspecs
+	for i in range(len(inputs)):
+		if type(inputs[i]) == type(''):
+			inputs[i] = macfs.FSSpec(inputs[i])
+	if type(output) == type(''):
+		output = macfs.FSSpec(output)
+		
+	input_list = []
+	for i in inputs:
+		input_list.append(CfmFile(i))
+		
+	# Create output file, if needed
+	creator, tp = inputs[0].GetCreatorType()
+	try:
+		Res.FSpCreateResFile(output, creator, tp, smAllScripts)
+	except Res.Error:
+		pass
+		
+	# Copy fragments
+	dfork = open(output.as_pathname(), 'wb')
+	for i in input_list:
+		i.copydata(dfork)
+	dfork.close()
+		
+	# Merge cfrg's
+	for i in input_list[1:]:
+		input_list[0].addfragments(i.getfragments())
+		
+	old_res_file = Res.CurResFile()
+	rfork = Res.FSpOpenResFile(output, WRITE)
+	Res.UseResFile(rfork)
+	
+	# Write cfrg
+	data = input_list[0].save()
+	cfrg_resource = Res.Resource(data)
+	cfrg_resource.AddResource('cfrg', 0, '')
+	resources_done = [('cfrg', 0)]
+	
+	# Write other resources
+	for i in input_list:
+		todo = i.getreslist()
+		for tp, id in todo:
+			if (tp, id) in resources_done:
+				continue
+			i.saveresto(tp, id, rfork)
+			resources_done.append(tp, id)
+			
+def main():
+	list = []
+	while 1:
+		fss, ok = macfs.PromptGetFile("Next input file:", "shlb", "APPL")
+		if not ok: break
+		list.append(fss)
+	if not list:
+		sys.exit(0)
+	output, ok = macfs.StandardPutFile("Output file:")
+	if not ok:
+		sys.exit(0)
+	mergecfmfiles(list, output)
+	
+if __name__ == '__main__':
+	main()
+	
+