ref: 63402619f6de96adeb1a45dff7a558d745448a1c
dir: /src/pt2_modloader.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 <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <ctype.h> // tolower() #ifdef _WIN32 #include <io.h> #else #include <unistd.h> #endif #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include "pt2_mouse.h" #include "pt2_header.h" #include "pt2_sampler.h" #include "pt2_textout.h" #include "pt2_audio.h" #include "pt2_helpers.h" #include "pt2_visuals.h" #include "pt2_unicode.h" #include "pt2_modloader.h" #include "pt2_sampleloader.h" # typedef struct mem_t { bool _eof; uint8_t *_ptr, *_base; uint32_t _cnt, _bufsiz; } mem_t; static bool oldAutoPlay; static char oldFullPath[(PATH_MAX * 2) + 2]; static uint32_t oldFullPathLen; static module_t *tempMod; extern SDL_Window *window; static mem_t *mopen(const uint8_t *src, uint32_t length); static void mclose(mem_t **buf); static int32_t mgetc(mem_t *buf); static size_t mread(void *buffer, size_t size, size_t count, mem_t *buf); static void mseek(mem_t *buf, int32_t offset, int32_t whence); static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits); void showSongUnsavedAskBox(int8_t askScreenType) { editor.ui.askScreenShown = true; editor.ui.askScreenType = askScreenType; pointerSetMode(POINTER_MODE_MSG1, NO_CARRY); setStatusMessage("SONG IS UNSAVED !", NO_CARRY); renderAskDialog(); } bool modSave(char *fileName) { int16_t tempPatternCount; int32_t i; uint32_t tempLoopLength, tempLoopStart, j, k; note_t tmp; FILE *fmodule; tempPatternCount = 0; fmodule = fopen(fileName, "wb"); if (fmodule == NULL) { displayErrorMsg("FILE I/O ERROR !"); return false; } for (i = 0; i < 20; i++) fputc(tolower(modEntry->head.moduleTitle[i]), fmodule); for (i = 0; i < MOD_SAMPLES; i++) { for (j = 0; j < 22; j++) fputc(tolower(modEntry->samples[i].text[j]), fmodule); fputc(modEntry->samples[i].length >> 9, fmodule); fputc(modEntry->samples[i].length >> 1, fmodule); fputc(modEntry->samples[i].fineTune & 0x0F, fmodule); fputc((modEntry->samples[i].volume > 64) ? 64 : modEntry->samples[i].volume, fmodule); tempLoopLength = modEntry->samples[i].loopLength; if (tempLoopLength < 2) tempLoopLength = 2; tempLoopStart = modEntry->samples[i].loopStart; if (tempLoopLength == 2) tempLoopStart = 0; fputc(tempLoopStart >> 9, fmodule); fputc(tempLoopStart >> 1, fmodule); fputc(tempLoopLength >> 9, fmodule); fputc(tempLoopLength >> 1, fmodule); } fputc(modEntry->head.orderCount & 0x00FF, fmodule); fputc(0x7F, fmodule); // ProTracker puts 0x7F at this place (restart pos/BPM in other trackers) for (i = 0; i < MOD_ORDERS; i++) fputc(modEntry->head.order[i] & 0xFF, fmodule); tempPatternCount = 0; for (i = 0; i < MOD_ORDERS; i++) { if (tempPatternCount < modEntry->head.order[i]) tempPatternCount = modEntry->head.order[i]; } if (++tempPatternCount > MAX_PATTERNS) tempPatternCount = MAX_PATTERNS; fwrite((tempPatternCount <= 64) ? "M.K." : "M!K!", 1, 4, fmodule); for (i = 0; i < tempPatternCount; i++) { for (j = 0; j < MOD_ROWS; j++) { for (k = 0; k < AMIGA_VOICES; k++) { tmp = modEntry->patterns[i][(j * AMIGA_VOICES) + k]; fputc((tmp.sample & 0xF0) | ((tmp.period >> 8) & 0x0F), fmodule); fputc(tmp.period & 0xFF, fmodule); fputc(((tmp.sample << 4) & 0xF0) | (tmp.command & 0x0F), fmodule); fputc(tmp.param, fmodule); } } } for (i = 0; i < MOD_SAMPLES; i++) { // Amiga ProTracker stuck "BEEP" sample fix if (modEntry->samples[i].length >= 2 && modEntry->samples[i].loopStart+modEntry->samples[i].loopLength == 2) { fputc(0, fmodule); fputc(0, fmodule); k = modEntry->samples[i].length; for (j = 2; j < k; j++) fputc(modEntry->sampleData[modEntry->samples[i].offset+j], fmodule); } else { fwrite(&modEntry->sampleData[MAX_SAMPLE_LEN * i], 1, modEntry->samples[i].length, fmodule); } } fclose(fmodule); displayMsg("MODULE SAVED !"); setMsgPointer(); editor.diskop.cached = false; if (editor.ui.diskOpScreenShown) editor.ui.updateDiskOpFileList = true; updateWindowTitle(MOD_NOT_MODIFIED); return true; } static int8_t checkModType(const char *buf) { if (!strncmp(buf, "M.K.", 4)) return FORMAT_MK; // ProTracker v1.x, handled as ProTracker v2.x else if (!strncmp(buf, "M!K!", 4)) return FORMAT_MK2; // ProTracker v2.x (if >64 patterns) else if (!strncmp(buf, "FLT4", 4)) return FORMAT_FLT4; // StarTrekker (4ch), handled as ProTracker v2.x else if (!strncmp(buf, "1CHN", 4)) return FORMAT_1CHN; // handled as 4ch else if (!strncmp(buf, "2CHN", 4)) return FORMAT_2CHN; // FastTracker II, handled as 4ch else if (!strncmp(buf, "3CHN", 4)) return FORMAT_3CHN; // handled as 4ch else if (!strncmp(buf, "4CHN", 4)) return FORMAT_4CHN; // rare type, not sure what tracker it comes from else if (!strncmp(buf, "N.T.", 4)) return FORMAT_MK; // NoiseTracker 1.0, handled as ProTracker v2.x else if (!strncmp(buf, "M&K!", 4)) return FORMAT_FEST; // Special NoiseTracker format (used in music disks?) else if (!strncmp(buf, "FEST", 4)) return FORMAT_FEST; // Special NoiseTracker format (used in music disks?) else if (!strncmp(buf, "NSMS", 4)) return FORMAT_MK; // OpenMPT Load_mod.cpp: "kingdomofpleasure.mod by bee hunter" else if (!strncmp(buf, "LARD", 4)) return FORMAT_MK; // OpenMPT Load_mod.cpp: "judgement_day_gvine.mod by 4-mat" else if (!strncmp(buf, "PATT", 4)) return FORMAT_MK; // OpenMPT Load_mod.cpp: "ProTracker 3.6" return FORMAT_UNKNOWN; // may be The Ultimate SoundTracker, 15 samples } // converts zeroes to spaces in a string, up until the last zero found static void fixZeroesInString(char *str, uint32_t maxLength) { int32_t i; for (i = maxLength-1; i >= 0; i--) { if (str[i] != '\0') break; } // convert zeroes to spaces if (i > 0) { for (int32_t j = 0; j < i; j++) { if (str[j] == '\0') str[j] = ' '; } } } module_t *modLoad(UNICHAR *fileName) { bool mightBeSTK, lateSTKVerFlag, veryLateSTKVerFlag; char modSig[4], tmpChar; int8_t numSamples; uint8_t ppCrunchData[4], bytes[4], *ppBuffer; uint8_t *modBuffer, ch, row, pattern, channels; uint16_t ciaPeriod; int32_t i, loopStart, loopLength, loopOverflowVal; uint32_t j, PP20, ppPackLen, ppUnpackLen; FILE *fmodule; module_t *newModule; moduleSample_t *s; note_t *note; mem_t *mod; /* these flags are kinda dumb and inaccurate, but we ** don't aim for excellent STK import anyway. */ veryLateSTKVerFlag = false; // "DFJ SoundTracker III" nad later lateSTKVerFlag = false; // "TJC SoundTracker II" and later mightBeSTK = false; mod = NULL; ppBuffer = NULL; modBuffer = NULL; fmodule = NULL; newModule = NULL; newModule = (module_t *)calloc(1, sizeof (module_t)); if (newModule == NULL) { statusOutOfMemory(); goto modLoadError; } fmodule = UNICHAR_FOPEN(fileName, "rb"); if (fmodule == NULL) { displayErrorMsg("FILE I/O ERROR !"); goto modLoadError; } fseek(fmodule, 0, SEEK_END); newModule->head.moduleSize = ftell(fmodule); fseek(fmodule, 0, SEEK_SET); // check if mod is a powerpacker mod fread(&PP20, 4, 1, fmodule); if (PP20 == 0x30325850) // "PX20" { displayErrorMsg("ENCRYPTED PPACK !"); goto modLoadError; } else if (PP20 == 0x30325050) // "PP20" { ppPackLen = newModule->head.moduleSize; if (ppPackLen & 3) { displayErrorMsg("POWERPACKER ERROR"); goto modLoadError; } fseek(fmodule, ppPackLen - 4, SEEK_SET); ppCrunchData[0] = (uint8_t)fgetc(fmodule); ppCrunchData[1] = (uint8_t)fgetc(fmodule); ppCrunchData[2] = (uint8_t)fgetc(fmodule); ppCrunchData[3] = (uint8_t)fgetc(fmodule); ppUnpackLen = (ppCrunchData[0] << 16) | (ppCrunchData[1] << 8) | ppCrunchData[2]; // smallest and biggest possible .MOD if (ppUnpackLen < 2108 || ppUnpackLen > 4195326) { displayErrorMsg("NOT A MOD FILE !"); goto modLoadError; } ppBuffer = (uint8_t *)malloc(ppPackLen); if (ppBuffer == NULL) { statusOutOfMemory(); goto modLoadError; } modBuffer = (uint8_t *)malloc(ppUnpackLen); if (modBuffer == NULL) { statusOutOfMemory(); goto modLoadError; } fseek(fmodule, 0, SEEK_SET); fread(ppBuffer, 1, ppPackLen, fmodule); fclose(fmodule); ppdecrunch(ppBuffer + 8, modBuffer, ppBuffer + 4, ppPackLen - 12, ppUnpackLen, ppCrunchData[3]); free(ppBuffer); newModule->head.moduleSize = ppUnpackLen; } else { // smallest and biggest possible PT .MOD if (newModule->head.moduleSize < 2108 || newModule->head.moduleSize > 4195326) { displayErrorMsg("NOT A MOD FILE !"); goto modLoadError; } modBuffer = (uint8_t *)malloc(newModule->head.moduleSize); if (modBuffer == NULL) { statusOutOfMemory(); goto modLoadError; } fseek(fmodule, 0, SEEK_SET); fread(modBuffer, 1, newModule->head.moduleSize, fmodule); fclose(fmodule); } mod = mopen(modBuffer, newModule->head.moduleSize); if (mod == NULL) { displayErrorMsg("FILE I/O ERROR !"); goto modLoadError; } // check module tag mseek(mod, 0x0438, SEEK_SET); mread(modSig, 1, 4, mod); newModule->head.format = checkModType(modSig); if (newModule->head.format == FORMAT_UNKNOWN) mightBeSTK = true; if (newModule->head.format == FORMAT_1CHN) channels = 1; else if (newModule->head.format == FORMAT_2CHN) channels = 2; else if (newModule->head.format == FORMAT_3CHN) channels = 3; else channels = 4; mseek(mod, 0, SEEK_SET); mread(newModule->head.moduleTitle, 1, 20, mod); newModule->head.moduleTitle[20] = '\0'; for (i = 0; i < 20; i++) { tmpChar = newModule->head.moduleTitle[i]; if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0') tmpChar = ' '; newModule->head.moduleTitle[i] = (char)tolower(tmpChar); } fixZeroesInString(newModule->head.moduleTitle, 20); // read sample information for (i = 0; i < MOD_SAMPLES; i++) { s = &newModule->samples[i]; if (mightBeSTK && i > 14) { s->loopLength = 2; } else { mread(s->text, 1, 22, mod); s->text[22] = '\0'; for (j = 0; j < 22; j++) { tmpChar = s->text[j]; if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0') tmpChar = ' '; s->text[j] = (char)tolower(tmpChar); } fixZeroesInString(s->text, 22); s->realLength = ((mgetc(mod) << 8) | mgetc(mod)) * 2; if (s->realLength > MAX_SAMPLE_LEN) s->length = MAX_SAMPLE_LEN; else s->length = (uint16_t)s->realLength; if (s->length > 9999) lateSTKVerFlag = true; // Only used if mightBeSTK is set if (newModule->head.format == FORMAT_FEST) s->fineTune = (uint8_t)((-mgetc(mod) & 0x1F) / 2); // One more bit of precision, + inverted else s->fineTune = (uint8_t)mgetc(mod) & 0x0F; s->volume = (int8_t)mgetc(mod); s->volume = CLAMP(s->volume, 0, 64); loopStart = ((mgetc(mod) << 8) | mgetc(mod)) * 2; loopLength = ((mgetc(mod) << 8) | mgetc(mod)) * 2; if (loopStart > MAX_SAMPLE_LEN || loopStart+loopLength > MAX_SAMPLE_LEN) { s->loopStart = 0; s->loopLength = 2; } else { s->loopStart = (uint16_t)loopStart; s->loopLength = (uint16_t)loopLength; } if (mightBeSTK) s->loopStart /= 2; if (s->loopLength < 2) s->loopLength = 2; // fix for poorly converted STK->PTMOD modules. if (!mightBeSTK && s->loopLength > 2 && s->loopStart+s->loopLength > s->length) { if ((s->loopStart/2) + s->loopLength <= s->length) s->loopStart /= 2; } if (mightBeSTK) { if (s->loopLength > 2 && s->loopStart < s->length) { s->tmpLoopStart = s->loopStart; // for sample data reading later on s->length -= s->loopStart; s->realLength -= s->loopStart; s->loopStart = 0; } // no finetune in STK/UST s->fineTune = 0; } // some modules are broken like this, adjust sample length if possible (this is ok if we have room) if (s->loopLength > 2 && s->loopStart+s->loopLength > s->length) { loopOverflowVal = (s->loopStart+s->loopLength) - s->length; if (s->length+loopOverflowVal <= MAX_SAMPLE_LEN) { s->length += loopOverflowVal; // this is safe, we're calloc()'ing 65535*(31+1) bytes } else { s->loopStart = 0; s->loopLength = 2; } } } } // STK 2.5 had loopStart in words, not bytes. Convert if late version STK. for (i = 0; i < 15; i++) { if (mightBeSTK && lateSTKVerFlag) { s = &newModule->samples[i]; if (s->loopStart > 2) { s->length -= s->tmpLoopStart; s->tmpLoopStart *= 2; } } } newModule->head.orderCount = (uint8_t)mgetc(mod); // fixes beatwave.mod (129 orders) and other weird MODs if (newModule->head.orderCount > 128) { if (newModule->head.orderCount > 129) { displayErrorMsg("NOT A MOD FILE !"); goto modLoadError; } newModule->head.orderCount = 128; } if (newModule->head.orderCount == 0) { displayErrorMsg("NOT A MOD FILE !"); goto modLoadError; } newModule->head.restartPos = (uint8_t)mgetc(mod); if (mightBeSTK && (newModule->head.restartPos == 0 || newModule->head.restartPos > 220)) { displayErrorMsg("NOT A MOD FILE !"); goto modLoadError; } if (mightBeSTK) { /* If we're still here at this point and the mightBeSTK flag is set, ** then it's definitely a proper The Ultimate SoundTracker (STK) module. */ newModule->head.format = FORMAT_STK; if (newModule->head.restartPos != 120) // 120 is a special case and means 50Hz (125BPM) { if (newModule->head.restartPos > 239) newModule->head.restartPos = 239; // convert UST tempo to BPM ciaPeriod = (240 - newModule->head.restartPos) * 122; newModule->head.initBPM = (uint16_t)round(((double)CIA_PAL_CLK / ciaPeriod) * (125.0 / 50.0)); } newModule->head.restartPos = 0; } for (i = 0; i < MOD_ORDERS; i++) { newModule->head.order[i] = (int16_t)mgetc(mod); if (newModule->head.order[i] > newModule->head.patternCount) newModule->head.patternCount = newModule->head.order[i]; } if (++newModule->head.patternCount > MAX_PATTERNS) { displayErrorMsg("UNSUPPORTED MOD !"); goto modLoadError; } if (newModule->head.format != FORMAT_STK) // The Ultimate SoundTracker MODs doesn't have this tag mseek(mod, 4, SEEK_CUR); // we already read/tested the tag earlier, skip it // init 100 patterns and load patternCount of patterns for (pattern = 0; pattern < MAX_PATTERNS; pattern++) { newModule->patterns[pattern] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t)); if (newModule->patterns[pattern] == NULL) { statusOutOfMemory(); goto modLoadError; } } // load pattern data for (pattern = 0; pattern < newModule->head.patternCount; pattern++) { note = newModule->patterns[pattern]; for (row = 0; row < MOD_ROWS; row++) { for (ch = 0; ch < channels; ch++) { mread(bytes, 1, 4, mod); note->period = ((bytes[0] & 0x0F) << 8) | bytes[1]; note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4); // don't (!) clamp, the player checks for invalid samples note->command = bytes[2] & 0x0F; note->param = bytes[3]; if (mightBeSTK) { if (note->command == 0xC || note->command == 0xD || note->command == 0xE) { // "TJC SoundTracker II" and later lateSTKVerFlag = true; } if (note->command == 0xF) { // "DFJ SoundTracker III" and later lateSTKVerFlag = true; veryLateSTKVerFlag = true; } } note++; } if (channels < 4) note += AMIGA_VOICES - channels; } } /* TODO: Find out if song is FORMAT_NT through heuristics ** Only detected for FEST songs for now. */ // pattern command conversion if (mightBeSTK || newModule->head.format == FORMAT_4CHN || newModule->head.format == FORMAT_NT || newModule->head.format == FORMAT_FEST) { for (pattern = 0; pattern < newModule->head.patternCount; pattern++) { note = newModule->patterns[pattern]; for (j = 0; j < MOD_ROWS*4; j++) { if (newModule->head.format == FORMAT_NT || newModule->head.format == FORMAT_FEST) { // any Dxx == D00 in N.T./FEST modules if (note->command == 0xD) note->param = 0; // effect F with param 0x00 does nothing in NT if (note->command == 0xF && note->param == 0) note->command = 0; } else if (mightBeSTK) { // convert STK effects to PT effects if (!lateSTKVerFlag) { // old SoundTracker 1.x commands if (note->command == 1) { // arpeggio note->command = 0; } else if (note->command == 2) { // pitch slide if (note->param & 0xF0) { // pitch slide down note->command = 2; note->param >>= 4; } else if (note->param & 0x0F) { // pitch slide up note->command = 1; } } } else { // "DFJ SoundTracker II" or later if (note->command == 0xD) { if (veryLateSTKVerFlag) // "DFJ SoundTracker III" or later { // pattern break w/ no param (param must be cleared to fix some songs) note->param = 0; } else { // volume slide note->command = 0xA; } } } } else if (newModule->head.format == FORMAT_4CHN) // 4CHN != PT MOD { // remove E8x (pan) commands as these are Karplus-Strong in ProTracker if (note->command == 0xE && (note->param >> 4) == 0x8) { note->command = 0; note->param = 0; } // effect F with param 0x00 does nothing in these 4CHN formats if (note->command == 0xF && note->param == 0) { note->command = 0; note->param = 0; } } note++; } } } for (i = 0; i < MOD_SAMPLES; i++) newModule->samples[i].offset = MAX_SAMPLE_LEN * i; newModule->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN); // +2 sample slots for overflow safety (Paula and scopes) if (newModule->sampleData == NULL) { statusOutOfMemory(); goto modLoadError; } // load sample data numSamples = (newModule->head.format == FORMAT_STK) ? 15 : 31; for (i = 0; i < numSamples; i++) { s = &newModule->samples[i]; if (mightBeSTK && (s->loopLength > 2 && s->loopLength < s->length)) mseek(mod, s->tmpLoopStart, SEEK_CUR); mread(&newModule->sampleData[s->offset], 1, s->length, mod); if (s->realLength > s->length) mseek(mod, s->realLength - s->length, SEEK_CUR); // fix beeping samples if (s->length >= 2 && s->loopStart+s->loopLength <= 2) { newModule->sampleData[s->offset+0] = 0; newModule->sampleData[s->offset+1] = 0; } } mclose(&mod); free(modBuffer); for (i = 0; i < AMIGA_VOICES; i++) newModule->channels[i].n_chanindex = i; return newModule; modLoadError: if (mod != NULL) mclose(&mod); if (modBuffer != NULL) free(modBuffer); if (ppBuffer != NULL) free(ppBuffer); if (newModule != NULL) { for (i = 0; i < MAX_PATTERNS; i++) { if (newModule->patterns[i] != NULL) free(newModule->patterns[i]); } free(newModule); } return NULL; } bool saveModule(bool checkIfFileExist, bool giveNewFreeFilename) { char fileName[128], tmpBuffer[64]; uint16_t i; struct stat statBuffer; memset(fileName, 0, sizeof (fileName)); if (ptConfig.modDot) { // extension.filename if (*modEntry->head.moduleTitle == '\0') { strcat(fileName, "mod.untitled"); } else { strcat(fileName, "mod."); for (i = 4; i < 20+4; i++) { fileName[i] = (char)tolower(modEntry->head.moduleTitle[i-4]); if (fileName[i] == '\0') break; sanitizeFilenameChar(&fileName[i]); } } } else { // filename.extension if (*modEntry->head.moduleTitle == '\0') { strcat(fileName, "untitled.mod"); } else { for (i = 0; i < 20; i++) { fileName[i] = (char)tolower(modEntry->head.moduleTitle[i]); if (fileName[i] == '\0') break; sanitizeFilenameChar(&fileName[i]); } strcat(fileName, ".mod"); } } if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0) { for (uint16_t j = 1; j <= 9999; j++) { memset(fileName, 0, sizeof (fileName)); if (ptConfig.modDot) { // extension.filename if (*modEntry->head.moduleTitle == '\0') { sprintf(fileName, "mod.untitled-%d", j); } else { for (i = 0; i < 20; i++) { tmpBuffer[i] = (char)tolower(modEntry->head.moduleTitle[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } sprintf(fileName, "mod.%s-%d", tmpBuffer, j); } } else { // filename.extension if (*modEntry->head.moduleTitle == '\0') { sprintf(fileName, "untitled-%d.mod", j); } else { for (i = 0; i < 20; i++) { tmpBuffer[i] = (char)tolower(modEntry->head.moduleTitle[i]); if (tmpBuffer[i] == '\0') break; sanitizeFilenameChar(&tmpBuffer[i]); } sprintf(fileName, "%s-%d.mod", tmpBuffer, j); } } if (stat(fileName, &statBuffer) != 0) break; } } if (checkIfFileExist) { if (stat(fileName, &statBuffer) == 0) { editor.ui.askScreenShown = true; editor.ui.askScreenType = ASK_SAVEMOD_OVERWRITE; pointerSetMode(POINTER_MODE_MSG1, NO_CARRY); setStatusMessage("OVERWRITE FILE ?", NO_CARRY); renderAskDialog(); return -1; } } if (editor.ui.askScreenShown) { editor.ui.answerNo = false; editor.ui.answerYes = false; editor.ui.askScreenShown = false; } return modSave(fileName); } static mem_t *mopen(const uint8_t *src, uint32_t length) { mem_t *b; if (src == NULL || length == 0) return NULL; b = (mem_t *)malloc(sizeof (mem_t)); if (b == NULL) return NULL; b->_base = (uint8_t *)src; b->_ptr = (uint8_t *)src; b->_cnt = length; b->_bufsiz = length; b->_eof = false; return b; } static void mclose(mem_t **buf) { if (*buf != NULL) { free(*buf); *buf = NULL; } } static int32_t mgetc(mem_t *buf) { int32_t b; if (buf == NULL || buf->_ptr == NULL || buf->_cnt <= 0) return 0; b = *buf->_ptr; buf->_cnt--; buf->_ptr++; if (buf->_cnt <= 0) { buf->_ptr = buf->_base + buf->_bufsiz; buf->_cnt = 0; buf->_eof = true; } return (int32_t)b; } static size_t mread(void *buffer, size_t size, size_t count, mem_t *buf) { int32_t pcnt; size_t wrcnt; if (buf == NULL || buf->_ptr == NULL) return 0; wrcnt = size * count; if (size == 0 || buf->_eof) return 0; pcnt = (buf->_cnt > (uint32_t)wrcnt) ? (uint32_t)wrcnt : buf->_cnt; memcpy(buffer, buf->_ptr, pcnt); buf->_cnt -= pcnt; buf->_ptr += pcnt; if (buf->_cnt <= 0) { buf->_ptr = buf->_base + buf->_bufsiz; buf->_cnt = 0; buf->_eof = true; } return pcnt/size; } static void mseek(mem_t *buf, int32_t offset, int32_t whence) { if (buf == NULL) return; if (buf->_base) { switch (whence) { case SEEK_SET: buf->_ptr = buf->_base + offset; break; case SEEK_CUR: buf->_ptr += offset; break; case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break; default: break; } buf->_eof = false; if (buf->_ptr >= buf->_base+buf->_bufsiz) { buf->_ptr = buf->_base+buf->_bufsiz; buf->_eof = true; } buf->_cnt = (buf->_base+buf->_bufsiz) - buf->_ptr; } } /* Code taken from Heikki Orsila's amigadepack. Seems to have no license, ** so I'll assume it fits into wtfpl (wtfpl.net). Heikki should contact me ** if it shall not. ** Modified by 8bitbubsy */ #define PP_READ_BITS(nbits, var) \ bitCnt = (nbits); \ while (bitsLeft < bitCnt) { \ if (bufSrc < src) return false; \ bitBuffer |= (*--bufSrc << bitsLeft); \ bitsLeft += 8; \ } \ (var) = 0; \ bitsLeft -= bitCnt; \ while (bitCnt--) { \ (var) = ((var) << 1) | (bitBuffer & 1); \ bitBuffer >>= 1; \ } \ static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits) { uint8_t *bufSrc, *dstEnd, *out, bitsLeft, bitCnt; uint32_t x, todo, offBits, offset, written, bitBuffer; if (src == NULL || dst == NULL || offsetLens == NULL) return false; bitsLeft = 0; bitBuffer = 0; written = 0; bufSrc = src + srcLen; out = dst + dstLen; dstEnd = out; PP_READ_BITS(skipBits, x); while (written < dstLen) { PP_READ_BITS(1, x); if (x == 0) { todo = 1; do { PP_READ_BITS(2, x); todo += x; } while (x == 3); while (todo--) { PP_READ_BITS(8, x); if (out <= dst) return false; *--out = (uint8_t)x; written++; } if (written == dstLen) break; } PP_READ_BITS(2, x); offBits = offsetLens[x]; todo = x + 2; if (x == 3) { PP_READ_BITS(1, x); if (x == 0) offBits = 7; PP_READ_BITS((uint8_t)offBits, offset); do { PP_READ_BITS(3, x); todo += x; } while (x == 7); } else { PP_READ_BITS((uint8_t)offBits, offset); } if (out+offset >= dstEnd) return false; while (todo--) { x = out[offset]; if (out <= dst) return false; *--out = (uint8_t)x; written++; } } return true; } void setupNewMod(void) { int8_t i; // setup GUI text pointers for (i = 0; i < MOD_SAMPLES; i++) { modEntry->samples[i].volumeDisp = &modEntry->samples[i].volume; modEntry->samples[i].lengthDisp = &modEntry->samples[i].length; modEntry->samples[i].loopStartDisp = &modEntry->samples[i].loopStart; modEntry->samples[i].loopLengthDisp = &modEntry->samples[i].loopLength; fillSampleRedoBuffer(i); } modSetPos(0, 0); modSetPattern(0); // set pattern to 00 instead of first order's pattern editor.currEditPatternDisp = &modEntry->currPattern; editor.currPosDisp = &modEntry->currOrder; editor.currPatternDisp = &modEntry->head.order[0]; editor.currPosEdPattDisp = &modEntry->head.order[0]; editor.currLengthDisp = &modEntry->head.orderCount; // calculate MOD size editor.ui.updateSongSize = true; editor.muted[0] = false; editor.muted[1] = false; editor.muted[2] = false; editor.muted[3] = false; editor.editMoveAdd = 1; editor.currSample = 0; editor.musicTime = 0; editor.modLoaded = true; editor.blockMarkFlag = false; editor.sampleZero = false; editor.keypadSampleOffset = 0; setLEDFilter(false); // real PT doesn't do this, but that's insane updateWindowTitle(MOD_NOT_MODIFIED); modSetSpeed(6); if (modEntry->head.initBPM > 0) modSetTempo(modEntry->head.initBPM); else modSetTempo(125); updateCurrSample(); editor.samplePos = 0; updateSamplePos(); } void loadModFromArg(char *arg) { uint32_t filenameLen; UNICHAR *filenameU; editor.ui.introScreenShown = false; statusAllRight(); filenameLen = (uint32_t)strlen(arg); filenameU = (UNICHAR *)calloc((filenameLen + 2), sizeof (UNICHAR)); if (filenameU == NULL) { statusOutOfMemory(); return; } #ifdef _WIN32 MultiByteToWideChar(CP_UTF8, 0, arg, -1, filenameU, filenameLen); #else strcpy(filenameU, arg); #endif tempMod = modLoad(filenameU); if (tempMod != NULL) { modEntry->moduleLoaded = false; modFree(); modEntry = tempMod; setupNewMod(); modEntry->moduleLoaded = true; } else { editor.errorMsgActive = true; editor.errorMsgBlock = true; editor.errorMsgCounter = 0; // status/error message is set in the mod loader setErrPointer(); } free(filenameU); } static bool testExtension(char *ext, uint8_t extLen, char *fullPath) { // checks for EXT.filename and filename.EXT char *fileName, begStr[8], endStr[8]; uint32_t fileNameLen; extLen++; // add one to length (dot) fileName = strrchr(fullPath, DIR_DELIMITER); if (fileName != NULL) fileName++; else fileName = fullPath; fileNameLen = (uint32_t)strlen(fileName); if (fileNameLen >= extLen) { sprintf(begStr, "%s.", ext); if (!_strnicmp(begStr, fileName, extLen)) return true; sprintf(endStr, ".%s", ext); if (!_strnicmp(endStr, fileName + (fileNameLen - extLen), extLen)) return true; } return false; } void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck) { bool isMod; char *fileName, *ansiName; uint8_t oldMode, oldPlayMode; UNICHAR *fullPathU; // don't allow drag n' drop if the tracker is busy if (editor.ui.pointerMode == POINTER_MODE_MSG1 || editor.diskop.isFilling || editor.isWAVRendering || editor.ui.samplerFiltersBoxShown || editor.ui.samplerVolBoxShown) { return; } ansiName = (char *)calloc(fullPathLen + 10, sizeof (char)); if (ansiName == NULL) { statusOutOfMemory(); return; } fullPathU = (UNICHAR *)calloc((fullPathLen + 2), sizeof (UNICHAR)); if (fullPathU == NULL) { statusOutOfMemory(); return; } #ifdef _WIN32 MultiByteToWideChar(CP_UTF8, 0, fullPath, -1, fullPathU, fullPathLen); #else strcpy(fullPathU, fullPath); #endif unicharToAnsi(ansiName, fullPathU, fullPathLen); // make a new pointer point to filename (strip path) fileName = strrchr(ansiName, DIR_DELIMITER); if (fileName != NULL) fileName++; else fileName = ansiName; // check if the file extension is a module (FIXME: check module by content instead..?) isMod = false; if (testExtension("MOD", 3, fileName)) isMod = true; else if (testExtension("M15", 3, fileName)) isMod = true; else if (testExtension("STK", 3, fileName)) isMod = true; else if (testExtension("NST", 3, fileName)) isMod = true; else if (testExtension("UST", 3, fileName)) isMod = true; else if (testExtension("PP", 2, fileName)) isMod = true; else if (testExtension("NT", 2, fileName)) isMod = true; if (isMod) { if (songModifiedCheck && modEntry->modified) { free(ansiName); free(fullPathU); memcpy(oldFullPath, fullPath, fullPathLen); oldFullPath[fullPathLen+0] = 0; oldFullPath[fullPathLen+1] = 0; oldFullPathLen = fullPathLen; oldAutoPlay = autoPlay; // de-minimize window and set focus so that the user sees the message box SDL_RestoreWindow(window); SDL_RaiseWindow(window); showSongUnsavedAskBox(ASK_DISCARD_SONG_DRAGNDROP); return; } tempMod = modLoad(fullPathU); if (tempMod != NULL) { oldMode = editor.currMode; oldPlayMode = editor.playMode; modStop(); modFree(); modEntry = tempMod; setupNewMod(); modEntry->moduleLoaded = true; statusAllRight(); if (autoPlay) { // start normal playback editor.playMode = PLAY_MODE_NORMAL; modPlay(DONT_SET_PATTERN, 0, 0); editor.currMode = MODE_PLAY; pointerSetMode(POINTER_MODE_PLAY, DO_CARRY); } else if ((oldMode == MODE_PLAY) || (oldMode == MODE_RECORD)) { // use last mode editor.playMode = oldPlayMode; if ((oldPlayMode == PLAY_MODE_PATTERN) || (oldMode == MODE_RECORD)) modPlay(0, 0, 0); else modPlay(DONT_SET_PATTERN, 0, 0); editor.currMode = oldMode; if (oldMode == MODE_RECORD) pointerSetMode(POINTER_MODE_RECORD, DO_CARRY); else pointerSetMode(POINTER_MODE_PLAY, DO_CARRY); } else { // stop playback editor.playMode = PLAY_MODE_NORMAL; editor.currMode = MODE_IDLE; editor.songPlaying = false; pointerSetMode(POINTER_MODE_IDLE, DO_CARRY); } displayMainScreen(); } else { editor.errorMsgActive = true; editor.errorMsgBlock = true; editor.errorMsgCounter = 0; setErrPointer(); // status/error message is set in the mod loader } } else { loadSample(fullPathU, fileName); } free(ansiName); free(fullPathU); } void loadDroppedFile2(void) { loadDroppedFile(oldFullPath, oldFullPathLen, oldAutoPlay, false); } module_t *createNewMod(void) { uint8_t i; module_t *newMod; newMod = (module_t *)calloc(1, sizeof (module_t)); if (newMod == NULL) goto oom; for (i = 0; i < MAX_PATTERNS; i++) { newMod->patterns[i] = (note_t *)calloc(1, MOD_ROWS * sizeof (note_t) * AMIGA_VOICES); if (newMod->patterns[i] == NULL) goto oom; } newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN); // +2 sample slots for overflow safety (Paula and scopes) if (newMod->sampleData == NULL) goto oom; newMod->head.orderCount = 1; newMod->head.patternCount = 1; for (i = 0; i < MOD_SAMPLES; i++) { newMod->samples[i].offset = MAX_SAMPLE_LEN * i; newMod->samples[i].loopLength = 2; // setup GUI text pointers newMod->samples[i].volumeDisp = &newMod->samples[i].volume; newMod->samples[i].lengthDisp = &newMod->samples[i].length; newMod->samples[i].loopStartDisp = &newMod->samples[i].loopStart; newMod->samples[i].loopLengthDisp = &newMod->samples[i].loopLength; } for (i = 0; i < AMIGA_VOICES; i++) newMod->channels[i].n_chanindex = i; // setup GUI text pointers editor.currEditPatternDisp = &newMod->currPattern; editor.currPosDisp = &newMod->currOrder; editor.currPatternDisp = &newMod->head.order[0]; editor.currPosEdPattDisp = &newMod->head.order[0]; editor.currLengthDisp = &newMod->head.orderCount; editor.ui.updateSongSize = true; return newMod; oom: showErrorMsgBox("Out of memory!"); return NULL; }