Last active
January 1, 2021 01:50
-
-
Save MCJack123/362c76cefe9550a21466bcc7d9844bb0 to your computer and use it in GitHub Desktop.
Tool to remove extra instruments from XM modules (warning: changes instrument IDs!)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#ifdef _WIN32 | |
#include <windows.h> | |
#include <io.h> | |
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) | |
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) | |
#endif | |
#else | |
#include <dirent.h> | |
#include <errno.h> | |
#endif | |
/* copying functions */ | |
int fteec(FILE * in, FILE * out) { | |
int c = fgetc(in); | |
fputc(c, out); | |
return c; | |
} | |
size_t ftee(void * buf, size_t size, size_t nelem, FILE * in, FILE * out) { | |
size_t c = fread(buf, size, nelem, in); | |
fwrite(buf, size, c, out); | |
return c; | |
} | |
void fcopy(size_t size, FILE * in, FILE * out) { | |
if (size < 8) { | |
/* avoid dynamic allocation if only a few bytes are copied */ | |
int i; | |
for (i = 0; i < size; i++) fputc(fgetc(in), out); | |
} else { | |
void * buf = malloc(size); | |
fwrite(buf, 1, fread(buf, 1, size, in), out); | |
free(buf); | |
} | |
} | |
/* ================= */ | |
int squashXM(const char * infile, const char * outfile) { | |
FILE * in, * out; | |
char modtext[18]; | |
uint32_t header_size = 0, instrumentCountPos, insize, outsize; | |
uint16_t channels = 0, patterns = 0, instruments = 0; | |
uint8_t instrumentConversion[256]; | |
uint8_t * instrumentData[256]; | |
uint32_t instrumentSize[256]; | |
uint8_t nextInstrument = 1; | |
int i, j, k; | |
in = fopen(infile, "rb"); | |
out = fopen(outfile, "wb"); | |
if (in == NULL) { | |
fprintf(stderr, "Could not open input file %s for reading.\n", infile); | |
return 2; | |
} else if (out == NULL) { | |
fprintf(stderr, "Could not open output file %s for reading.\n", outfile); | |
return 3; | |
} | |
modtext[17] = 0; | |
ftee(modtext, 1, 17, in, out); | |
if (strcmp(modtext, "Extended Module: ") != 0) { | |
fclose(in); | |
fclose(out); | |
fprintf(stderr, "Input file is not an XM module.\n"); | |
return 4; | |
} | |
fcopy(43, in, out); | |
ftee(&header_size, 4, 1, in, out); | |
fcopy(4, in, out); | |
ftee(&channels, 2, 1, in, out); | |
ftee(&patterns, 2, 1, in, out); | |
instrumentCountPos = ftell(out); | |
ftee(&instruments, 2, 1, in, out); | |
fcopy(header_size - 14, in, out); | |
memset(instrumentConversion, 0, sizeof(instrumentConversion)); | |
memset(instrumentData, 0, sizeof(instrumentData)); | |
for (i = 0; i < patterns; i++) { | |
uint32_t pheader_size = 0, pattern_start; | |
uint16_t rows = 0, pattern_size = 0; | |
ftee(&pheader_size, 4, 1, in, out); | |
if (fteec(in, out)) { | |
fclose(in); | |
fclose(out); | |
fprintf(stderr, "Input file uses an incompatible packing scheme.\n"); | |
return 4; | |
} | |
ftee(&rows, 2, 1, in, out); | |
ftee(&pattern_size, 2, 1, in, out); | |
if (pheader_size > 9) fcopy(pheader_size - 9, in, out); | |
pattern_start = ftell(in); | |
for (j = 0; j < rows; j++) { | |
for (k = 0; k < channels; k++) { | |
uint8_t follow = fteec(in, out); | |
if (follow & 0x80) { | |
if (follow & 0x01) fcopy(1, in, out); | |
if (follow & 0x02) { | |
uint8_t inst = fgetc(in); | |
uint8_t found = instrumentConversion[inst]; | |
if (found == 0) instrumentConversion[inst] = found = nextInstrument++; | |
fputc(found, out); | |
} | |
if (follow & 0x04) fcopy(1, in, out); | |
if (follow & 0x08) fcopy(1, in, out); | |
if (follow & 0x10) fcopy(1, in, out); | |
} else { | |
uint8_t found; | |
uint8_t inst = fgetc(in); | |
found = instrumentConversion[inst]; | |
if (found == 0) instrumentConversion[inst] = found = nextInstrument++; | |
fputc(found, out); | |
fcopy(3, in, out); | |
} | |
} | |
} | |
if (ftell(in) < pattern_start + pattern_size) fcopy(pattern_size - (ftell(in) - pattern_start), in, out); | |
} | |
for (i = 1; i <= instruments; i++) { | |
uint32_t instrument_size = 0; | |
uint8_t * instrument; | |
uint16_t samples = 0; | |
uint32_t pos = 29; | |
fread(&instrument_size, 4, 1, in); | |
instrument = malloc(instrument_size); | |
((uint32_t*)instrument)[0] = instrument_size; | |
fread(&instrument[4], 1, 23, in); | |
fread(&samples, 2, 1, in); | |
*(uint16_t*)&instrument[27] = samples; | |
if (samples > 0) { | |
uint32_t sample_header_size = 0; | |
fread(&sample_header_size, 4, 1, in); | |
*(uint32_t*)&instrument[29] = sample_header_size; | |
fread(&instrument[33], 1, instrument_size - 33, in); | |
pos = instrument_size; | |
for (j = 0; j < samples; j++) { | |
uint32_t sample_size = 0; | |
fread(&sample_size, 4, 1, in); | |
instrument = realloc(instrument, pos + sample_header_size + sample_size); | |
*(uint32_t*)&instrument[pos] = sample_size; | |
fread(&instrument[pos+4], 1, sample_header_size + sample_size - 4, in); | |
pos += sample_header_size + sample_size; | |
} | |
} else if (instrument_size > 29) { | |
pos = instrument_size; | |
fread(&instrument[29], 1, instrument_size - 29, in); | |
} | |
if (instrumentConversion[i]) { | |
instrumentData[instrumentConversion[i]] = instrument; | |
instrumentSize[instrumentConversion[i]] = pos; | |
} else free(instrument); | |
} | |
for (i = 1; i < nextInstrument; i++) { | |
if (instrumentData[i] != NULL) { /* for safety */ | |
fwrite(instrumentData[i], 1, instrumentSize[i], out); | |
free(instrumentData[i]); | |
} | |
} | |
insize = ftell(in); | |
outsize = ftell(out); | |
fseek(out, instrumentCountPos, SEEK_SET); | |
fputc(nextInstrument - 1, out); | |
fputc(0, out); | |
fclose(in); | |
fclose(out); | |
printf("Removed %d instruments from %s, saving %d bytes (-%.1f%%)\n", instruments - (nextInstrument - 1), infile, insize - outsize, ((double)(insize - outsize) * 100.0) / (double)insize); | |
return 0; | |
} | |
int main(int argc, const char * argv[]) { | |
struct stat st; | |
if (argc < 3) { | |
fprintf(stderr, "Usage: %s <input.xm|indir> <output.xm|outdir>\n", argv[0]); | |
return 1; | |
} | |
if (stat(argv[1], &st) == 0 && S_ISDIR(st.st_mode)) { | |
#ifdef _WIN32 | |
WIN32_FIND_DATA ffd; | |
HANDLE hFind; | |
char newpath[MAX_PATH + 5]; | |
#else | |
struct dirent * dir; | |
DIR * d; | |
#endif | |
if (stat(argv[2], &st) != 0 || !S_ISDIR(st.st_mode)) { | |
fprintf(stderr, "Output directory %s does not exist.\n", argv[2]); | |
return 7; | |
} | |
if (strcmp(argv[1], argv[2]) == 0) { | |
fprintf(stderr, "Input and output directories cannot be the same.\n"); | |
return 8; | |
} | |
#ifdef _WIN32 | |
strcpy(newpath, argv[1]); | |
strcat(newpath, "\\*.xm"); | |
hFind = FindFirstFile(newpath, &ffd); | |
if (hFind == INVALID_HANDLE_VALUE) { | |
fprintf(stderr, "Could not open directory for reading: %d\n", GetLastError()); | |
return GetLastError(); | |
} | |
do { | |
const char * s = strrchr(ffd.cFileName, '.'); | |
if ((ffd.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_DIRECTORY)) == 0 && s != NULL && strcmp(s, ".xm") == 0) { | |
char * from, * to; | |
int val; | |
from = malloc(strlen(argv[1]) + strlen(ffd.cFileName) + 2); | |
to = malloc(strlen(argv[2]) + strlen(ffd.cFileName) + 2); | |
strcpy(from, argv[1]); | |
strcat(from, "\\"); | |
strcat(from, ffd.cFileName); | |
strcpy(to, argv[2]); | |
strcat(to, "\\"); | |
strcat(to, ffd.cFileName); | |
val = squashXM(from, to); | |
free(from); | |
free(to); | |
if (val) { | |
FindClose(hFind); | |
return val; | |
} | |
} | |
} while (FindNextFile(hFind, &ffd)); | |
FindClose(hFind); | |
#else | |
d = opendir(argv[1]); | |
if (d == NULL) { | |
fprintf(stderr, "Could not open directory for reading: %d\n", errno); | |
return errno; | |
} | |
while ((dir = readdir(d)) != NULL) { | |
const char * s = strrchr(dir->d_name, '.'); | |
if ((dir->d_type == DT_REG || dir->d_type == DT_UNKNOWN) && s != NULL && strcmp(s, ".xm") == 0) { | |
char * from, * to; | |
int val; | |
from = malloc(strlen(argv[1]) + strlen(dir->d_name) + 2); | |
to = malloc(strlen(argv[2]) + strlen(dir->d_name) + 2); | |
strcpy(from, argv[1]); | |
strcat(from, "/"); | |
strncat(from, dir->d_name, strlen(dir->d_name)); | |
strcpy(to, argv[2]); | |
strcat(to, "/"); | |
strncat(to, dir->d_name, strlen(dir->d_name)); | |
val = squashXM(from, to); | |
free(from); | |
free(to); | |
if (val) { | |
closedir(d); | |
return val; | |
} | |
} | |
} | |
closedir(d); | |
#endif | |
} else return squashXM(argv[1], argv[2]); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment