Allow specifying sectors per run

This commit is contained in:
Dave Vasilevsky 2024-11-06 18:47:33 -05:00
Родитель a928dd4daa
Коммит d1d652c56d
8 изменённых файлов: 91 добавлений и 58 удалений

Просмотреть файл

@ -5,10 +5,6 @@
#include <bzlib.h>
#include "dmg/adc.h"
// LZMA and LZFSE seem to need a larger decompression buffer for macOS to be happy. This value is empirically taken from
// an LZMA dmg created on macOS 14 with hdiutil.
#define MODERN_DECOMPRESS_BUFFER_REQUESTED 0x82F
#ifdef HAVE_LIBLZMA
#include <lzma.h>
@ -75,28 +71,45 @@ static int zlibCompress(unsigned char *inBuffer, size_t inSize,
return (compress2(outBuffer, compSize, inBuffer, inSize, level) != Z_OK);
}
size_t oldDecompressBuffer(size_t runSectors)
{
// A reasonable heuristic
// Bzip2 at level 1 usually needs the largest extra space, compared to other compressors.
// Happens to equal 0x208 for 0x200 sectors, same as before.
return runSectors + 4 + (runSectors >> 7);
}
size_t modernDecompressBuffer(size_t runSectors)
{
// Modern algorithms need more space for some reason, about double the size of a run.
// Sometimes it's a bit more, so add a generous amount of padding.
// It's unclear why so much is needed, lzma/lzfse shouldn't need this much in normal usage!
return runSectors * 2 + 64;
}
int getCompressor(Compressor* comp, char *name)
{
if (name == NULL) {
comp->level = -1;
comp->minDecompressBufferRequested = 0;
}
if (name == NULL || strcasecmp(name, "bzip2") == 0) {
comp->block_type = BLOCK_BZIP2;
comp->compress = bz2Compress;
comp->decompressBuffer = oldDecompressBuffer;
return 0;
}
if (strcasecmp(name, "zlib") == 0) {
comp->block_type = BLOCK_ZLIB;
comp->compress = zlibCompress;
comp->decompressBuffer = oldDecompressBuffer;
return 0;
}
#ifdef HAVE_LIBLZMA
if (strcasecmp(name, "lzma") == 0) {
comp->block_type = BLOCK_LZMA;
comp->compress = lzmaCompress;
comp->minDecompressBufferRequested = MODERN_DECOMPRESS_BUFFER_REQUESTED;
comp->decompressBuffer = modernDecompressBuffer;
return 0;
}
#endif
@ -104,7 +117,7 @@ int getCompressor(Compressor* comp, char *name)
if (strcasecmp(name, "lzfse") == 0) {
comp->block_type = BLOCK_LZFSE;
comp->compress = lzfseCompress;
comp->minDecompressBufferRequested = MODERN_DECOMPRESS_BUFFER_REQUESTED;
comp->decompressBuffer = modernDecompressBuffer;
return 0;
}
#endif
@ -158,3 +171,4 @@ int decompressRun(uint32_t type,
}
return ret;
}

Просмотреть файл

