Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active January 1, 2021 01:50
Show Gist options
  • Save MCJack123/362c76cefe9550a21466bcc7d9844bb0 to your computer and use it in GitHub Desktop.
Save MCJack123/362c76cefe9550a21466bcc7d9844bb0 to your computer and use it in GitHub Desktop.
Tool to remove extra instruments from XM modules (warning: changes instrument IDs!)
#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