// for finding memory leaks in debug mode with Visual Studio#if defined _DEBUG && defined _MSC_VER#include <crtdbg.h>#endif #include <stdio.h>#include <stdbool.h>#include "ft2_header.h"#include "ft2_audio.h"#include "ft2_gui.h"#include "ft2_mouse.h"#include "ft2_sample_ed.h"#include "ft2_module_loader.h"#include "ft2_tables.h"#include "ft2_structs.h" static int8_t smpChunkBuf[1024];static uint8_t packedPattData[65536], modPattData[64*32*4];static SDL_Thread *thread; static const char modIDs[32][5] ={ "1CHN", "2CHN", "3CHN", "4CHN", "5CHN", "6CHN", "7CHN", "8CHN", "9CHN", "10CH", "11CH", "12CH", "13CH", "14CH", "15CH", "16CH", "17CH", "18CH", "19CH", "20CH", "21CH", "22CH", "23CH", "24CH", "25CH", "26CH", "27CH", "28CH", "29CH", "30CH", "31CH", "32CH"}; static uint16_t packPatt(uint8_t *writePtr, uint8_t *pattPtr, uint16_t numRows); bool saveXM(UNICHAR *filenameU){ int16_t i, j, k, a; size_t result; xmHdr_t h; xmPatHdr_t ph; instr_t *ins; xmInsHdr_t ih; sample_t *s; xmSmpHdr_t *dst; FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL) { okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?"); return false; } memcpy(h.ID, "Extended Module: ", 17); // song name int32_t nameLength = (int32_t)strlen(song.name); if (nameLength > 20) nameLength = 20; memset(h.name, ' ', 20); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(h.name, song.name, nameLength); h.x1A = 0x1A; // program/tracker name nameLength = (int32_t)strlen(PROG_NAME_STR); if (nameLength > 20) nameLength = 20; memset(h.progName, ' ', 20); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(h.progName, PROG_NAME_STR, nameLength); h.version = 0x0104; h.headerSize = 20 + 256; h.numOrders = song.songLength; h.songLoopStart = song.songLoopStart; h.numChannels = (uint16_t)song.numChannels; h.speed = song.speed; h.BPM = song.BPM; // count number of patterns i = MAX_PATTERNS; do { if (patternEmpty(i-1)) i--; else break; } while (i > 0); h.numPatterns = i; // count number of instruments i = 128; while (i > 0 && getUsedSamples(i) == 0 && song.instrName[i][0] == '\0') i--; h.numInstr = i; h.flags = audio.linearPeriodsFlag; memcpy(h.orders, song.orders, 256); if (fwrite(&h, sizeof (h), 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } for (i = 0; i < h.numPatterns; i++) { if (patternEmpty(i)) { if (pattern[i] != NULL) { free(pattern[i]); pattern[i] = NULL; } patternNumRows[i] = 64; } ph.headerSize = sizeof (xmPatHdr_t); ph.numRows = patternNumRows[i]; ph.type = 0; if (pattern[i] == NULL) { ph.dataSize = 0; if (fwrite(&ph, ph.headerSize, 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } else { ph.dataSize = packPatt(packedPattData, (uint8_t *)pattern[i], patternNumRows[i]); result = fwrite(&ph, ph.headerSize, 1, f); result += fwrite(packedPattData, ph.dataSize, 1, f); if (result != 2) // write was not OK { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } } memset(&ih, 0, sizeof (ih)); // important, clears reserved stuff for (i = 1; i <= h.numInstr; i++) { if (instr[i] == NULL) j = 0; else j = i; a = getUsedSamples(i); nameLength = (int32_t)strlen(song.instrName[i]); if (nameLength > 22) nameLength = 22; memset(ih.name, 0, 22); // pad with zero if (nameLength > 0) memcpy(ih.name, song.instrName[i], nameLength); ih.type = 0; ih.numSamples = a; ih.sampleSize = sizeof (xmSmpHdr_t); if (a > 0) { ins = instr[j]; memcpy(ih.note2SampleLUT, ins->note2SampleLUT, 96); memcpy(ih.volEnvPoints, ins->volEnvPoints, 12*2*sizeof(int16_t)); memcpy(ih.panEnvPoints, ins->panEnvPoints, 12*2*sizeof(int16_t)); ih.volEnvLength = ins->volEnvLength; ih.panEnvLength = ins->panEnvLength; ih.volEnvSustain = ins->volEnvSustain; ih.volEnvLoopStart = ins->volEnvLoopStart; ih.volEnvLoopEnd = ins->volEnvLoopEnd; ih.panEnvSustain = ins->panEnvSustain; ih.panEnvLoopStart = ins->panEnvLoopStart; ih.panEnvLoopEnd = ins->panEnvLoopEnd; ih.volEnvFlags = ins->volEnvFlags; ih.panEnvFlags = ins->panEnvFlags; ih.vibType = ins->autoVibType; ih.vibSweep = ins->autoVibSweep; ih.vibDepth = ins->autoVibDepth; ih.vibRate = ins->autoVibRate; ih.fadeout = ins->fadeout; ih.midiOn = ins->midiOn ? 1 : 0; ih.midiChannel = ins->midiChannel; ih.midiProgram = ins->midiProgram; ih.midiBend = ins->midiBend; ih.mute = ins->mute ? 1 : 0; ih.instrSize = INSTR_HEADER_SIZE; for (k = 0; k < a; k++) { s = &instr[j]->smp[k]; dst = &ih.smp[k]; bool sample16Bit = !!(s->flags & SAMPLE_16BIT); dst->length = s->length; dst->loopStart = s->loopStart; dst->loopLength = s->loopLength; if (sample16Bit) { dst->length <<= 1; dst->loopStart <<= 1; dst->loopLength <<= 1; } dst->volume = s->volume; dst->finetune = s->finetune; dst->flags = s->flags; dst->panning = s->panning; dst->relativeNote = s->relativeNote; nameLength = (int32_t)strlen(s->name); if (nameLength > 22) nameLength = 22; dst->nameLength = (uint8_t)nameLength; memset(dst->name, ' ', 22); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(dst->name, s->name, nameLength); if (s->dataPtr == NULL) dst->length = 0; } } else { ih.instrSize = 22 + 11; } if (fwrite(&ih, ih.instrSize + (a * sizeof (xmSmpHdr_t)), 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } for (k = 1; k <= a; k++) { s = &instr[j]->smp[k-1]; if (s->dataPtr != NULL) { unfixSample(s); samp2Delta(s->dataPtr, s->length, s->flags); result = fwrite(s->dataPtr, 1, SAMPLE_LENGTH_BYTES(s), f); delta2Samp(s->dataPtr, s->length, s->flags); fixSample(s); if (result != (size_t)SAMPLE_LENGTH_BYTES(s)) // write not OK { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } } } removeSongModifiedFlag(); fclose(f); editor.diskOpReadDir = true; // force diskop re-read setMouseBusy(false); return true;} static bool saveMOD(UNICHAR *filenameU){ int16_t i; int32_t j, k; instr_t *ins; sample_t *smp; modHdr_t hdr; // Commented out. This one was probably confusing to many people... /* if (audio.linearPeriodsFlag) okBoxThreadSafe(0, "System message", "Warning: \"Frequency slides\" is not set to Amiga!"); */ int32_t songLength = song.songLength; if (songLength > 128) { songLength = 128; okBoxThreadSafe(0, "System message", "Warning: Song length is above 128!"); } // calculate number of patterns referenced (max 128 orders) int32_t numPatterns = 0; for (i = 0; i < songLength; i++) { if (song.orders[i] > numPatterns) numPatterns = song.orders[i]; } numPatterns++; if (numPatterns > 100) { numPatterns = 100; okBoxThreadSafe(0, "System message", "Warning: Song has more than 100 patterns!"); } // check if song has more than 31 instruments for (i = 32; i <= 128; i++) { if (getRealUsedSamples(i) > 0) { okBoxThreadSafe(0, "System message", "Warning: Song has more than 31 instruments!"); break; } } // check if the first 31 samples have a length above 65534 samples bool test = false; bool test2 = false; for (i = 1; i <= 31; i++) { ins = instr[i]; if (ins == NULL) continue; smp = &ins->smp[0]; if (smp->length > 131070) test = true; else if (smp->length > 65534) test2 = true; } if (test) okBoxThreadSafe(0, "System message", "Warning: Song has sample lengths that are too long for the MOD format!"); else if (test2) okBoxThreadSafe(0, "System message", "Warning: Song has sample lengths above 65534! Not all MOD players support this."); // check if XM instrument features are being used test = false; for (i = 1; i <= 31; i++) { ins = instr[i]; if (ins == NULL) continue; smp = &ins->smp[0]; j = getRealUsedSamples(i); if (j > 1) { test = true; break; } if (j == 1) { if (ins->fadeout != 0 || ins->volEnvFlags != 0 || ins->panEnvFlags != 0 || ins->autoVibRate > 0 || GET_LOOPTYPE(smp->flags) == LOOP_BIDI || smp->relativeNote != 0 || ins->midiOn) { test = true; break; } } } if (test) okBoxThreadSafe(0, "System message", "Warning: Song is using XM instrument features!"); bool tooLongPatterns = false; bool tooManyInstr = false; bool incompatEfx = false; bool noteUnderflow = false; for (i = 0; i < numPatterns; i++) { if (pattern[i] == NULL) continue; if (patternNumRows[i] < 64) { okBoxThreadSafe(0, "System message", "Error: Pattern lengths can't be below 64! Module wasn't saved."); return false; } if (patternNumRows[i] > 64) tooLongPatterns = true; for (j = 0; j < 64; j++) { for (k = 0; k < song.numChannels; k++) { note_t *p = &pattern[i][(j * MAX_CHANNELS) + k]; if (p->instr > 31) tooManyInstr = true; if (p->efx > 0xF || p->vol != 0) incompatEfx = true; // added security that wasn't present in FT2 if (p->note > 0 && p->note < 10) noteUnderflow = true; } } } if (tooLongPatterns) okBoxThreadSafe(0, "System message", "Warning: Song has pattern lengths above 64!"); if (tooManyInstr) okBoxThreadSafe(0, "System message", "Warning: Patterns have instrument numbers above 31!"); if (incompatEfx) okBoxThreadSafe(0, "System message", "Warning: Patterns have incompatible effects!"); if (noteUnderflow) okBoxThreadSafe(0, "System message", "Warning: Patterns have notes below A-0!"); // save module now memset(&hdr, 0, sizeof (hdr)); // song name int32_t nameLength = (int32_t)strlen(song.name); if (nameLength > 20) nameLength = 20; memset(hdr.name, 0, 20); // pad with zeroes if (nameLength > 0) memcpy(hdr.name, song.name, nameLength); hdr.numOrders = (uint8_t)songLength; // pre-clamped to 0..128 hdr.songLoopStart = (uint8_t)song.songLoopStart; if (hdr.songLoopStart >= hdr.numOrders) // repeat-point must be lower than the song length hdr.songLoopStart = 0; memcpy(hdr.orders, song.orders, hdr.numOrders); if (song.numChannels == 4) memcpy(hdr.ID, (numPatterns > 64) ? "M!K!" : "M.K.", 4); else memcpy(hdr.ID, modIDs[song.numChannels-1], 4); // fill MOD sample headers for (i = 1; i <= 31; i++) { modSmpHdr_t *modSmp = &hdr.smp[i-1]; nameLength = (int32_t)strlen(song.instrName[i]); if (nameLength > 22) nameLength = 22; memset(modSmp->name, 0, 22); // pad with zeroes if (nameLength > 0) memcpy(modSmp->name, song.instrName[i], nameLength); if (instr[i] != NULL && getRealUsedSamples(i) != 0) { smp = &instr[i]->smp[0]; int32_t length = smp->length >> 1; int32_t loopStart = smp->loopStart >> 1; int32_t loopLength = smp->loopLength >> 1; // length/loopStart/loopLength are now in units of words if (length > UINT16_MAX) length = UINT16_MAX; if (GET_LOOPTYPE(smp->flags) == LOOP_OFF) { loopStart = 0; loopLength = 1; } else // looped sample { if (loopLength == 0) // ProTracker hates loopLengths of zero loopLength = 1; if (loopStart+loopLength > length) { loopStart = 0; loopLength = 1; } } modSmp->length = (uint16_t)SWAP16(length); modSmp->finetune = FINETUNE_XM2MOD(smp->finetune); modSmp->volume = smp->volume; modSmp->loopStart = (uint16_t)SWAP16(loopStart); modSmp->loopLength = (uint16_t)SWAP16(loopLength); } } FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL)Lines 1–500 of 738. Load more
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdbool.h> #include "ft2_header.h" #include "ft2_audio.h" #include "ft2_gui.h" #include "ft2_mouse.h" #include "ft2_sample_ed.h" #include "ft2_module_loader.h" #include "ft2_tables.h" #include "ft2_structs.h" static int8_t smpChunkBuf[1024]; static uint8_t packedPattData[65536], modPattData[64*32*4]; static SDL_Thread *thread; static const char modIDs[32][5] = { "1CHN", "2CHN", "3CHN", "4CHN", "5CHN", "6CHN", "7CHN", "8CHN", "9CHN", "10CH", "11CH", "12CH", "13CH", "14CH", "15CH", "16CH", "17CH", "18CH", "19CH", "20CH", "21CH", "22CH", "23CH", "24CH", "25CH", "26CH", "27CH", "28CH", "29CH", "30CH", "31CH", "32CH" }; static uint16_t packPatt(uint8_t *writePtr, uint8_t *pattPtr, uint16_t numRows); bool saveXM(UNICHAR *filenameU) { int16_t i, j, k, a; size_t result; xmHdr_t h; xmPatHdr_t ph; instr_t *ins; xmInsHdr_t ih; sample_t *s; xmSmpHdr_t *dst; FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL) { okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?"); return false; } memcpy(h.ID, "Extended Module: ", 17); // song name int32_t nameLength = (int32_t)strlen(song.name); if (nameLength > 20) nameLength = 20; memset(h.name, ' ', 20); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(h.name, song.name, nameLength); h.x1A = 0x1A; // program/tracker name nameLength = (int32_t)strlen(PROG_NAME_STR); if (nameLength > 20) nameLength = 20; memset(h.progName, ' ', 20); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(h.progName, PROG_NAME_STR, nameLength); h.version = 0x0104; h.headerSize = 20 + 256; h.numOrders = song.songLength; h.songLoopStart = song.songLoopStart; h.numChannels = (uint16_t)song.numChannels; h.speed = song.speed; h.BPM = song.BPM; // count number of patterns i = MAX_PATTERNS; do { if (patternEmpty(i-1)) i--; else break; } while (i > 0); h.numPatterns = i; // count number of instruments i = 128; while (i > 0 && getUsedSamples(i) == 0 && song.instrName[i][0] == '\0') i--; h.numInstr = i; h.flags = audio.linearPeriodsFlag; memcpy(h.orders, song.orders, 256); if (fwrite(&h, sizeof (h), 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } for (i = 0; i < h.numPatterns; i++) { if (patternEmpty(i)) { if (pattern[i] != NULL) { free(pattern[i]); pattern[i] = NULL; } patternNumRows[i] = 64; } ph.headerSize = sizeof (xmPatHdr_t); ph.numRows = patternNumRows[i]; ph.type = 0; if (pattern[i] == NULL) { ph.dataSize = 0; if (fwrite(&ph, ph.headerSize, 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } else { ph.dataSize = packPatt(packedPattData, (uint8_t *)pattern[i], patternNumRows[i]); result = fwrite(&ph, ph.headerSize, 1, f); result += fwrite(packedPattData, ph.dataSize, 1, f); if (result != 2) // write was not OK { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } } memset(&ih, 0, sizeof (ih)); // important, clears reserved stuff for (i = 1; i <= h.numInstr; i++) { if (instr[i] == NULL) j = 0; else j = i; a = getUsedSamples(i); nameLength = (int32_t)strlen(song.instrName[i]); if (nameLength > 22) nameLength = 22; memset(ih.name, 0, 22); // pad with zero if (nameLength > 0) memcpy(ih.name, song.instrName[i], nameLength); ih.type = 0; ih.numSamples = a; ih.sampleSize = sizeof (xmSmpHdr_t); if (a > 0) { ins = instr[j]; memcpy(ih.note2SampleLUT, ins->note2SampleLUT, 96); memcpy(ih.volEnvPoints, ins->volEnvPoints, 12*2*sizeof(int16_t)); memcpy(ih.panEnvPoints, ins->panEnvPoints, 12*2*sizeof(int16_t)); ih.volEnvLength = ins->volEnvLength; ih.panEnvLength = ins->panEnvLength; ih.volEnvSustain = ins->volEnvSustain; ih.volEnvLoopStart = ins->volEnvLoopStart; ih.volEnvLoopEnd = ins->volEnvLoopEnd; ih.panEnvSustain = ins->panEnvSustain; ih.panEnvLoopStart = ins->panEnvLoopStart; ih.panEnvLoopEnd = ins->panEnvLoopEnd; ih.volEnvFlags = ins->volEnvFlags; ih.panEnvFlags = ins->panEnvFlags; ih.vibType = ins->autoVibType; ih.vibSweep = ins->autoVibSweep; ih.vibDepth = ins->autoVibDepth; ih.vibRate = ins->autoVibRate; ih.fadeout = ins->fadeout; ih.midiOn = ins->midiOn ? 1 : 0; ih.midiChannel = ins->midiChannel; ih.midiProgram = ins->midiProgram; ih.midiBend = ins->midiBend; ih.mute = ins->mute ? 1 : 0; ih.instrSize = INSTR_HEADER_SIZE; for (k = 0; k < a; k++) { s = &instr[j]->smp[k]; dst = &ih.smp[k]; bool sample16Bit = !!(s->flags & SAMPLE_16BIT); dst->length = s->length; dst->loopStart = s->loopStart; dst->loopLength = s->loopLength; if (sample16Bit) { dst->length <<= 1; dst->loopStart <<= 1; dst->loopLength <<= 1; } dst->volume = s->volume; dst->finetune = s->finetune; dst->flags = s->flags; dst->panning = s->panning; dst->relativeNote = s->relativeNote; nameLength = (int32_t)strlen(s->name); if (nameLength > 22) nameLength = 22; dst->nameLength = (uint8_t)nameLength; memset(dst->name, ' ', 22); // yes, FT2 pads the name with spaces if (nameLength > 0) memcpy(dst->name, s->name, nameLength); if (s->dataPtr == NULL) dst->length = 0; } } else { ih.instrSize = 22 + 11; } if (fwrite(&ih, ih.instrSize + (a * sizeof (xmSmpHdr_t)), 1, f) != 1) { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } for (k = 1; k <= a; k++) { s = &instr[j]->smp[k-1]; if (s->dataPtr != NULL) { unfixSample(s); samp2Delta(s->dataPtr, s->length, s->flags); result = fwrite(s->dataPtr, 1, SAMPLE_LENGTH_BYTES(s), f); delta2Samp(s->dataPtr, s->length, s->flags); fixSample(s); if (result != (size_t)SAMPLE_LENGTH_BYTES(s)) // write not OK { fclose(f); okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!"); return false; } } } } removeSongModifiedFlag(); fclose(f); editor.diskOpReadDir = true; // force diskop re-read setMouseBusy(false); return true; } static bool saveMOD(UNICHAR *filenameU) { int16_t i; int32_t j, k; instr_t *ins; sample_t *smp; modHdr_t hdr; // Commented out. This one was probably confusing to many people... /* if (audio.linearPeriodsFlag) okBoxThreadSafe(0, "System message", "Warning: \"Frequency slides\" is not set to Amiga!"); */ int32_t songLength = song.songLength; if (songLength > 128) { songLength = 128; okBoxThreadSafe(0, "System message", "Warning: Song length is above 128!"); } // calculate number of patterns referenced (max 128 orders) int32_t numPatterns = 0; for (i = 0; i < songLength; i++) { if (song.orders[i] > numPatterns) numPatterns = song.orders[i]; } numPatterns++; if (numPatterns > 100) { numPatterns = 100; okBoxThreadSafe(0, "System message", "Warning: Song has more than 100 patterns!"); } // check if song has more than 31 instruments for (i = 32; i <= 128; i++) { if (getRealUsedSamples(i) > 0) { okBoxThreadSafe(0, "System message", "Warning: Song has more than 31 instruments!"); break; } } // check if the first 31 samples have a length above 65534 samples bool test = false; bool test2 = false; for (i = 1; i <= 31; i++) { ins = instr[i]; if (ins == NULL) continue; smp = &ins->smp[0]; if (smp->length > 131070) test = true; else if (smp->length > 65534) test2 = true; } if (test) okBoxThreadSafe(0, "System message", "Warning: Song has sample lengths that are too long for the MOD format!"); else if (test2) okBoxThreadSafe(0, "System message", "Warning: Song has sample lengths above 65534! Not all MOD players support this."); // check if XM instrument features are being used test = false; for (i = 1; i <= 31; i++) { ins = instr[i]; if (ins == NULL) continue; smp = &ins->smp[0]; j = getRealUsedSamples(i); if (j > 1) { test = true; break; } if (j == 1) { if (ins->fadeout != 0 || ins->volEnvFlags != 0 || ins->panEnvFlags != 0 || ins->autoVibRate > 0 || GET_LOOPTYPE(smp->flags) == LOOP_BIDI || smp->relativeNote != 0 || ins->midiOn) { test = true; break; } } } if (test) okBoxThreadSafe(0, "System message", "Warning: Song is using XM instrument features!"); bool tooLongPatterns = false; bool tooManyInstr = false; bool incompatEfx = false; bool noteUnderflow = false; for (i = 0; i < numPatterns; i++) { if (pattern[i] == NULL) continue; if (patternNumRows[i] < 64) { okBoxThreadSafe(0, "System message", "Error: Pattern lengths can't be below 64! Module wasn't saved."); return false; } if (patternNumRows[i] > 64) tooLongPatterns = true; for (j = 0; j < 64; j++) { for (k = 0; k < song.numChannels; k++) { note_t *p = &pattern[i][(j * MAX_CHANNELS) + k]; if (p->instr > 31) tooManyInstr = true; if (p->efx > 0xF || p->vol != 0) incompatEfx = true; // added security that wasn't present in FT2 if (p->note > 0 && p->note < 10) noteUnderflow = true; } } } if (tooLongPatterns) okBoxThreadSafe(0, "System message", "Warning: Song has pattern lengths above 64!"); if (tooManyInstr) okBoxThreadSafe(0, "System message", "Warning: Patterns have instrument numbers above 31!"); if (incompatEfx) okBoxThreadSafe(0, "System message", "Warning: Patterns have incompatible effects!"); if (noteUnderflow) okBoxThreadSafe(0, "System message", "Warning: Patterns have notes below A-0!"); // save module now memset(&hdr, 0, sizeof (hdr)); // song name int32_t nameLength = (int32_t)strlen(song.name); if (nameLength > 20) nameLength = 20; memset(hdr.name, 0, 20); // pad with zeroes if (nameLength > 0) memcpy(hdr.name, song.name, nameLength); hdr.numOrders = (uint8_t)songLength; // pre-clamped to 0..128 hdr.songLoopStart = (uint8_t)song.songLoopStart; if (hdr.songLoopStart >= hdr.numOrders) // repeat-point must be lower than the song length hdr.songLoopStart = 0; memcpy(hdr.orders, song.orders, hdr.numOrders); if (song.numChannels == 4) memcpy(hdr.ID, (numPatterns > 64) ? "M!K!" : "M.K.", 4); else memcpy(hdr.ID, modIDs[song.numChannels-1], 4); // fill MOD sample headers for (i = 1; i <= 31; i++) { modSmpHdr_t *modSmp = &hdr.smp[i-1]; nameLength = (int32_t)strlen(song.instrName[i]); if (nameLength > 22) nameLength = 22; memset(modSmp->name, 0, 22); // pad with zeroes if (nameLength > 0) memcpy(modSmp->name, song.instrName[i], nameLength); if (instr[i] != NULL && getRealUsedSamples(i) != 0) { smp = &instr[i]->smp[0]; int32_t length = smp->length >> 1; int32_t loopStart = smp->loopStart >> 1; int32_t loopLength = smp->loopLength >> 1; // length/loopStart/loopLength are now in units of words if (length > UINT16_MAX) length = UINT16_MAX; if (GET_LOOPTYPE(smp->flags) == LOOP_OFF) { loopStart = 0; loopLength = 1; } else // looped sample { if (loopLength == 0) // ProTracker hates loopLengths of zero loopLength = 1; if (loopStart+loopLength > length) { loopStart = 0; loopLength = 1; } } modSmp->length = (uint16_t)SWAP16(length); modSmp->finetune = FINETUNE_XM2MOD(smp->finetune); modSmp->volume = smp->volume; modSmp->loopStart = (uint16_t)SWAP16(loopStart); modSmp->loopLength = (uint16_t)SWAP16(loopLength); } } FILE *f = UNICHAR_FOPEN(filenameU, "wb"); if (f == NULL)