From 8746491cc49434a6939deac8ae9844d6d6d26e3f Mon Sep 17 00:00:00 2001
From: Josh Kropf <josh@slashdev.ca>
Date: Tue, 17 Feb 2009 18:38:40 -0500
Subject: [PATCH 2/2] Pack sibling cod files into pkzip format during save

Added zlib as optional dependency to autotools files
to support crc32 checks when building packed .cod file
---
 configure.ac        |    5 ++
 src/Makefile.am     |    2 +-
 src/cod-internal.h  |   77 ++++++++++++++++++++++
 src/cod.cc          |  174 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/cod.h           |   61 ++++++++++++++++++
 src/m_javaloader.cc |   31 +++++++---
 src/m_javaloader.h  |    4 +-
 7 files changed, 344 insertions(+), 10 deletions(-)

diff --git a/configure.ac b/configure.ac
index 875a8c1..68b01b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -67,6 +67,11 @@ PKG_CHECK_MODULES([FUSE], [fuse >= 2.5],
 	[echo "FUSE library not found, skipping fuse module."; FUSE_FOUND=0]
 	)
 
+PKG_CHECK_MODULES([ZLIB], [zlib],
+	[AC_DEFINE([HAVE_ZLIB], [1], [Use crc32 when generating packed .cod files])],
+	[echo "WARING: zlib not found... packed .cod files will fail crc checks"]
+	)
+
 pkgconfigdir=${libdir}/pkgconfig
 AC_SUBST(pkgconfigdir)
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 2d4ed36..a76f1dd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -155,7 +155,7 @@ libbarry_la_SOURCES = time.cc \
 	sha1.cc \
 	iconv.cc
 #libbarry_la_LIBADD = $(LTLIBOBJS) $(LIBUSB_LIBS) $(OPENSSL_LIBS)
-libbarry_la_LIBADD = $(LTLIBOBJS) $(LIBUSB_LIBS) @LTLIBICONV@
+libbarry_la_LIBADD = $(LTLIBOBJS) $(LIBUSB_LIBS) $(ZLIB_LIBS) @LTLIBICONV@
 libbarry_la_LDFLAGS = -version-info ${LIB_BARRY_VERSION}
 
 ##if DO_TEST
diff --git a/src/cod-internal.h b/src/cod-internal.h
index 4799c4e..b4e8584 100644
--- a/src/cod-internal.h
+++ b/src/cod-internal.h
@@ -8,6 +8,7 @@
     See also:
         http://drbolsen.wordpress.com/2006/07/26/blackberry-cod-file-format/
         http://drbolsen.wordpress.com/2006/08/11/10/
