From c4916e3fd90b5ba10768ae186517c7ef28e16e4e Mon Sep 17 00:00:00 2001 From: JackMacWindows Date: Sat, 25 Jul 2020 02:50:39 -0400 Subject: [PATCH] Version 1.0 --- unkrawerter.cpp | 296 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 235 insertions(+), 61 deletions(-) diff --git a/unkrawerter.cpp b/unkrawerter.cpp index f712f19..6a83bf8 100644 --- a/unkrawerter.cpp +++ b/unkrawerter.cpp @@ -1,6 +1,6 @@ /* * UnkrawerterGBA - * Version 0.9 + * Version 1.0 * * This program automatically extracts music files from Gameboy Advance games * that use the Krawall sound engine. Audio files are extracted in the XM module @@ -39,9 +39,7 @@ #include #include -bool dwordAlignment = true; // maybe change this if offsets aren't 4-byte aligned? - // I haven't found any blocks that aren't, but who knows, maybe there are ROMs that aren't? - +// Maps type numbers detected in searchForOffsets to strings for display (only used in verbose mode) const char * typemap[] = { "unknown", "module", @@ -53,6 +51,7 @@ const char * typemap[] = { "any" }; +// Structure to hold results of offset search struct OffsetSearchResult { bool success = false; uint32_t instrumentAddr = 0; @@ -62,42 +61,46 @@ struct OffsetSearchResult { std::vector modules; }; +// Searches a ROM file pointer for offsets to modules, an instrument list, and a sample list. +// This looks for sets of 4-byte aligned addresses in the form 0x08xxxxxx or 0x09xxxxxx +// Once the sets are found, their types are determined by dereferencing the addresses and checking +// whether the data stored therein is consistent with the structure type. +// Sets that don't match exactly one type are discarded. +// Returns a structure with the addresses to the instrument & sample lists, as well as all modules. OffsetSearchResult searchForOffsets(FILE* fp, int threshold = 4, bool verbose = false) { OffsetSearchResult retval; fseek(fp, 0, SEEK_END); - uint32_t romSize = ftell(fp); + uint32_t romSize = ftell(fp); // Store the ROM's size so addresses that go over are ignored rewind(fp); std::vector > foundAddressLists; uint32_t startAddress = 0, count = 0; - if (dwordAlignment) { - // Look for lists of pointers (starting with 0x08xxxxxx) - uint32_t lastDword = 0; - while (!feof(fp) && !ferror(fp)) { - fread(&lastDword, 4, 1, fp); - if ((lastDword & 0x08000000) && !(lastDword & 0xF6000000) && (lastDword & 0x1ffffff) < romSize && lastDword != 0x08080808 && !((uint16_t)(lastDword >> 16) - (uint16_t)(lastDword & 0xffff) < 4 && (lastDword & 0x00ff00ff) == 0x00080008)) { - if (startAddress == 0 || count == 0) startAddress = ftell(fp) - 4; - count++; - } else if (count >= threshold && count < 1024) { - foundAddressLists.push_back(std::make_tuple(startAddress, count, 0)); - startAddress = 0; - count = 0; - } else if (count > 0) { - startAddress = count = 0; - } + // Look for lists of pointers (starting with 0x08xxxxxx or 0x09xxxxxx) + uint32_t lastDword = 0; + while (!feof(fp) && !ferror(fp)) { + fread(&lastDword, 4, 1, fp); + if ((lastDword & 0x08000000) && !(lastDword & 0xF6000000) && (lastDword & 0x1ffffff) < romSize && lastDword != 0x08080808 && !((uint16_t)(lastDword >> 16) - (uint16_t)(lastDword & 0xffff) < 4 && (lastDword & 0x00ff00ff) == 0x00080008)) { + // Count this address in a set + if (startAddress == 0 || count == 0) startAddress = ftell(fp) - 4; + count++; + } else if (count >= threshold && count < 1024) { + // We found an address list, add it to the results + foundAddressLists.push_back(std::make_tuple(startAddress, count, 0)); + startAddress = 0; + count = 0; + } else if (count > 0) { + // Ignore this address (list) + startAddress = count = 0; } - } else { - fprintf(stderr, "Unimplemented\n"); - return retval; } // Erase a few matches foundAddressLists.erase(std::remove_if(foundAddressLists.begin(), foundAddressLists.end(), [fp](std::tuple& addr)->bool { - // Check for consecutive addresses + // Check for addresses that are too close together int numsize = std::min(std::get<1>(addr), 4u); uint32_t nums[4]; fseek(fp, std::get<0>(addr), SEEK_SET); for (int i = 0; i < numsize; i++) fread(nums + i, 4, 1, fp); - for (int i = 1; i < numsize; i++) if ((int32_t)nums[i] - (int32_t)nums[i-1] < 0x10) return true; // Spacing too close + for (int i = 1; i < numsize; i++) if ((int32_t)nums[i] - (int32_t)nums[i-1] < 0x10) return true; return false; }), foundAddressLists.end()); @@ -147,7 +150,7 @@ OffsetSearchResult searchForOffsets(FILE* fp, int threshold = 4, bool verbose = uint16_t tmp = 0, last = 0; for (int i = 0; i < 96; i++) { fread(&tmp, 2, 1, fp); - if (tmp > 256 || (i > 0 && abs((int32_t)tmp - (int32_t)last) > 16)) {possible_mask &= 0b011; break;} + if ((tmp > 256 || (i > 0 && abs((int32_t)tmp - (int32_t)last) > 16)) && i < 94) {possible_mask &= 0b011; break;} last = tmp; } if (!(possible_mask & 4)) break; @@ -175,6 +178,7 @@ OffsetSearchResult searchForOffsets(FILE* fp, int threshold = 4, bool verbose = else if (std::get<2>(p) == 4 && std::get<1>(p) > retval.instrumentCount) {retval.instrumentCount = std::get<1>(p); retval.instrumentAddr = std::get<0>(p);} } + // Show brief of results if (retval.instrumentAddr) printf("> Found instrument list at address %08X\n", retval.instrumentAddr); if (retval.sampleAddr) printf("> Found sample list at address %08X\n", retval.sampleAddr); for (int i = 0; i < retval.modules.size(); i++) { @@ -214,6 +218,7 @@ void readSampleToWAV(FILE* fp, uint32_t offset, const char * filename) { fclose(wav); } +// Taken from Krawall's mtypes.h file extern "C" { #ifdef _MSC_VER #pragma pack(push, 1) @@ -299,11 +304,13 @@ extern "C" { #endif } +// Read a pattern from a file pointer to a Pattern structure pointer Pattern * readPatternFile(FILE* fp, uint32_t offset) { fseek(fp, offset + 32, SEEK_SET); std::vector fileContents; unsigned short rows = 0; fread(&rows, 2, 1, fp); + // We don't need to do full decoding; decode just enough to understand the size of the pattern for (int row = 0; row < rows; row++) { for (;;) { unsigned char follow = fgetc(fp); @@ -333,6 +340,8 @@ Pattern * readPatternFile(FILE* fp, uint32_t offset) { return retval; } +// Read a module from a file pointer to a Module structure pointer +// This reads all its patterns as well Module * readModuleFile(FILE* fp, uint32_t offset) { Module * retval = (Module*)malloc(sizeof(Module)); memset(retval, 0, sizeof(Module)); @@ -353,6 +362,7 @@ Module * readModuleFile(FILE* fp, uint32_t offset) { return retval2; } +// Read an instrument from a file pointer to an Instrument structure Instrument readInstrumentFile(FILE* fp, uint32_t offset) { fseek(fp, offset, SEEK_SET); Instrument retval; @@ -360,6 +370,7 @@ Instrument readInstrumentFile(FILE* fp, uint32_t offset) { return retval; } +// Read a sample from a file pointer to a Sample structure pointer Sample * readSampleFile(FILE* fp, uint32_t offset) { fseek(fp, offset + 4, SEEK_SET); uint32_t size = 0; @@ -374,80 +385,200 @@ Sample * readSampleFile(FILE* fp, uint32_t offset) { return retval; } +// Stores note data while converting typedef struct { unsigned char xmflag; unsigned char note, volume, effect, effectop; unsigned short instrument; } Note; +// Quick function to repeatedly put a character inline void fputcn(int c, int num, FILE* fp) {for (; num > 0; num--) fputc(c, fp);} +// Effect map to convert Krawall effects to XM effects +// (effect . effectop) = first | (effectop & second) +// If first == 0xFFFF: ignore +// Some effects must be converted from S3M syntax to XM syntax. +// Some effects are only supported in S3M files, and are not converted. +// Some effects are only supported in MPT/OpenMPT, and may not play properly on other trackers. +const std::pair effectMap[] = { + {0xFFFF, 0xFF}, + {0x0F00, 0xFF}, // EFF_SPEED + {0x0F00, 0xFF}, // EFF_BPM + {0x0F00, 0xFF}, // EFF_SPEEDBPM + {0x0B00, 0xFF}, // EFF_PATTERN_JUMP + {0x0D00, 0xFF}, // EFF_PATTERN_BREAK 5 + {0x0A00, 0xFF}, // EFF_VOLSLIDE_S3M (S3M!) + {0x0A00, 0xFF}, // EFF_VOLSLIDE_XM + {0x0EB0, 0x0F}, // EFF_VOLSLIDE_DOWN_XM_FINE + {0x0EA0, 0x0F}, // EFF_VOLSLIDE_UP_XM_FINE + {0x0200, 0xFF}, // EFF_PORTA_DOWN_XM 10 + {0x0200, 0xFF}, // EFF_PORTA_DOWN_S3M (S3M!) + {0x0E20, 0x0F}, // EFF_PORTA_DOWN_XM_FINE + {0x2120, 0x0F}, // EFF_PORTA_DOWN_XM_EFINE + {0x0100, 0xFF}, // EFF_PORTA_UP_XM + {0x0100, 0xFF}, // EFF_PORTA_UP_S3M 15 (S3M!) + {0x0E10, 0x0F}, // EFF_PORTA_UP_XM_FINE + {0x2110, 0x0F}, // EFF_PORTA_UP_XM_EFINE + {0x0C00, 0xFF}, // EFF_VOLUME + {0x0300, 0xFF}, // EFF_PORTA_NOTE + {0x0400, 0xFF}, // EFF_VIBRATO 20 + {0x1D00, 0xFF}, // EFF_TREMOR + {0x0000, 0xFF}, // EFF_ARPEGGIO + {0x0600, 0xFF}, // EFF_VOLSLIDE_VIBRATO + {0x0500, 0xFF}, // EFF_VOLSLIDE_PORTA + {0xFFFF, 0xFF}, // EFF_CHANNEL_VOL 25 (S3M!) + {0xFFFF, 0xFF}, // EFF_CHANNEL_VOLSLIDE (S3M!) + {0x0900, 0xFF}, // EFF_OFFSET + {0x1900, 0xFF}, // EFF_PANSLIDE + {0x0E90, 0x0F}, // EFF_RETRIG + {0x0700, 0xFF}, // EFF_TREMOLO 30 + {0xFFFF, 0xFF}, // EFF_FVIBRATO (S3M!) + {0x1000, 0xFF}, // EFF_GLOBAL_VOL + {0x1100, 0xFF}, // EFF_GLOBAL_VOLSLIDE + {0x0800, 0xFF}, // EFF_PAN + {0x2300, 0xFF}, // EFF_PANBRELLO 35 (MPT!) + {0xFFFF, 0xFF}, // EFF_MARK + {0x0E30, 0x0F}, // EFF_GLISSANDO + {0x0E40, 0x0F}, // EFF_WAVE_VIBR + {0x0E70, 0x0F}, // EFF_WAVE_TREMOLO + {0x2150, 0x0F}, // EFF_WAVE_PANBRELLO 40 (MPT!) + {0x2160, 0x0F}, // EFF_PATTERN_DELAYF (!) + {0x0800, 0xFF}, // EFF_OLD_PAN (!) converted to EFF_PAN + {0x0E60, 0x0F}, // EFF_PATTERN_LOOP + {0x0EC0, 0x0F}, // EFF_NOTE_CUT + {0x0ED0, 0x0F}, // EFF_NOTE_DELAY 45 + {0x0EE0, 0x0F}, // EFF_PATTERN_DELAY (*) + {0x1300, 0xFF}, // EFF_ENV_SETPOS + {0x0900, 0xFF}, // EFF_OFFSET_HIGH + {0x0600, 0xFF}, // EFF_VOLSLIDE_VIBRATO_XM + {0x0500, 0xFF} // EFF_VOLSLIDE_PORTA_XM 50 +}; + +// Writes a module from a file pointer to a new XM file. // XM file format from http://web.archive.org/web/20060809013752/http://pipin.tmd.ns.ac.yu/extra/fileformat/modules/xm/xm.txt int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector &sampleOffsets, const std::vector &instrumentOffsets, const char * filename) { + // Open the XM file FILE* out = fopen(filename, "wb"); if (out == NULL) { fprintf(stderr, "Could not open output file %s for writing.\n", filename); return 2; } + // Read the module from the file Module * mod = readModuleFile(fp, moduleOffset); unsigned char patternCount = 0; for (int i = 0; i < mod->numOrders; i++) patternCount = std::max(patternCount, mod->order[i]); patternCount++; - //printf("Writing header\n"); - fwrite("Extended Module: Krawall conversion \032FastTracker II \x04\x01\x14\x01\0\0", 1, 64, out); + // Write the XM header info + fwrite("Extended Module: Krawall conversion \032UnkrawerterGBA \x04\x01\x14\x01\0\0", 1, 64, out); fputc(mod->numOrders, out); - fputcn(0, 3, out); + fputcn(0, 3, out); // 4-byte padding fputc(mod->channels, out); - fputc(0, out); + fputc(0, out); // 2-byte padding unsigned short pnum = patternCount; fwrite(&pnum, 2, 1, out); pnum = instrumentOffsets.size(); fwrite(&pnum, 2, 1, out); fputc((mod->flagLinearSlides ? 1 : 0), out); - fputc(0, out); + fputc(0, out); // 2-byte padding fputc(mod->initSpeed, out); - fputc(0, out); + fputc(0, out); // 2-byte padding fputc(mod->initBPM, out); - fputc(0, out); + fputc(0, out); // 2-byte padding fwrite(mod->order, 1, 256, out); + // Write each pattern for (int i = 0; i < patternCount; i++) { - //printf("Writing pattern %d\n", i); + // Write pattern header fputc(9, out); - fputcn(0, 4, out); + fputcn(0, 4, out); // 4-byte padding + packing type (always 0) fwrite(&mod->patterns[i]->rows, 2, 1, out); - uint32_t sizePos = ftell(out); - fputcn(0, 2, out); + uint32_t sizePos = ftell(out); // Save the position so we can come back to write the size + fputcn(0, 2, out); // placeholder, we'll come back to this + // Convert the Krawall data into XM data const unsigned char * data = mod->patterns[i]->data; - Note * thisrow = (Note*)calloc(mod->channels, sizeof(Note)); + Note * thisrow = (Note*)calloc(mod->channels, sizeof(Note)); // stores the current row's notes + unsigned char warnings = 0; // for S3M/MPT warnings, we only warn once per pattern for (int row = 0; row < mod->patterns[i]->rows; row++) { - memset(thisrow, 0, sizeof(Note) * mod->channels); + memset(thisrow, 0, sizeof(Note) * mod->channels); // Zero so we can check the values for 0 later for (;;) { + // Read the channel/next byte types unsigned char follow = *data++; - if (!follow) break; - unsigned char xmflag = 0x80; + if (!follow) break; // If it's 0, the row's done + unsigned char xmflag = 0x80; // Stores the next byte types in XM format int channel = follow & 0x1f; unsigned char note = 0, volume = 0, effect = 0, effectop = 0; unsigned short instrument = 0; - if (follow & 0x20) { + if (follow & 0x20) { // Note & instrument follows xmflag |= 0x03; note = *data++; instrument = *data++; - if (note & 0x80) { + if (note & 0x80) { // If the note > 128, the instrument field is 2 bytes long instrument |= *data++ << 8; note &= 0x7f; } if (note > 97 || note == 0) note = 97; } - if (follow & 0x40) { + if (follow & 0x40) { // Volume follows xmflag |= 0x04; volume = *data++; } - if (follow & 0x80) { + if (follow & 0x80) { // Effect follows xmflag |= 0x18; effect = *data++; effectop = *data++; + // Convert the Krawall effect into an XM effect + unsigned short xmeffect = effectMap[effect].first; + unsigned char effectmask = effectMap[effect].second; + if (xmeffect == 0xFFFF) { // Ignored + xmflag &= ~0x18; + effect = 0; + effectop = 0; + } else if (effect == 6) { // S3M volume slide + if ((effectop & 0xF0) == 0xF0) { // fine decrease + effect = 0x0E; + effectop = 0xB0 | (effectop & 0x0F); + } else if ((effectop & 0x0F) == 0x0F) { // fine increase + effect = 0x0E; + effectop = 0xA0 | (effectop >> 4); + } else { // normal volume slide + effect = 0x0A; + } + } else if (effect == 11) { // S3M porta down + if ((effectop & 0xF0) == 0xF0) { // fine + effect = 0x0E; + effectop = 0x20 | (effectop & 0x0F); + } else if ((effectop & 0xF0) == 0xE0) { // extra fine + effect = 0x21; + effectop = 0x20 | (effectop & 0x0F); + } else { // normal + effect = 0x02; + } + } else if (effect == 15) { // S3M porta up + if ((effectop & 0xF0) == 0xF0) { // fine + effect = 0x0E; + effectop = 0x10 | (effectop & 0x0F); + } else if ((effectop & 0xF0) == 0xE0) { // extra fine + effect = 0x21; + effectop = 0x10 | (effectop & 0x0F); + } else { // normal + effect = 0x01; + } + } else if (effect == 25 || effect == 26 || effect == 31) { // Unsupported S3M effects + if (!(warnings & 0x02)) {warnings |= 0x02; printf("Warning: Pattern %d uses an effect specific to S3M. It will not play correctly.\n", i);} + xmflag &= ~0x18; + effect = 0; + effectop = 0; + } else { // Other effects + // Warn if MPT-only + if (effect == 35 || effect == 40 && !(warnings & 0x01)) {warnings |= 0x01; printf("Warning: Pattern %d uses an effect specific to OpenMPT. It will not play correctly in other trackers.\n", i);} + xmeffect = xmeffect | (effectop & effectmask); + effect = xmeffect >> 8; + effectop = xmeffect & 0xFF; + } } + // If the channel is OOB then don't store it (prevents segfaults, but that shouldn't happen if the file's good) if (channel >= mod->channels) continue; + // Store the note data in the row thisrow[channel].xmflag = xmflag; thisrow[channel].note = note; thisrow[channel].instrument = instrument; @@ -455,50 +586,73 @@ int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector thisrow[channel].effect = effect; thisrow[channel].effectop = effectop; } + // Since Krawall doesn't need to fill all channels and XM does, convert that out for (int i = 0; i < mod->channels; i++) { - if (thisrow[i].xmflag) { + if (thisrow[i].xmflag) { // If this was set, the note should be added fputc(thisrow[i].xmflag, out); if (thisrow[i].xmflag & 0x01) fputc(thisrow[i].note, out); if (thisrow[i].xmflag & 0x02) fputc(thisrow[i].instrument & 0x7F, out); if (thisrow[i].xmflag & 0x04) fputc(thisrow[i].volume, out); if (thisrow[i].xmflag & 0x08) fputc(thisrow[i].effect, out); if (thisrow[i].xmflag & 0x10) fputc(thisrow[i].effectop, out); - } else fputc(0x80, out); + } else fputc(0x80, out); // Empty note (do nothing this row) } } free(thisrow); + // Write the size of the packed pattern data uint32_t endPos = ftell(out); fseek(out, sizePos, SEEK_SET); unsigned short size = endPos - sizePos - 2; fwrite(&size, 2, 1, out); fseek(out, endPos, SEEK_SET); } + // Write all of the instruments + // In the future it may be worth it to optimize this so we don't write unused instruments for (int i = 0; i < instrumentOffsets.size(); i++) { - //printf("Writing instrument %d\n", i); + // Read the instrument info Instrument instr = readInstrumentFile(fp, instrumentOffsets[i]); + // Find all of the unique samples std::vector samples; samples.resize(96); samples.erase(std::unique_copy(instr.samples, instr.samples + 96, samples.begin()), samples.end()); unsigned short snum = samples.size(); + // Start writing instrument header fputc(snum == 0 ? 29 : 252, out); - fputcn(0, 3, out); + fputcn(0, 3, out); // 4-byte padding char name[22]; memset(name, 0, 22); snprintf(name, 22, "Instrument%d", i); fwrite(name, 1, 22, out); fputc(0, out); fwrite(&snum, 2, 1, out); - if (snum == 0) continue; + if (snum == 0) continue; // XM spec says if there's no samples then skip the rest + // Convert arbitrary sample numbers in the sample map to 0, 1, 2, etc. + // This is because Krawall has a global sample map, while XM counts samples per instrument std::map sample_conversion; for (unsigned short i = 0; i < snum; i++) sample_conversion[samples[i]] = i + 1; for (int i = 0; i < 96; i++) instr.samples[i] = sample_conversion[instr.samples[i]]; + // Write instrument data fputc(40, out); - fputcn(0, 3, out); + fputcn(0, 3, out); // 4-byte padding fwrite(instr.samples, 1, 96, out); - fwrite(instr.envVol.nodes, 4, 12, out); - fwrite(instr.envPan.nodes, 4, 12, out); - fputc(instr.envVol.max, out); - fputc(instr.envPan.max, out); + // Convert envelopes to XM format + // Turns out we don't even need the inc field! Everything's packed in coord. + unsigned short tmp; + for (int i = 0; i < 12; i++) { + tmp = instr.envVol.nodes[i].coord & 0x1ff; + fwrite(&tmp, 2, 1, out); + tmp = instr.envVol.nodes[i].coord >> 9; + fwrite(&tmp, 2, 1, out); + } + for (int i = 0; i < 12; i++) { + tmp = instr.envPan.nodes[i].coord & 0x1ff; + fwrite(&tmp, 2, 1, out); + tmp = instr.envPan.nodes[i].coord >> 9; + fwrite(&tmp, 2, 1, out); + } + // Here's a whole bunch of envelope parameters to write + fputc(instr.envVol.max + 1, out); + fputc(instr.envPan.max + 1, out); fputc(instr.envVol.sus, out); fputc(instr.envVol.loopStart, out); fputc(instr.envVol.max, out); @@ -512,21 +666,28 @@ int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector fputc(instr.vibDepth, out); fputc(instr.vibRate, out); fwrite(&instr.volFade, 2, 1, out); - fputcn(0, 11, out); + fputcn(0, 11, out); // Padding as required by XM + // Write all of the samples required for this instrument + // XM requires all of the headers to be written before the data, so we read + // all of the samples in one loop and then write the data in another + // Seems inefficient but it's impossible to avoid std::vector sarr; for (int j = 0; j < snum; j++) { - if (samples[j] > sampleOffsets.size()) continue; - //printf("Writing sample %d\n", j); + if (samples[j] > sampleOffsets.size()) continue; // If the sample isn't present then skip it + // Read the sample from the file Sample * s = readSampleFile(fp, sampleOffsets[samples[j]]); + // Write the sample header if (s->hq) { uint32_t ssize = s->size / 2; fwrite(&ssize, 4, 1, out); } else fwrite(&s->size, 4, 1, out); + // Loop start has to be computed from the end & length if (s->loopLength == 0) fputcn(0, 4, out); else { uint32_t start = s->size - s->loopLength; fwrite(&start, 4, 1, out); } + // Some other sample parameters fwrite(&s->loopLength, 4, 1, out); fputc(s->volDefault, out); fputc(s->fineTune, out); @@ -537,17 +698,21 @@ int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector memset(name, ' ', 22); snprintf(name, 22, "Sample%d", i); fwrite(name, 1, 22, out); - sarr.push_back(s); + sarr.push_back(s); // Push the read sample back so we don't have to allocate & read it again } + // Write the actual sample data for (int j = 0; j < sarr.size(); j++) { Sample * s = sarr[j]; - if (s->hq) { // 16-bit + // Everything's written as deltas instead of absolute values + if (s->hq) { // 16-bit? + // Don't have an example of this in the wild (yet?), so support isn't guaranteed signed short old = 0; for (uint32_t k = 0; k < s->size; k+=2) { fputc(((signed short*)s->data)[k] - old, out); old = ((signed short*)s->data)[k]; } } else { // 8-bit + // We also convert from signed to unsigned here since it has to be unsigned unsigned char old = 0; for (uint32_t k = 0; k < s->size; k++) { fputc(((int)s->data[k] + 0x80) - old, out); @@ -557,6 +722,7 @@ int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector free(s); } } + // Free & close the patterns, module, & file for (int i = 0; i < patternCount; i++) free((void*)mod->patterns[i]); free(mod); fclose(out); @@ -564,6 +730,7 @@ int writeModuleToXM(FILE* fp, uint32_t moduleOffset, const std::vector return 0; } +// Looks for a string in a file bool fstr(FILE* fp, const char * str) { rewind(fp); const char * ptr = str; @@ -580,20 +747,25 @@ int main(int argc, const char * argv[]) { fprintf(stderr, "Usage: %s [output dir] [search threshold] [verbose]\n", argv[0]); return 1; } + // Open the ROM file FILE* fp = fopen(argv[1], "rb"); if (fp == NULL) { fprintf(stderr, "Could not open file %s for reading.", argv[1]); return 2; } + // Look for a Krawall signature in the file and warn if one isn't found if (!fstr(fp, "Krawall")) printf("Warning: Could not find Krawall signature. Are you sure this game uses the Krawall engine?\n"); rewind(fp); + // Search for the offsets OffsetSearchResult offsets; if (argc > 3) offsets = searchForOffsets(fp, atoi(argv[3]), argc > 4); else offsets = searchForOffsets(fp); + // If we don't have all of the required offsets, we can't continue if (!offsets.success) { fprintf(stderr, "Could not find all of the offsets required.\n * Does the ROM use the Krawall engine?\n * Try adjusting the search threshold.\n * You may need to find offsets yourself.\n"); return 3; } + // Read each of the offsets from the lists in the file into vectors std::vector sampleOffsets, instrumentOffsets; uint32_t tmp = 0; fseek(fp, offsets.sampleAddr, SEEK_SET); @@ -606,9 +778,11 @@ int main(int argc, const char * argv[]) { fread(&tmp, 4, 1, fp); instrumentOffsets.push_back(tmp & 0x1ffffff); } + // Write out all of the new modules for (int i = 0; i < offsets.modules.size(); i++) { std::string name = (argc > 2 ? std::string(argv[2]) + "/" : std::string()) + "Module" + std::to_string(i) + ".xm"; - writeModuleToXM(fp, offsets.modules[i], sampleOffsets, instrumentOffsets, name.c_str()); + int r = writeModuleToXM(fp, offsets.modules[i], sampleOffsets, instrumentOffsets, name.c_str()); + if (r) {fclose(fp); return r;} } fclose(fp); return 0;