@ -38,6 +38,7 @@ void usage(const char *name) {
printf("\t-k\tkey\n");
printf("\t-J\tcompressor name (%s)\n", compressionNames());
printf("\t-L\tcompression level\n");
printf("\t-r\trun size (in sectors)\n");
exit(2);
}
@ -93,11 +94,12 @@ int main(int argc, char* argv[]) {
char *key = NULL;
Compressor comp;
int ret;
int runSectors = DEFAULT_SECTORS_AT_A_TIME;
TestByteOrder();
getCompressor(&comp, NULL);
while ((opt = mgetopt(argc, argv, "kJL")) != -1) {
while ((opt = mgetopt(argc, argv, "kJLr")) != -1) {
switch (opt) {
case 'k':
key = moptarg;
@ -112,6 +114,13 @@ int main(int argc, char* argv[]) {
case 'L':
sscanf(moptarg, "%d", &comp.level);
break;
case 'r':
sscanf(moptarg, "%d", &runSectors);
if (runSectors < DEFAULT_SECTORS_AT_A_TIME) {
fprintf(stderr, "Run size must be at least %d sectors\n", DEFAULT_SECTORS_AT_A_TIME);
return 2;
}
break;
}
}
@ -140,15 +149,15 @@ int main(int argc, char* argv[]) {
if (moptind < argc) {
anchor = argv[moptind++];
}
buildDmg(in, out, SECTOR_SIZE, anchor, &comp);
buildDmg(in, out, SECTOR_SIZE, anchor, &comp, runSectors);
} else if(strcmp(cmd, "build2048") == 0) {
buildDmg(in, out, 2048, NULL, &comp);
buildDmg(in, out, 2048, NULL, &comp, runSectors);
} else if(strcmp(cmd, "res") == 0) {
outResources(in, out);
} else if(strcmp(cmd, "iso") == 0) {
convertToISO(in, out);
} else if(strcmp(cmd, "dmg") == 0) {
convertToDMG(in, out, &comp);
convertToDMG(in, out, &comp, runSectors);
} else if(strcmp(cmd, "attribute") == 0) {
char *anchor, *data;
if(argc < moptind + 2) {

Просмотреть файл

@ -88,7 +88,7 @@ uint32_t calculateMasterChecksum(ResourceKey* resources) {
return result;
}
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize, const char* sentinel, Compressor *comp) {
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize, const char* sentinel, Compressor *comp, size_t runSectors) {
io_func* io;
Volume* volume;
@ -136,11 +136,11 @@ int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int B
partitions = createApplePartitionMap((volumeHeader->totalBlocks * volumeHeader->blockSize)/SECTOR_SIZE, HFSX_VOLUME_TYPE, BlockSize);
int pNum = writeDriverDescriptorMap(-1, abstractOut, DDM, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, comp);
int pNum = writeDriverDescriptorMap(-1, abstractOut, DDM, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, comp, runSectors);
free(DDM);
pNum = writeApplePartitionMap(pNum, abstractOut, partitions, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, &nsiz, comp);
pNum = writeApplePartitionMap(pNum, abstractOut, partitions, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, &nsiz, comp, runSectors);
free(partitions);
pNum = writeATAPI(pNum, abstractOut, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, &nsiz, comp);
pNum = writeATAPI(pNum, abstractOut, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, &nsiz, comp, runSectors);
memset(&uncompressedToken, 0, sizeof(uncompressedToken));
SHA1Init(&(uncompressedToken.sha1));
@ -159,7 +159,7 @@ int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int B
}
blkx = insertBLKX(abstractOut, abstractIn, USER_OFFSET, (volumeHeader->totalBlocks * volumeHeader->blockSize)/SECTOR_SIZE,
pNum, CHECKSUM_UDIF_CRC32, &BlockSHA1CRC, &uncompressedToken, &CRCProxy, &dataForkToken, volume, attribution, comp);
pNum, CHECKSUM_UDIF_CRC32, &BlockSHA1CRC, &uncompressedToken, &CRCProxy, &dataForkToken, volume, attribution, comp, runSectors);
AttributionResource attributionResource;
memset(&attributionResource, 0, sizeof(AttributionResource));
@ -291,7 +291,7 @@ int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int B
return TRUE;
}
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor *comp) {
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor *comp, size_t runSectors) {
Partition* partitions;
DriverDescriptorRecord* DDM;
int i;
@ -340,7 +340,7 @@ int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor
if(DDM->sbSig == DRIVER_DESCRIPTOR_SIGNATURE) {
BlockSize = DDM->sbBlkSize;
int pNum = writeDriverDescriptorMap(-1, abstractOut, DDM, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, comp);
int pNum = writeDriverDescriptorMap(-1, abstractOut, DDM, BlockSize, &CRCProxy, (void*) (&dataForkToken), &resources, comp, runSectors);
free(DDM);
printf("Processing partition map...\n"); fflush(stdout);
@ -371,7 +371,7 @@ int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor
abstractIn->seek(abstractIn, partitions[i].pmPyPartStart * BlockSize);
blkx = insertBLKX(abstractOut, abstractIn, partitions[i].pmPyPartStart, partitions[i].pmPartBlkCnt, i, CHECKSUM_UDIF_CRC32,
&BlockCRC, &uncompressedToken, &CRCProxy, &dataForkToken, NULL, NULL, comp);
&BlockCRC, &uncompressedToken, &CRCProxy, &dataForkToken, NULL, NULL, comp, runSectors);
blkx->checksum.data[0] = uncompressedToken.crc;
resources = insertData(resources, "blkx", i, partitionName, 0, false, (const char*) blkx, sizeof(BLKXTable) + (blkx->blocksRunCount * sizeof(BLKXRun)), ATTRIBUTE_HDIUTIL);
@ -412,7 +412,7 @@ int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor
abstractIn->seek(abstractIn, 0);
blkx = insertBLKX(abstractOut, abstractIn, 0, fileLength/SECTOR_SIZE, ENTIRE_DEVICE_DESCRIPTOR, CHECKSUM_UDIF_CRC32,
&BlockCRC, &uncompressedToken, &CRCProxy, &dataForkToken, NULL, NULL, comp);
&BlockCRC, &uncompressedToken, &CRCProxy, &dataForkToken, NULL, NULL, comp, runSectors);
blkx->checksum.data[0] = uncompressedToken.crc;
resources = insertData(resources, "blkx", 0, "whole disk (unknown partition : 0)", 0, false, (const char*) blkx, sizeof(BLKXTable) + (blkx->blocksRunCount * sizeof(BLKXRun)), ATTRIBUTE_HDIUTIL);
free(blkx);

Просмотреть файл

@ -9,23 +9,6 @@
#include <dmg/attribution.h>
#include <inttypes.h>
// Okay, this value sucks. You shouldn't touch it because it affects how many ignore sections get added to the blkx list
// If the blkx list gets too fragmented with ignore sections, then the copy list in certain versions of the iPhone's
// asr becomes too big. Due to Apple's BUGGY CODE, this causes asr to segfault! This is because the copy list becomes
// too large for the initial buffer allocated, and realloc is called by asr. Unfortunately, after the realloc, the initial
// pointer is still used by asr for a little while! Frakking noob mistake.
// The only reason why it works at all is their really idiotic algorithm to determine where to put ignore blocks. It's
// certainly nothing reasonable like "put in an ignore block if you encounter more than X blank sectors" (like mine)
// There's always a large-ish one at the end, and a tiny 2 sector one at the end too, to take care of the space after
// the backup volume header. No frakking clue how they go about determining how to do that.
#define SECTORS_AT_A_TIME 0x200
// The base decompression buffer requested, if nothing else is specified.
#define DECOMPRESS_BUFFER_REQUESTED 0x208
typedef struct block {
size_t bufferSize;
@ -43,6 +26,7 @@ typedef struct block {
} block;
typedef struct {
size_t runSectors;
size_t bufferSize;
AbstractAttribution* attribution;
@ -103,7 +87,7 @@ static block* blockRead(threadData* d) {
block* b = blockAlloc(d->bufferSize, d->curRun);
b->run.sectorStart = d->curSector;
b->run.sectorCount = (d->numSectors > SECTORS_AT_A_TIME) ? SECTORS_AT_A_TIME : d->numSectors;
b->run.sectorCount = (d->numSectors > d->runSectors) ? d->runSectors : d->numSectors;
size_t readSize = b->run.sectorCount * SECTOR_SIZE;
if (b->idx == 0) {
@ -229,10 +213,11 @@ static void *threadWorker(void* arg) {
BLKXTable* insertBLKX(AbstractFile* out_, AbstractFile* in_, uint32_t firstSectorNumber, uint32_t numSectors_, uint32_t blocksDescriptor,
uint32_t checksumType, ChecksumFunc uncompressedChk_, void* uncompressedChkToken_, ChecksumFunc compressedChk_,
void* compressedChkToken_, Volume* volume, AbstractAttribution* attribution_, Compressor* comp) {
void* compressedChkToken_, Volume* volume, AbstractAttribution* attribution_, Compressor* comp, size_t runSectors) {
threadData td = {
.out = out_,
.in = in_,
.runSectors = runSectors,
.numSectors = numSectors_,
.uncompressedChk = uncompressedChk_,
.uncompressedChkToken = uncompressedChkToken_,
@ -256,10 +241,12 @@ BLKXTable* insertBLKX(AbstractFile* out_, AbstractFile* in_, uint32_t firstSecto
td.blkx->firstSectorNumber = firstSectorNumber;
td.blkx->sectorCount = td.numSectors;
td.blkx->dataStart = 0;
td.blkx->decompressBufferRequested = DECOMPRESS_BUFFER_REQUESTED;
if (comp->minDecompressBufferRequested > td.blkx->decompressBufferRequested) {
td.blkx->decompressBufferRequested = comp->minDecompressBufferRequested;
td.blkx->decompressBufferRequested = comp->decompressBuffer(runSectors);
if (MIN_DECOMPRESS_BUFFER_SECTORS > td.blkx->decompressBufferRequested) {
td.blkx->decompressBufferRequested = MIN_DECOMPRESS_BUFFER_SECTORS;
}
td.blkx->blocksDescriptor = blocksDescriptor;
td.blkx->reserved1 = 0;
td.blkx->reserved2 = 0;

Просмотреть файл

@ -499,7 +499,7 @@ DriverDescriptorRecord* createDriverDescriptorMap(uint32_t numSectors, unsigned
}
int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecord* DDM, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken,
ResourceKey **resources, Compressor *comp) {
ResourceKey **resources, Compressor *comp, size_t runSectors) {
AbstractFile* bufferFile;
BLKXTable* blkx;
ChecksumToken uncompressedToken;
@ -514,7 +514,7 @@ int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecor
bufferFile = createAbstractFileFromMemory((void**)&buffer, DDM_SIZE * BlockSize);
blkx = insertBLKX(file, bufferFile, DDM_OFFSET, DDM_SIZE, DDM_DESCRIPTOR, CHECKSUM_UDIF_CRC32, &CRCProxy, &uncompressedToken,
dataForkChecksum, dataForkToken, NULL, NULL, comp);
dataForkChecksum, dataForkToken, NULL, NULL, comp, runSectors);
blkx->checksum.data[0] = uncompressedToken.crc;
@ -534,7 +534,8 @@ int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecor
return pNum;
}
int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn, Compressor *comp) {
int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn,
Compressor *comp, size_t runSectors) {
AbstractFile* bufferFile;
BLKXTable* blkx;
ChecksumToken uncompressedToken;
@ -552,7 +553,7 @@ int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions,
bufferFile = createAbstractFileFromMemory((void**)&buffer, realPartitionSize);
blkx = insertBLKX(file, bufferFile, PARTITION_OFFSET * BlockSize / SECTOR_SIZE, realPartitionSize / SECTOR_SIZE, pNum, CHECKSUM_UDIF_CRC32,
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp);
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp, runSectors);
bufferFile->close(bufferFile);
@ -588,7 +589,8 @@ int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions,
return pNum + 1;
}
int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn, Compressor *comp) {
int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn,
Compressor *comp, size_t runSectors) {
AbstractFile* bufferFile;
BLKXTable* blkx;
ChecksumToken uncompressedToken;
@ -606,12 +608,12 @@ int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFun
if(BlockSize != SECTOR_SIZE)
{
blkx = insertBLKX(file, bufferFile, ATAPI_OFFSET, BlockSize / SECTOR_SIZE, pNum, CHECKSUM_UDIF_CRC32,
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp);
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp, runSectors);
}
else
{
blkx = insertBLKX(file, bufferFile, ATAPI_OFFSET, ATAPI_SIZE, pNum, CHECKSUM_UDIF_CRC32,
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp);
&BlockCRC, &uncompressedToken, dataForkChecksum, dataForkToken, NULL, NULL, comp, runSectors);
}
bufferFile->close(bufferFile);

Просмотреть файл

@ -7,16 +7,20 @@
extern "C" {
#endif
#define MIN_DECOMPRESS_BUFFER_SECTORS 0x208
// Return zero on success
typedef int (*CompressFunc)(unsigned char *inBuffer, size_t inSize,
unsigned char *outBuffer, size_t outBufSize, size_t *compSize,
int level);
typedef size_t (*DecompressBufferFunc)(size_t runSectors);
typedef struct {
CompressFunc compress;
DecompressBufferFunc decompressBuffer;
int level;
uint32_t block_type;
uint32_t minDecompressBufferRequested;
} Compressor;
// Pass NULL name to get the default. Return zero on success

Просмотреть файл

@ -11,6 +11,19 @@
#include "common.h"
#include "compress.h"
// Okay, this value sucks. You shouldn't touch it because it affects how many ignore sections get added to the blkx list
// If the blkx list gets too fragmented with ignore sections, then the copy list in certain versions of the iPhone's
// asr becomes too big. Due to Apple's BUGGY CODE, this causes asr to segfault! This is because the copy list becomes
// too large for the initial buffer allocated, and realloc is called by asr. Unfortunately, after the realloc, the initial
// pointer is still used by asr for a little while! Frakking noob mistake.
// The only reason why it works at all is their really idiotic algorithm to determine where to put ignore blocks. It's
// certainly nothing reasonable like "put in an ignore block if you encounter more than X blank sectors" (like mine)
// There's always a large-ish one at the end, and a tiny 2 sector one at the end too, to take care of the space after
// the backup volume header. No frakking clue how they go about determining how to do that.
#define DEFAULT_SECTORS_AT_A_TIME 0x200
#define CHECKSUM_UDIF_CRC32 0x00000002
#define CHECKSUM_MD5 0x00000004
#define CHECKSUM_MKBLOCK 0x0002
@ -321,23 +334,26 @@ extern "C" {
void readDriverDescriptorMap(AbstractFile* file, ResourceKey* resources);
DriverDescriptorRecord* createDriverDescriptorMap(uint32_t numSectors, unsigned int BlockSize);
int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecord* DDM, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, Compressor *comp);
int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecord* DDM, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources,
Compressor *comp, size_t runSectors);
void readApplePartitionMap(AbstractFile* file, ResourceKey* resources, unsigned int BlockSize);
Partition* createApplePartitionMap(uint32_t numSectors, const char* volumeType, unsigned int BlockSize);
int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn, Compressor *comp);
int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn, Compressor *comp);
int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn,
Compressor *comp, size_t runSectors);
int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn,
Compressor *comp, size_t runSectors);
int writeFreePartition(int pNum, AbstractFile* outFile, uint32_t offset, uint32_t numSectors, ResourceKey** resources);
void extractBLKX(AbstractFile* in, AbstractFile* out, BLKXTable* blkx);
BLKXTable* insertBLKX(AbstractFile* out, AbstractFile* in, uint32_t firstSectorNumber, uint32_t numSectors, uint32_t blocksDescriptor,
uint32_t checksumType, ChecksumFunc uncompressedChk, void* uncompressedChkToken, ChecksumFunc compressedChk,
void* compressedChkToken, Volume* volume, AbstractAttribution* attribution, Compressor* comp);
void* compressedChkToken, Volume* volume, AbstractAttribution* attribution, Compressor* comp, size_t runSectors);
int extractDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, int partNum);
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize, const char* sentinel, Compressor *comp);
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize, const char* sentinel, Compressor *comp, size_t runSectors);
int convertToISO(AbstractFile* abstractIn, AbstractFile* abstractOut);
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor* comp);
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor* comp, size_t runSectors);
#ifdef __cplusplus
}
#endif

Просмотреть файл

@ -3,14 +3,15 @@
#include <dmg/dmg.h>
#include "abstractfile.h"
#include "compress.h"
#ifdef __cplusplus
extern "C" {
#endif
int extractDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, int partNum);
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize);
int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize, Compressor *comp, size_t runSectors);
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut);
int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut, Compressor *comp, size_t runSectors);
int convertToISO(AbstractFile* abstractIn, AbstractFile* abstractOut);
#ifdef __cplusplus
}