+        http://www.pkware.com/documents/casestudies/APPNOTE.TXT
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -30,6 +31,82 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#define PKZIP_LOCAL_FILE_SIG	{0x50, 0x4B, 0x03, 0x04}
+#define PKZIP_DIRECTORY_SIG	{0x50, 0x4B, 0x01, 0x02}
+#define PKZIP_END_DIRECTORY_SIG	{0x50, 0x4B, 0x05, 0x06}
+
+
+typedef struct BXLOCAL {
+	uint16_t	hour:5;
+	uint16_t	minute:6;
+	uint16_t	second:5;
+} __attribute__ ((packed)) msdos_time_t;
+
+
+typedef struct BXLOCAL {
+	uint16_t	year:7;		// number of years since 1980
+	uint16_t	month:4;
+	uint16_t	day:5;
+} __attribute__ ((packed)) msdos_date_t;
+
+
+typedef struct BXLOCAL {
+	//uint8_t	signature[4];		// PKZIP local file header 0x504B0304
+	uint16_t	version_needed;		// version needed to extract, 0x0A00
+	uint16_t	general_flag;		// general purpose bit flag, 0x0000
+	uint16_t	compression_method;	// compression method, 0x0000 = stored, no compression
+	msdos_time_t	last_mod_time;
+	msdos_date_t	last_mod_date;
+	uint32_t	crc_32;
+	uint32_t	compressed_size;	// compression method is 'stored'
+	uint32_t	uncompressed_size;	// both sizes are equal
+	uint16_t	file_name_length;
+	uint16_t	extra_field_length;
+	//char		file_name[variable];
+	//char		extra_field[variable];
+} __attribute__ ((packed)) pkzip_local_header_t;
+
+
+typedef struct BXLOCAL {
+	//uint8_t	signature[4];		// PKZIP central directory 0x504B0304
+	uint16_t	version_madeby;		// version used to compress, 0x0A00
+	uint16_t	version_needed;		// version needed to extract, 0x0A00
+	uint16_t	general_flag;		// general purpose bit flag, 0x0000
+	uint16_t	compression_method;	// compression method, 0x0000 = stored, no compression
+	msdos_time_t	last_mod_time;
+	msdos_date_t	last_mod_date;
+	uint32_t	crc_32;
+	uint32_t	compressed_size;	// size of corresponding local file entry
+	uint32_t	uncompressed_size;	// both sizes are equal
+	uint16_t	file_name_length;
+	uint16_t	extra_field_length;
+	uint16_t	file_comment_length;
+	uint16_t	disk_number;		// number of the disk on which this file begins, always zero
+	uint16_t	internal_file_attr;	// always zero
+	uint32_t	external_file_attr;	// always zero
+	uint32_t	relative_offset;	// offset from beginning of this disk (this zip file)
+						// to start of corresponding local file entry
+	//char		file_name[variable];
+	//char		extra_field[variable];
+	//char		file_comment[variable];
+} __attribute__ ((packed)) pkzip_directory_t;
+
+
+typedef struct BXLOCAL {
+	//uint8_t	signature[4];		// PKZIP end central directory 0x504B0506
+	uint16_t	this_disk_number;	// number of this disk, always zero
+	uint16_t	disk_with_first;	// number of the disk with the start of
+						// central directory, always zero
+	uint16_t	this_disk_entry_count;	// total number of entries in the central directory on this disk
+	uint16_t	total_entry_count;	// total number of entries in the central directory
+						// always equals this_disk_entry_count
+	uint32_t	directory_length;	// total size of the central directory
+	uint32_t	directory_offset;	// offset from beginning of this disk (this zip file)
+						// to the first central directory entry
+	uint16_t	file_comment_length;
+	//char		file_comment[variable];
+} __attribute__ ((packed)) pkzip_end_directory_t;
+
 
 typedef struct BXLOCAL {
 	uint16_t 	type;			// Type			// 50 4B
diff --git a/src/cod.cc b/src/cod.cc
index 2021607..96e5e14 100644
--- a/src/cod.cc
+++ b/src/cod.cc
@@ -24,15 +24,189 @@
 #include "cod-internal.h"
 #include "error.h"
 #include "endian.h"
+#include <config.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <string>
+#include <string.h>
+
+#ifdef HAVE_ZLIB
+ #include <zlib.h>
+#endif
 
 using namespace std;
 
 namespace Barry {
 
+
+uint32_t SeekNextCod(std::istream &input)
+{
+	char local_file_sig[] = PKZIP_LOCAL_FILE_SIG;
+	char directory_sig[] = PKZIP_DIRECTORY_SIG;
+
+	char signature[4];
+
+	if( input.eof() )
+		return 0;
+
+	if( input.read(signature, sizeof(signature)).eof() ) {
+		throw Error("SeekNextCod: EOF while reading file signature");
+	}
+
+	if( memcmp(signature, local_file_sig, sizeof(signature)) == 0 ) {
+		pkzip_local_header_t header;
+
+		if( input.read((char *)&header, sizeof(pkzip_local_header_t)).eof() ) {
+			throw Error("SeekNextCod: EOF while reading PKZIP header");
+		}
+
+		// skip cod file name and extra field, we don't need them
+		size_t skip_len = header.file_name_length + header.extra_field_length;
+		if( input.ignore(skip_len).eof() ) {
+			throw Error("SeekNextCod: EOF while skipping unused fields");
+		}
+
+		return btohl(header.compressed_size);
+	}
+	else if( memcmp(signature, directory_sig, sizeof(signature)) == 0 ) {
+		// done reading when central directory is reached
+		return 0;
+	}
+	else {
+		// stream does not contain packed cod files, unget the 4 bytes
+		for( unsigned int i=0; i<sizeof(signature); ++i ) input.unget();
+
+		// find and return size of cod file
+
+		if( input.seekg(0, ios::end).fail() ) {
+			throw Error("SeekNextCod: seek to end failed");
+		}
+
+		uint32_t size = input.tellg();
+
+		if( input.seekg(0, ios::beg).fail() ) {
+			throw Error("SeekNextCod: seek to start failed");
+		}
+
+		return size;
+	}
+}
+
+
+CodFileBuilder::CodFileBuilder(const std::string &module_name, size_t module_count)
+	: m_module_name(module_name)
+	, m_module_count(module_count)
+	, m_current_module(0)
+{
+}
+
+CodFileBuilder::~CodFileBuilder()
+{
+}
+
+void CodFileBuilder::WriteNextHeader(std::ostream &output, const uint8_t* module_buffer, uint32_t module_size)
+{
+	// ignored for single module .cod files (simple .cod file)
+	if( m_module_count == 1 ) {
+		return;
+	}
+
+	// 32bit CRC of module in file header and directory entry
+	// using zero for CRC will result in warnings when inflating .cod file
+	uint32_t crc = 0;
+
+#ifdef HAVE_ZLIB
+	crc = crc32(0, NULL, module_size);
+	crc = crc32(crc, module_buffer, module_size);
+#endif
+
+	// .cod file name for siblings have hyphenated index number, name-1.cod
+	std::ostringstream file_name(m_module_name, ios::app);
+	if( m_current_module == 0 )
+		file_name << ".cod";
+	else
+		file_name << "-" << m_current_module << ".cod";
+
+	// current stream pointer is relative offset to start of file entry
+	uint32_t entry_offset = output.tellp();
+
+	// structures for the local file entry and central directory entry
+	pkzip_local_header_t header;
+	pkzip_directory_t entry;
+
+	// zero both structs, most fields are zero
+	memset(&header, 0, sizeof(pkzip_local_header_t));
+	memset(&entry, 0, sizeof(pkzip_directory_t));
+
+	char header_sig[] = PKZIP_LOCAL_FILE_SIG;
+	output.write(header_sig, sizeof(header_sig));
+
+	// version is always 0x00A0 = 'Windows NTFS'
+	header.version_needed = htobs(10);
+
+	// time and date fields seem to randomly have invalid or fixed values
+	// just leave them as zero
+	//header.last_mod_time
+	//header.last_mod_date
+
+	header.crc_32 = htobl(crc);
+	header.compressed_size = htobl(module_size);
+	header.uncompressed_size = htobl(module_size);
+	header.file_name_length = htobs(file_name.str().length());
+
+	// the very first cod sibling to be written has an extra field
+	// length equal to 4, with all zeros in the field itself
+	// all subsequent siblings have a zero length extra field
+	//header.extra_field_length = htobs(4);
+
+	output.write((char *)&header, sizeof(pkzip_local_header_t));
+	output << file_name.str();
+
+	char footer_sig[] = PKZIP_DIRECTORY_SIG;
+
+	// version is always 0x00A0 = 'Windows NTFS'
+	entry.version_madeby = htobs(10);
+	entry.version_needed = htobs(10);
+
+	entry.crc_32 = htobl(crc);
+	entry.compressed_size = htobl(module_size);
+	entry.uncompressed_size = htobl(module_size);
+	entry.file_name_length = htobs(file_name.str().length());
+	entry.relative_offset = htobl(entry_offset);
+
+	m_directory.write(footer_sig, sizeof(footer_sig));
+	m_directory.write((char*)&entry, sizeof(pkzip_directory_t));
+	m_directory << file_name.str();
+
+	m_current_module ++;
+}
+
+void CodFileBuilder::WriteFooter(std::ostream &output)
+{
+	// ignored for single module .cod files (simple .cod file)
+	if( m_module_count == 1 ) {
+		return;
+	}
+
+	char sig[] = PKZIP_END_DIRECTORY_SIG;
+
+	pkzip_end_directory_t end;
+	memset(&end, 0, sizeof(pkzip_end_directory_t));
+
+	end.this_disk_entry_count = htobs(m_current_module);
+	end.total_entry_count = htobs(m_current_module);
+	end.directory_length = m_directory.str().length();
+
+	// current stream pointer is relative offset to start of directory
+	end.directory_offset = output.tellp();
+
+	output.write(m_directory.str().c_str(), m_directory.str().length());
+	output.write(sig, sizeof(sig));
+	output.write((char *)&end, sizeof(pkzip_end_directory_t));
+}
+
+
 //////////////////////////////////////////////////////////////////////////////
 // CodFile class
 
diff --git a/src/cod.h b/src/cod.h
index b7029f7..0df40e6 100644
--- a/src/cod.h
+++ b/src/cod.h
@@ -26,12 +26,73 @@
 #include "data.h"
 #include <sys/types.h>
 #include <stdio.h>
+#include <iostream>
+#include <sstream>
 
 #define CODFILE_TYPE_PACKED	0x4B50
 #define CODFILE_TYPE_SIMPLE	0xC0DE
 
 namespace Barry {
 
+
+//
+// SeekNextCod
+//
+/// Seeks the input stream to the next packed sibling .cod file and returns
+/// the packed .cod file size.  When all siblings have been read, zero is
+/// returned.
+///
+/// When input stream does not contain the signature for a packed .cod file,
+/// it's assumed the entire stream is the .cod file.
+///
+/// \param input stream to read from
+///
+/// \return size of next packed .cod file, or 0 finished reading .cod files
+///
+uint32_t SeekNextCod(std::istream &input);
+
+
+///
+/// The CodFileBuilder class is used to assemble multiple .cod files into
+/// a single packed .cod file using the pkzip file format.
+///
+class BXEXPORT CodFileBuilder
+{
+	const std::string &m_module_name;
+
+	size_t m_module_count;
+	unsigned int m_current_module;
+
+	std::ostringstream m_directory;
+
+public:
+	CodFileBuilder(const std::string &module_name, size_t module_count = 1);
+
+	~CodFileBuilder();
+
+	///
+	/// Writes packed .cod file header to the output stream, and appends
+	/// an entry to the central directory.  If the module count used to
+	/// create CodFileBuilder is equal to one, the call is ignored.
+	///
+	/// \param output stream to write to
+	///
+	/// \param buffer buffered .cod file data, input to CRC-32 function
+	///
+	/// \param module_size total size of .cod file data
+	///
+	void WriteNextHeader(std::ostream &output, const uint8_t* buffer,
+			uint32_t module_size);
+
+	///
+	/// Write the central directory and central directory ending indicator
+	/// to the output stream.
+	///
+	/// \param output stream to write to
+	///
+	void WriteFooter(std::ostream &output);
+};
+
 class BXEXPORT CodFile
 {
 	FILE *m_fp;
diff --git a/src/m_javaloader.cc b/src/m_javaloader.cc
index 490605c..1337eac 100644
--- a/src/m_javaloader.cc
+++ b/src/m_javaloader.cc
@@ -884,7 +884,7 @@ void JavaLoader::ClearEventlog()
 	}
 }
 
-void JavaLoader::SaveData(JLPacket &packet, uint16_t id, std::ostream &output)
+void JavaLoader::SaveData(JLPacket &packet, uint16_t id, CodFileBuilder &builder, std::ostream &output)
 {
 	packet.SaveModule(id);
 	m_socket->Packet(packet);
@@ -896,29 +896,40 @@ void JavaLoader::SaveData(JLPacket &packet, uint16_t id, std::ostream &output)
 	// get total size of cod file or this sibling cod file
 	Data &response = packet.GetReceive();
 	m_socket->Receive(response);
-//	Protocol::CheckSize(response, SB_JLPACKET_HEADER_SIZE + sizeof(uint32_t));
-//	MAKE_JLPACKET(jpack, response);
-//	uint32_t total_size = be_btohl(jpack->u.cod_size); // unused variable
+	Protocol::CheckSize(response, SB_JLPACKET_HEADER_SIZE + sizeof(uint32_t));
+	MAKE_JLPACKET(jpack, response);
+	uint32_t total_size = be_btohl(jpack->u.cod_size);
+
+	// allocate buffer to hold data for this sibling
+	Data buffer(-1, total_size);
+	uint32_t offset = 0;
 
 	for( ;; ) {
 		packet.GetData();
 		m_socket->Packet(packet);
 
 		if( packet.Command() == SB_COMMAND_JL_ACK )
-			return;
+			break;
 
 		if( packet.Command() != SB_COMMAND_JL_GET_DATA_ENTRY ) {
 			ThrowJLError("JavaLoader::SaveData", packet.Command());
 		}
 
-		// expected size of data in resonse packet
+		// expected size of data in response packet
 		unsigned int expect = packet.Size();
 
 		m_socket->Receive(response);
 		Protocol::CheckSize(response, SB_JLPACKET_HEADER_SIZE + expect);
 
-		output.write((const char *)response.GetData() + SB_JLPACKET_HEADER_SIZE, expect);
+		memcpy(buffer.GetBuffer() + offset,
+			response.GetData() + SB_JLPACKET_HEADER_SIZE,
+			expect);
+
+		offset += expect;
 	}
+
+	builder.WriteNextHeader(output, buffer.GetData(), total_size);
+	output.write((const char *)buffer.GetData(), total_size);
 }
 
 void JavaLoader::Save(const std::string &cod_name, std::ostream &output)
@@ -970,10 +981,14 @@ void JavaLoader::Save(const std::string &cod_name, std::ostream &output)
 	const uint16_t *end = begin + count;
 	vector<uint16_t> ids(begin, end);
 
+	CodFileBuilder builder(cod_name, count);
+
 	// save each block of data
 	for( size_t i = 0; i < count; i++ ) {
-		SaveData(packet, be_btohs(ids[i]), output);
+		SaveData(packet, be_btohs(ids[i]), builder, output);
 	}
+
+	builder.WriteFooter(output);
 }
 
 }} // namespace Barry::Mode
diff --git a/src/m_javaloader.h b/src/m_javaloader.h
index 30225bf..25e4837 100644
--- a/src/m_javaloader.h
+++ b/src/m_javaloader.h
@@ -36,6 +36,7 @@ class Parser;
 class Builder;
 class Controller;
 class CodFile;
+class CodFileBuilder;
 
 class JLDirectoryEntry;
 
@@ -193,7 +194,8 @@ protected:
 		bool include_subdirs);
 	void ThrowJLError(const std::string &msg, uint8_t cmd);
 	void DoErase(uint8_t cmd, const std::string &cod_name);
-	void SaveData(JLPacket &packet, uint16_t, std::ostream &output);
+	void SaveData(JLPacket &packet, uint16_t, CodFileBuilder &builder,
+		std::ostream &output);
 
 public:
 	JavaLoader(Controller &con);
-- 
1.6.1.1

