// SAPSPLIT v0.1 by VinsCool
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
typedef unsigned char BYTE;
typedef enum
{
FAILURE = -1,
SUCCESSFUL,
INPUT_ERROR,
OUTPUT_ERROR,
ARGUMENT_ERROR,
PARAMETER_ERROR,
HELP_SCREEN,
INVALID_DATA,
NOT_ENOUGH_MEMORY
} TStatusCode;
typedef enum
{
UNDEFINED = -1,
SPLIT,
MERGE,
CONCATENATE,
ANALYSE
} TOptionMode;
typedef struct
{
BYTE* buffer;
int size;
bool isDuplicate;
} TChunkSection;
TOptionMode optionMode = UNDEFINED;
TChunkSection** channelStream = NULL, ** sectionStream = NULL;
int frameCount = 0, streamCount = 0, sectionCount = 0, chunkCount = 0;
BYTE* fileBuffer = NULL;
char* inputName = NULL, * outputName = NULL;
FILE* in = NULL, * out = NULL;
bool isStereo = false;
void FreeMemory();
// Exit the program, an optional code may be provided for handling errors
void Quit(TStatusCode statusCode, const char* argument)
{
// Clear all the allocated memory
FreeMemory();
switch (statusCode)
{
case SUCCESSFUL:
fprintf(stderr, "%s\n\n", argument? argument : "Success!");
break;
case INPUT_ERROR:
fprintf(stderr, "Could not open '%s'\n%s\n\n", argument ? argument : "", argument ?
"No such file or directory" : "Missing argument: [-i] [filename]");
break;
case OUTPUT_ERROR:
fprintf(stderr, "Could not write '%s'\n%s\n\n", argument ? argument : "", argument ?
"No such file or directory" : "Missing argument: [-o] [filename]");
break;
case ARGUMENT_ERROR:
fprintf(stderr, "Invalid argument: '%s'\n\n", argument);
break;
case PARAMETER_ERROR:
fprintf(stderr, "Invalid parameter: '%s'\n\n", argument);
break;
case HELP_SCREEN:
fprintf(stderr, "[SAPSPLIT v0.1 by VinsCool]\n\n"
"Usage: '%s [-argument] [parameter]'\n\n"
"Multiple arguments may be used at once, in no particular order\n"
"Optional parameters may also be required for specific purposes\n\n"
"[-i] [filename] Input file to be processed\n"
"[-o] [filename] Output file(s) to be saved\n"
"[-h] Display this screen and exit the program\n\n", argument);
break;
case INVALID_DATA:
fprintf(stderr, "Invalid data: '%s'\n\n", argument);
break;
case NOT_ENOUGH_MEMORY:
fprintf(stderr, "Error: Could not create new data due to insufficient memory\n\n");
break;
default:
fprintf(stderr, "%s\n\n", argument ?
argument : "Error: Unknown program status, something wrong occured");
}
fprintf(stderr, "The status code of '%i' was returned upon exit\n\n", statusCode);
exit(statusCode);
}
void SplitBuffer(TChunkSection* pChunk, int offset)
{
if (!pChunk)
Quit(FAILURE, NULL);
int frame = 0;
while (offset < frameCount)
{
pChunk->buffer[frame++] = fileBuffer[offset];
offset += streamCount;
}
}
void FillChunk(TChunkSection* pChunkFrom, TChunkSection* pChunkTo, int offset)
{
if (!pChunkFrom || !pChunkTo)
Quit(FAILURE, NULL);
int frame = 0;
while (frame < pChunkTo->size)
{
pChunkTo->buffer[frame++] = pChunkFrom->buffer[offset++];
if (offset > pChunkFrom->size)
{
printf("Warning: Copy from source chunk truncated, maximum size reached\n\n");
break;
}
}
}
bool FindDuplicateChunk(TChunkSection* pChunkFrom, TChunkSection* pChunkTo)
{
if (!pChunkFrom || !pChunkTo)
Quit(FAILURE, NULL);
if (pChunkFrom->size != pChunkTo->size)
return false;
if (!(memcmp(pChunkFrom->buffer, pChunkTo->buffer, pChunkFrom->size)))
return pChunkTo->isDuplicate = true;
return false;
}
void WriteChunk(TChunkSection* pChunk, int channel, int section)
{
if (!pChunk)
Quit(FAILURE, NULL);
// Create the output filename appended with additional infos as an extention
char fileName[1024];
sprintf(fileName, "%s.%X_%02X", outputName, channel, section);
// Open the output file
if (!(out = fopen(fileName, "wb")))
Quit(OUTPUT_ERROR, fileName);
fseek(out, 0, SEEK_SET);
// Write the entire buffer to the file
size_t writeCount = fwrite(pChunk->buffer, 1, pChunk->size, out);
// Close the file once it is written
fclose(out);
out = NULL;
printf("Chunk file '%s' written\n", fileName);
// If there is a mismatch between the bytes written and expected, abort the procedure
if (writeCount != pChunk->size)
Quit(FAILURE, "Fatal error: The number of bytes written did not match the expected count");
}
void DeleteChunk(TChunkSection* pChunk)
{
if (pChunk)
{
free(pChunk->buffer);
free(pChunk);
pChunk = NULL;
}
}
TChunkSection* CreateChunk(int size)
{
TChunkSection* pChunk;
if (!(pChunk = (TChunkSection*)malloc(sizeof(TChunkSection))))
Quit(NOT_ENOUGH_MEMORY, NULL);
if (!(pChunk->buffer = (BYTE*)malloc(size * sizeof(BYTE))))
{
DeleteChunk(pChunk);
Quit(NOT_ENOUGH_MEMORY, NULL);
}
pChunk->size = size;
pChunk->isDuplicate = false;
return pChunk;
}
int GetChunkSize(TChunkSection* pChunk)
{
if (!pChunk)
Quit(FAILURE, NULL);
return pChunk->size;
}
void ProcessArguments(int argc, char** argv)
{
// If the program was executed with no argument, display the help screen by default
if (argc <= 1)
Quit(HELP_SCREEN, argv[0]);
char* option = NULL;
// Parse command line arguments and parameters
for (int i = 1; i < argc; i++)
{
if (argv[i][0] == '-' && argv[i][2] == 0)
{
switch (argv[i][1])
{
case 'c':
sectionCount = atoi(argv[++i]);
continue;
case 'i':
inputName = argv[++i];
continue;
case 'o':
outputName = argv[++i];
continue;
case 's':
isStereo = true;
continue;
case 'm':
option = argv[++i];
if (!strcmp(option, "split"))
optionMode = SPLIT;
else if (!strcmp(option, "merge"))
optionMode = MERGE;
else if (!strcmp(option, "concatenate"))
optionMode = CONCATENATE;
else if (!strcmp(option, "analyse"))
optionMode = ANALYSE;
else
break;
//optionMode = UNDEFINED;
continue;
case 'h':
Quit(HELP_SCREEN, argv[0]);
}
}
// Anything reaching this line is assumed to be an invalid argument
Quit(ARGUMENT_ERROR, argv[i]);
}
// Set the number of channels to process, Mono uses 8+1 by default
streamCount = (9 * (isStereo + 1));
// Set the number of chunks to process, if defined
chunkCount = streamCount * sectionCount;
// Verify if the input and output filenames are valid before continuing further
if (!inputName || strlen(inputName) < 1)
Quit(INPUT_ERROR, inputName);
if (!outputName || strlen(outputName) < 1)
Quit(OUTPUT_ERROR, outputName);
}
void SplitChunks()
{
// Open the input file
if (!(in = fopen(inputName, "rb")))
Quit(INPUT_ERROR, inputName);
fseek(in, 0, SEEK_END);
// Identify the file size, and reject anything not matching a multiple of streamCount
if ((frameCount = ftell(in)) % streamCount)
Quit(INVALID_DATA, inputName);
fseek(in, 0, SEEK_SET);
// Allocate the file buffer memory using the framecount for reference
if (!(fileBuffer = (BYTE*)malloc(frameCount * sizeof(BYTE))))
Quit(NOT_ENOUGH_MEMORY, NULL);
// Load the entire file into the buffer
size_t readCount = fread(fileBuffer, sizeof(BYTE), frameCount, in);
// Close the file once it is read
fclose(in);
in = NULL;
// If there is a mismatch between the bytes read and identified, abort the procedure
if (readCount != frameCount)
Quit(FAILURE, "Fatal error: The number of bytes read did not match the expected count");
// Allocate the channel stream memory using the streamcount for reference
if (!(channelStream = (TChunkSection**)calloc(streamCount, sizeof(TChunkSection*))))
Quit(NOT_ENOUGH_MEMORY, NULL);
// Split each channel to individual byte stream
for (int i = 0; i < streamCount; i++)
SplitBuffer(channelStream[i] = CreateChunk(frameCount / streamCount), i);
}
void ProcessChunks()
{
switch (optionMode)
{
case SPLIT:
// Create new chunks and save them as new files
if (sectionCount > 1 && chunkCount > 0)
{
// Allocate the section stream memory using the chunk count for reference
if (!(sectionStream = (TChunkSection**)calloc(chunkCount, sizeof(TChunkSection*))))
Quit(NOT_ENOUGH_MEMORY, NULL);
int count = 0;
for (int i = 0; i < streamCount; i++)
{
int chunkSize = GetChunkSize(channelStream[i]);
int sectionSize = chunkSize / sectionCount;
int offset = 0;
if (!chunkSize)
Quit(FAILURE, "Fatal Error: A size of 0 was returned");
while (offset < chunkSize && count < chunkCount)
{
FillChunk(channelStream[i], sectionStream[count++] = CreateChunk(sectionSize), offset);
offset += sectionSize;
}
}
//bool FindDuplicateChunk(TChunkSection* pChunkFrom, TChunkSection* pChunkTo)
printf("Chunk count: %i\n\n", count);
for (int k = 0; k < chunkCount; k++)
{
count = 0;
for (int i = 0; i < streamCount; i++)
{
for (int j = 0; j < sectionCount; j++)
{
if (k != count && FindDuplicateChunk(sectionStream[k], sectionStream[count]))
{
printf("Chunk %i is a duplicate of chunk %i, used in Channel %i, section %i\n",
k, count, i, j);
}
count++;
}
}
}
count = 0;
for (int i = 0; i < streamCount; i++)
{
for (int j = 0; j < sectionCount; j++)
{
if (count > chunkCount)
break;
WriteChunk(sectionStream[count++], i, j);
}
}
}
// Save data from channel streams directly
else
{
for (int i = 0; i < streamCount; i++)
WriteChunk(channelStream[i], i, 0);
}
break;
case ANALYSE:
// Analyse the framecount to find the optimal section size per channel stream
for (int i = 0; i < streamCount; i++)
{
int count = 0;
int size = GetChunkSize(channelStream[i]);
if (!size)
Quit(FAILURE, "Fatal Error: A size of 0 was returned");
printf("Integral divisions for Channel %i:\n", i);
for (int j = 1; j < 256; j++)
{
if (size % j)
continue;
printf("%i (%i bytes)\n", j, size / j);
count++;
}
printf("For a total of %i possible numbers\n\n", count);
}
break;
default:
Quit(FAILURE, "Missing argument: [-m] [option]");
}
}
void FreeMemory()
{
// We no longer need the file buffer at this point
if (fileBuffer)
{
free(fileBuffer);
fileBuffer = NULL;
}
// We no longer need the channel buffer either
if (channelStream)
{
// Delete the byte streams once they are no longer needed
for (int i = 0; i < streamCount; i++)
DeleteChunk(channelStream[i]);
free(channelStream);
channelStream = NULL;
}
// We no longer need the section buffer either
if (sectionStream)
{
// Delete the byte streams once they are no longer needed
for (int i = 0; i < chunkCount; i++)
DeleteChunk(sectionStream[i]);
free(sectionStream);
sectionStream = NULL;
}
// Close files if they are still open for some reason
if (in)
{
fclose(in);
in = NULL;
}
if (out)
{
fclose(out);
out = NULL;
}
}
int main(int argc, char** argv)
{
// Just so things look less cramped right off the bat
printf("\n");
// Parse the command line arguments
ProcessArguments(argc, argv);
// Load the input file and split the buffers into individual channel streams
SplitChunks();
// Run the procedure using all the necessary parameters
ProcessChunks();
// Finished without error
Quit(SUCCESSFUL, NULL);
}