ref: 9435ca2b06a5108b9314e700967ad4cb97ca791f
dir: /src/ft2_module_loader.c/
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdint.h> #include <stdbool.h> #ifndef _WIN32 #include <unistd.h> #endif #include "ft2_header.h" #include "ft2_scopes.h" #include "ft2_trim.h" #include "ft2_inst_ed.h" #include "ft2_sample_ed.h" #include "ft2_wav_renderer.h" #include "ft2_pattern_ed.h" #include "ft2_gui.h" #include "ft2_diskop.h" #include "ft2_sample_loader.h" #include "ft2_mouse.h" #include "ft2_midi.h" #include "ft2_events.h" #include "ft2_video.h" #include "ft2_structs.h" #include "ft2_sysreqs.h" bool loadDIGI(FILE *f, uint32_t filesize); bool loadMOD(FILE *f, uint32_t filesize); bool loadS3M(FILE *f, uint32_t filesize); bool loadSTK(FILE *f, uint32_t filesize); bool loadSTM(FILE *f, uint32_t filesize); bool loadXM(FILE *f, uint32_t filesize); enum { FORMAT_UNKNOWN = 0, FORMAT_POSSIBLY_STK = 1, FORMAT_XM = 2, FORMAT_MOD = 3, FORMAT_S3M = 4, FORMAT_STM = 5, FORMAT_DIGI = 6 }; // file extensions accepted by Disk Op. in module mode char *supportedModExtensions[] = { "xm", "ft", "nst", "stk", "mod", "s3m", "stm", "fst", "digi", "END_OF_LIST" // do NOT move, remove or edit this line! }; // globals for module loaders volatile bool tmpLinearPeriodsFlag; int16_t pattLensTmp[MAX_PATTERNS]; tonTyp *pattTmp[MAX_PATTERNS]; instrTyp *instrTmp[1+256]; songTyp songTmp; // -------------------------- static volatile bool musicIsLoading, moduleLoaded, moduleFailedToLoad; static SDL_Thread *thread; static uint8_t oldPlayMode; static void setupLoadedModule(void); static void freeTmpModule(void); // Crude module detection routine. These aren't always accurate detections! static int8_t detectModule(FILE *f) { uint8_t D[256], I[4]; fseek(f, 0, SEEK_END); uint32_t fileLength = (uint32_t)ftell(f); rewind(f); memset(D, 0, sizeof (D)); fread(D, 1, sizeof (D), f); fseek(f, 1080, SEEK_SET); // MOD ID I[0] = I[1] = I[2] = I[3] = 0; fread(I, 1, 4, f); rewind(f); // DIGI Booster (non-Pro) if (!memcmp("DIGI Booster module", &D[0x00], 19+1) && D[0x19] >= 1 && D[0x19] <= 8) return FORMAT_DIGI; // Scream Tracker 3 S3M (and compatible trackers) if (!memcmp("SCRM", &D[0x2C], 4) && D[0x1D] == 16) // XXX: byte=16 in all cases? return FORMAT_S3M; // Scream Tracker 2 STM if ((!memcmp("!Scream!", &D[0x14], 8) || !memcmp("BMOD2STM", &D[0x14], 8) || !memcmp("WUZAMOD!", &D[0x14], 8) || !memcmp("SWavePro", &D[0x14], 8)) && D[0x1D] == 2) // XXX: byte=2 for "WUZAMOD!"/"SWavePro" ? { return FORMAT_STM; } // Generic multi-channel MOD (1..9 channels) if (isdigit(I[0]) && I[0] != '0' && I[1] == 'C' && I[2] == 'H' && I[3] == 'N') // xCHN return FORMAT_MOD; // Digital Tracker (Atari Falcon) if (I[0] == 'F' && I[1] == 'A' && I[2] == '0' && I[3] >= '4' && I[3] <= '8') // FA0x (x=4..8) return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'H') // xxCH return FORMAT_MOD; // Generic multi-channel MOD (10..99 channels) if (isdigit(I[0]) && isdigit(I[1]) && I[0] != '0' && I[2] == 'C' && I[3] == 'N') // xxCN (same as xxCH) return FORMAT_MOD; // ProTracker and generic MOD formats if (!memcmp("M.K.", I, 4) || !memcmp("M!K!", I, 4) || !memcmp("NSMS", I, 4) || !memcmp("LARD", I, 4) || !memcmp("PATT", I, 4) || !memcmp("FLT4", I, 4) || !memcmp("FLT8", I, 4) || !memcmp("EXO4", I, 4) || !memcmp("EXO8", I, 4) || !memcmp("N.T.", I, 4) || !memcmp("M&K!", I, 4) || !memcmp("FEST", I, 4) || !memcmp("CD61", I, 4) || !memcmp("CD81", I, 4) || !memcmp("OKTA", I, 4) || !memcmp("OCTA", I, 4)) { return FORMAT_MOD; } /* Check if the file is a .it module (Impulse Tracker, not supported). ** Some people may attempt to load .IT files in the FT2 clone, so ** reject them here instead of accidentally loading them as .STK */ if (!memcmp("IMPM", D, 4) && D[0x16] == 0) return FORMAT_UNKNOWN; /* Fasttracker II XM and compatible trackers. ** Note: This test can falsely be true for STK modules (and non-supported files) where the ** first 17 bytes start with "Extended Module: ". This is unlikely to happen. */ if (!memcmp("Extended Module: ", &D[0x00], 17)) return FORMAT_XM; /* Lastly, we assume that the file is either a 15-sample STK or an unsupported file. ** Let's assume it's an STK and do some sanity checks. If they fail, we have an ** unsupported file. */ // minimum and maximum (?) possible size for a supported STK if (fileLength < 1624 || fileLength > 984634) return FORMAT_UNKNOWN; // test STK numOrders+BPM for illegal values fseek(f, 470, SEEK_SET); D[0] = D[1] = 0; fread(D, 1, 2, f); rewind(f); if (D[0] >= 0 && D[0] <= 128 && D[1] <= 220) return FORMAT_POSSIBLY_STK; return FORMAT_UNKNOWN; } static bool doLoadMusic(bool externalThreadFlag) { // setup message box functions loaderMsgBox = externalThreadFlag ? myLoaderMsgBoxThreadSafe : myLoaderMsgBox; loaderSysReq = externalThreadFlag ? okBoxThreadSafe : okBox; if (editor.tmpFilenameU == NULL) { loaderMsgBox("Generic memory fault during loading!"); goto loadError; } FILE *f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb"); if (f == NULL) { loaderMsgBox("General I/O error during loading! Is the file in use? Does it exist?"); goto loadError; } int8_t format = detectModule(f); fseek(f, 0, SEEK_END); uint32_t filesize = ftell(f); rewind(f); switch (format) { case FORMAT_XM: moduleLoaded = loadXM(f, filesize); break; case FORMAT_S3M: moduleLoaded = loadS3M(f, filesize); break; case FORMAT_STM: moduleLoaded = loadSTM(f, filesize); break; case FORMAT_MOD: moduleLoaded = loadMOD(f, filesize); break; case FORMAT_POSSIBLY_STK: moduleLoaded = loadSTK(f, filesize); break; case FORMAT_DIGI: moduleLoaded = loadDIGI(f, filesize); break; default: loaderMsgBox("This file is not a supported module!"); break; } fclose(f); if (!moduleLoaded) goto loadError; moduleLoaded = true; return true; loadError: freeTmpModule(); moduleFailedToLoad = true; return false; } static void clearTmpModule(void) { memset(pattTmp, 0, sizeof (pattTmp)); memset(instrTmp, 0, sizeof (instrTmp)); memset(&songTmp, 0, sizeof (songTmp)); for (uint32_t i = 0; i < MAX_PATTERNS; i++) pattLensTmp[i] = 64; } static int32_t SDLCALL loadMusicThread(void *ptr) { return doLoadMusic(true); (void)ptr; } void loadMusic(UNICHAR *filenameU) { if (musicIsLoading || filenameU == NULL) return; mouseAnimOn(); musicIsLoading = true; moduleLoaded = false; moduleFailedToLoad = false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); thread = SDL_CreateThread(loadMusicThread, NULL, NULL); if (thread == NULL) { editor.loadMusicEvent = EVENT_NONE; okBox(0, "System message", "Couldn't create thread!"); musicIsLoading = false; return; } SDL_DetachThread(thread); } bool loadMusicUnthreaded(UNICHAR *filenameU, bool autoPlay) { if (filenameU == NULL) return false; clearTmpModule(); // clear stuff from last loading session (very important) UNICHAR_STRCPY(editor.tmpFilenameU, filenameU); editor.loadMusicEvent = EVENT_NONE; doLoadMusic(false); if (moduleLoaded) { setupLoadedModule(); if (autoPlay) startPlaying(PLAYMODE_SONG, 0); return true; } return false; } bool allocateTmpPatt(int32_t nr, uint16_t rows) { pattTmp[nr] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1); if (pattTmp[nr] == NULL) return false; pattLensTmp[nr] = rows; return true; } bool allocateTmpInstr(int16_t nr) { if (instrTmp[nr] != NULL) return false; // already allocated instrTyp *p = (instrTyp *)calloc(1, sizeof (instrTyp)); if (p == NULL) return false; sampleTyp *s = p->samp; for (int32_t i = 0; i < MAX_SMP_PER_INST; i++, s++) { s->pan = 128; s->vol = 64; } instrTmp[nr] = p; return true; } static void freeTmpModule(void) // called on module load error { // free all patterns for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (pattTmp[i] != NULL) { free(pattTmp[i]); pattTmp[i] = NULL; } } // free all instruments and samples for (int32_t i = 1; i <= 256; i++) // if >128 instruments, we fake-load up to 128 extra (and discard them later) { if (instrTmp[i] == NULL) continue; sampleTyp *s = instrTmp[i]->samp; for (int32_t j = 0; j < MAX_SMP_PER_INST; j++, s++) { if (s->origPek != NULL) free(s->origPek); } free(instrTmp[i]); instrTmp[i] = NULL; } } bool tmpPatternEmpty(uint16_t nr) { if (pattTmp[nr] == NULL) return true; uint8_t *scanPtr = (uint8_t *)pattTmp[nr]; const uint32_t scanLen = pattLensTmp[nr] * TRACK_WIDTH; for (uint32_t i = 0; i < scanLen; i++) { if (scanPtr[i] != 0) return false; } return true; } void clearUnusedChannels(tonTyp *p, int16_t pattLen, int32_t antChn) { if (p == NULL || antChn >= MAX_VOICES) return; const int32_t width = sizeof (tonTyp) * (MAX_VOICES - antChn); tonTyp *ptr = &p[antChn]; for (int32_t i = 0; i < pattLen; i++, ptr += MAX_VOICES) memset(ptr, 0, width); } // called from input/video thread after the module was done loading static void setupLoadedModule(void) { lockMixerCallback(); freeAllInstr(); freeAllPatterns(); oldPlayMode = playMode; playMode = PLAYMODE_IDLE; songPlaying = false; #ifdef HAS_MIDI midi.currMIDIVibDepth = 0; midi.currMIDIPitch = 0; #endif memset(editor.keyOnTab, 0, sizeof (editor.keyOnTab)); // copy over new pattern pointers and lengths for (int32_t i = 0; i < MAX_PATTERNS; i++) { patt[i] = pattTmp[i]; pattLens[i] = pattLensTmp[i]; } // copy over song struct memcpy(&song, &songTmp, sizeof (songTyp)); fixSongName(); // copy over new instruments (includes sample pointers) for (int16_t i = 1; i <= MAX_INST; i++) { instr[i] = instrTmp[i]; fixInstrAndSampleNames(i); if (instr[i] != NULL) { for (int32_t j = 0; j < MAX_SMP_PER_INST; j++) { sampleTyp *s = &instr[i]->samp[j]; checkSampleRepeat(s); if (s->pek != NULL) fixSample(s); // prepare sample for branchless linear interpolation } } } // we are the owners of the allocated memory ptrs set by the loader thread now // support non-even channel numbers if (song.antChn & 1) { song.antChn++; if (song.antChn > MAX_VOICES) song.antChn = MAX_VOICES; } if (song.len == 0) song.len = 1; if (song.repS >= song.len) song.repS = 0; song.globVol = 64; setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5); setScrollBarPos(SB_POS_ED, 0, false); resetChannels(); setPos(0, 0, true); P_SetSpeed(song.speed); editor.tmpPattern = editor.editPattern; // set kludge variable editor.speed = song.speed; editor.tempo = song.tempo; editor.timer = song.timer; editor.globalVol = song.globVol; setFrqTab(tmpLinearPeriodsFlag); unlockMixerCallback(); editor.currVolEnvPoint = 0; editor.currPanEnvPoint = 0; refreshScopes(); exitTextEditing(); updateTextBoxPointers(); resetChannelOffset(); updateChanNums(); resetWavRenderer(); clearPattMark(); resetTrimSizes(); resetPlaybackTime(); diskOpSetFilename(DISKOP_ITEM_MODULE, editor.tmpFilenameU); // redraw top part of screen if (ui.extended) { togglePatternEditorExtended(); // exit togglePatternEditorExtended(); // re-enter (force redrawing) } else { // redraw top screen hideTopScreen(); showTopScreen(true); } updateSampleEditorSample(); showBottomScreen(); // redraw bottom screen (also redraws pattern editor) if (ui.instEditorShown) drawPiano(NULL); // redraw piano now (since if playing = wait for next tick update) removeSongModifiedFlag(); moduleFailedToLoad = false; moduleLoaded = false; editor.loadMusicEvent = EVENT_NONE; } bool handleModuleLoadFromArg(int argc, char **argv) { UNICHAR tmpPathU[PATH_MAX+2]; // this is crude, we always expect only one parameter, and that it is the module. if (argc != 2 || argv[1] == NULL || argv[1][0] == '\0') return false; #ifdef __APPLE__ if (argc == 2 && !strncmp(argv[1], "-psn_", 5)) return false; // OS X < 10.9 passes a -psn_x_xxxxx parameter on double-click launch #endif const uint32_t filenameLen = (const uint32_t)strlen(argv[1]); UNICHAR *filenameU = (UNICHAR *)calloc(filenameLen+1, sizeof (UNICHAR)); if (filenameU == NULL) { okBox(0, "System message", "Not enough memory!"); return false; } #ifdef _WIN32 MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, filenameU, filenameLen); #else strcpy(filenameU, argv[1]); #endif // store old path UNICHAR_GETCWD(tmpPathU, PATH_MAX); // set path to where the main executable is UNICHAR_CHDIR(editor.binaryPathU); const int32_t filesize = getFileSize(filenameU); if (filesize == -1 || filesize >= 512L*1024*1024) // 1) >=2GB 2) >=512MB { okBox(0, "System message", "Error: The module is too big to be loaded!"); free(filenameU); UNICHAR_CHDIR(tmpPathU); // set old path back return false; } bool result = loadMusicUnthreaded(filenameU, true); free(filenameU); UNICHAR_CHDIR(tmpPathU); // set old path back return result; } static bool fileIsModule(UNICHAR *pathU) { FILE *f = UNICHAR_FOPEN(pathU, "rb"); if (f == NULL) return false; int8_t modFormat = detectModule(f); fclose(f); /* If the module was not identified (possibly STK type), ** check the file extension and handle it as a module only ** if it starts with "mod."/"stk." or ends with ".mod"/".stk" (case insensitive). */ if (modFormat == FORMAT_POSSIBLY_STK) { char *path = unicharToCp437(pathU, false); if (path == NULL) return false; int32_t pathLen = (int32_t)strlen(path); // get filename from path int32_t i = pathLen; while (i--) { if (path[i] == DIR_DELIMITER) break; } char *filename = path; if (i > 0) filename += i + 1; int32_t filenameLen = (int32_t)strlen(filename); // -------------------------- if (filenameLen > 5) { if (!_strnicmp("mod.", filename, 4) || !_strnicmp("stk.", filename, 4)) { free(path); return true; } if (!_strnicmp(".mod", &filename[filenameLen-4], 4) || !_strnicmp(".stk", &filename[filenameLen-4], 4)) { free(path); return true; } } free(path); return false; } return (modFormat != FORMAT_UNKNOWN); } void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck) { if (ui.sysReqShown || fullPathUTF8 == NULL) return; const int32_t fullPathLen = (const int32_t)strlen(fullPathUTF8); if (fullPathLen == 0) return; UNICHAR *fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR)); if (fullPathU == NULL) { okBox(0, "System message", "Not enough memory!"); return; } #ifdef _WIN32 MultiByteToWideChar(CP_UTF8, 0, fullPathUTF8, -1, fullPathU, fullPathLen); #else strcpy(fullPathU, fullPathUTF8); #endif const int32_t filesize = getFileSize(fullPathU); if (filesize == -1) // >2GB { okBox(0, "System message", "The file is too big and can't be loaded (over 2GB)."); free(fullPathU); return; } if (filesize >= 128L*1024*1024) // 128MB { if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1) { free(fullPathU); return; } } // pass UTF8 to these tests so that we can test file ending in ASCII/ANSI if (fileIsInstr(fullPathU)) { loadInstr(fullPathU); } else if (fileIsModule(fullPathU)) { SDL_RestoreWindow(video.window); if (songModifiedCheck && song.isModified) { // de-minimize window and set focus so that the user sees the message box SDL_RestoreWindow(video.window); SDL_RaiseWindow(video.window); if (!askUnsavedChanges(ASK_TYPE_LOAD_SONG)) { free(fullPathU); return; } } editor.loadMusicEvent = EVENT_LOADMUSIC_DRAGNDROP; loadMusic(fullPathU); } else { loadSample(fullPathU, editor.curSmp, false); } free(fullPathU); } static void handleOldPlayMode(void) { playMode = oldPlayMode; if (oldPlayMode != PLAYMODE_IDLE && oldPlayMode != PLAYMODE_EDIT) startPlaying(oldPlayMode, 0); songPlaying = (playMode >= PLAYMODE_SONG); } // called from input/video thread after module load thread was finished void handleLoadMusicEvents(void) { if (!moduleLoaded && !moduleFailedToLoad) return; // no event to handle if (moduleFailedToLoad) { // module failed to load from loading thread musicIsLoading = false; moduleFailedToLoad = false; moduleLoaded = false; editor.loadMusicEvent = EVENT_NONE; setMouseBusy(false); return; } if (moduleLoaded) { // module was successfully loaded from loading thread switch (editor.loadMusicEvent) { // module dragged and dropped *OR* user double clicked a file associated with FT2 clone case EVENT_LOADMUSIC_DRAGNDROP: { setupLoadedModule(); if (editor.autoPlayOnDrop) startPlaying(PLAYMODE_SONG, 0); else handleOldPlayMode(); } break; // filename passed as an exe argument *OR* user double clicked a file associated with FT2 clone case EVENT_LOADMUSIC_ARGV: { setupLoadedModule(); startPlaying(PLAYMODE_SONG, 0); } break; // module filename pressed in Disk Op. case EVENT_LOADMUSIC_DISKOP: { setupLoadedModule(); handleOldPlayMode(); } break; default: break; } moduleLoaded = false; editor.loadMusicEvent = EVENT_NONE; musicIsLoading = false; mouseAnimOff(); } }