ref: 0d1a2cf15c023a03f0ee62017133b7035e142a56
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;
} MEMFILE;
static bool oldAutoPlay;
static char oldFullPath[(PATH_MAX * 2) + 2];
static uint32_t oldFullPathLen;
static module_t *tempMod;
extern SDL_Window *window;
static MEMFILE *mopen(const uint8_t *src, uint32_t length);
static void mclose(MEMFILE **buf);
static int32_t mgetc(MEMFILE *buf);
static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf);
static void mseek(MEMFILE *buf, int32_t offset, int32_t whence);
static void mrewind(MEMFILE *buf);
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;
}
#define IS_ID(s, b) !strncmp(s, b, 4)
static uint8_t getModType(uint8_t *numChannels, const char *id)
{
*numChannels = 4;
if (IS_ID("M.K.", id) || IS_ID("M!K!", id) || IS_ID("NSMS", id) || IS_ID("LARD", id) || IS_ID("PATT", id))
{
return FORMAT_MK; // ProTracker (or compatible)
}
else if (IS_ID("FLT4", id))
{
return FORMAT_FLT; // Startrekker (4 channels)
}
else if (IS_ID("N.T.", id))
{
return FORMAT_NT; // NoiseTracker
}
else if (IS_ID("M&K!", id) || IS_ID("FEST", id))
{
return FORMAT_HMNT; // His Master's NoiseTracker
}
else if (id[1] == 'C' && id[2] == 'H' && id[3] == 'N')
{
*numChannels = id[0] - '0';
return FORMAT_FT2; // Fasttracker II 1..9 channels (or other trackers)
}
return FORMAT_UNKNOWN; // may be The Ultimate Soundtracker (set to FORMAT_STK later)
}
// 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] = ' ';
}
}
}
static uint8_t *unpackPPModule(FILE *f, uint32_t *filesize)
{
uint8_t *modBuffer, ppCrunchData[4], *ppBuffer;
uint32_t ppPackLen, ppUnpackLen;
ppPackLen = *filesize;
if ((ppPackLen & 3) || ppPackLen <= 12)
{
displayErrorMsg("POWERPACKER ERROR");
return NULL;
}
ppBuffer = (uint8_t *)malloc(ppPackLen);
if (ppBuffer == NULL)
{
statusOutOfMemory();
return NULL;
}
fseek(f, ppPackLen-4, SEEK_SET);
ppCrunchData[0] = (uint8_t)fgetc(f);
ppCrunchData[1] = (uint8_t)fgetc(f);
ppCrunchData[2] = (uint8_t)fgetc(f);
ppCrunchData[3] = (uint8_t)fgetc(f);
ppUnpackLen = (ppCrunchData[0] << 16) | (ppCrunchData[1] << 8) | ppCrunchData[2];
modBuffer = (uint8_t *)malloc(ppUnpackLen);
if (modBuffer == NULL)
{
free(ppBuffer);
statusOutOfMemory();
return NULL;
}
rewind(f);
fread(ppBuffer, 1, ppPackLen, f);
fclose(f);
if (!ppdecrunch(ppBuffer+8, modBuffer, ppBuffer+4, ppPackLen-12, ppUnpackLen, ppCrunchData[3]))
{
free(ppBuffer);
displayErrorMsg("POWERPACKER ERROR");
return NULL;
}
free(ppBuffer);
*filesize = ppUnpackLen;
return modBuffer;
}
module_t *modLoad(UNICHAR *fileName)
{
bool mightBeSTK, lateSTKVerFlag, veryLateSTKVerFlag;
char modID[4], tmpChar;
int8_t numSamples;
uint8_t bytes[4], restartPos, modFormat;
uint8_t *modBuffer, numChannels;
int32_t i, j, k, loopStart, loopLength, loopOverflowVal, numPatterns;
uint32_t powerPackerID, filesize;
FILE *f;
MEMFILE *m;
module_t *newMod;
moduleSample_t *s;
note_t *note;
veryLateSTKVerFlag = false; // "DFJ SoundTracker III" and later
lateSTKVerFlag = false; // "TJC SoundTracker II" and later
mightBeSTK = false;
m = NULL;
f = NULL;
modBuffer = NULL;
newMod = (module_t *)calloc(1, sizeof (module_t));
if (newMod == NULL)
{
statusOutOfMemory();
goto modLoadError;
}
f = UNICHAR_FOPEN(fileName, "rb");
if (f == NULL)
{
displayErrorMsg("FILE I/O ERROR !");
goto modLoadError;
}
fseek(f, 0, SEEK_END);
filesize = ftell(f);
rewind(f);
// check if mod is a powerpacker mod
fread(&powerPackerID, 4, 1, f);
if (powerPackerID == 0x30325850) // "PX20"
{
displayErrorMsg("ENCRYPTED MOD !");
goto modLoadError;
}
else if (powerPackerID == 0x30325050) // "PP20"
{
modBuffer = unpackPPModule(f, &filesize);
if (modBuffer == NULL)
goto modLoadError; // error msg is set in unpackPPModule()
}
else
{
modBuffer = (uint8_t *)malloc(filesize);
if (modBuffer == NULL)
{
statusOutOfMemory();
goto modLoadError;
}
fseek(f, 0, SEEK_SET);
fread(modBuffer, 1, filesize, f);
fclose(f);
}
// smallest and biggest possible PT .MOD
if (filesize < 2108 || filesize > 4195326)
{
displayErrorMsg("NOT A MOD FILE !");
goto modLoadError;
}
// Use MEMFILE functions on module buffer (similar to FILE functions)
m = mopen(modBuffer, filesize);
if (m == NULL)
{
displayErrorMsg("FILE I/O ERROR !");
goto modLoadError;
}
// check magic ID
memset(modID, 0, 4); // in case mread fails
mseek(m, 1080, SEEK_SET);
mread(modID, 1, 4, m);
modFormat = getModType(&numChannels, modID);
if (numChannels == 0 || numChannels > AMIGA_VOICES)
{
displayErrorMsg("UNSUPPORTED MOD !");
goto modLoadError;
}
if (modFormat == FORMAT_UNKNOWN)
mightBeSTK = true;
mrewind(m);
mread(newMod->head.moduleTitle, 1, 20, m);
newMod->head.moduleTitle[20] = '\0';
// convert illegal song name characters to space
for (i = 0; i < 20; i++)
{
tmpChar = newMod->head.moduleTitle[i];
if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
tmpChar = ' ';
newMod->head.moduleTitle[i] = (char)tolower(tmpChar);
}
fixZeroesInString(newMod->head.moduleTitle, 20);
// read sample headers
s = newMod->samples;
for (i = 0; i < MOD_SAMPLES; i++, s++)
{
if (mightBeSTK && i >= 15) // skip reading sample headers past sample slot 15 in STK/UST modules
{
s->loopLength = 2; // this be set though
continue;
}
mread(s->text, 1, 22, m);
s->text[22] = '\0';
if (modFormat == FORMAT_HMNT)
{
// most of "His Master's Noisetracker" songs have junk sample names, so let's wipe it.
memset(s->text, 0, 22);
}
else
{
// convert illegal sample name characters to space
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->length = ((mgetc(m) << 8) | mgetc(m)) * 2;
/* Only late versions of Ultimate SoundTracker could have samples larger than 9999 bytes.
** If found, we know for sure that this is a late STK module.
*/
if (mightBeSTK && s->length > 9999)
lateSTKVerFlag = true;
if (modFormat == FORMAT_HMNT) // finetune in "His Master's NoiseTracker" is different
s->fineTune = (uint8_t)((-mgetc(m) & 0x1F) / 2); // one more bit of precision, + inverted
else
s->fineTune = (uint8_t)mgetc(m) & 0xF;
if (mightBeSTK)
s->fineTune = 0; // this is high byte of volume in STK/UST (has no finetune), set to zero
s->volume = (int8_t)mgetc(m);
if ((uint8_t)s->volume > 64)
s->volume = 64;
loopStart = ((mgetc(m) << 8) | mgetc(m)) * 2;
loopLength = ((mgetc(m) << 8) | mgetc(m)) * 2;
if (loopLength < 2)
loopLength = 2; // fixes empty samples in .MODs saved from FT2
// we don't support samples bigger than 65534 bytes, disable uncompatible loops
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;
}
// in The Ultimate SoundTracker, sample loop start is in bytes, not words
if (mightBeSTK)
s->loopStart /= 2;
// fix for poorly converted STK (< v2.5) -> PT/NT modules (FIXME: Worth keeping or not?)
if (!mightBeSTK && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
{
if ((s->loopStart/2) + s->loopLength <= s->length)
s->loopStart /= 2;
}
}
newMod->head.orderCount = (uint8_t)mgetc(m);
if (modFormat == FORMAT_MK && newMod->head.orderCount == 129)
newMod->head.orderCount = 127; // fixes a specific copy of beatwave.mod
if (newMod->head.orderCount > 129)
{
displayErrorMsg("NOT A MOD FILE !");
goto modLoadError;
}
if (newMod->head.orderCount == 0)
{
displayErrorMsg("NOT A MOD FILE !");
goto modLoadError;
}
restartPos = (uint8_t)mgetc(m);
if (mightBeSTK && restartPos > 220)
{
displayErrorMsg("NOT A MOD FILE !");
goto modLoadError;
}
newMod->head.initialTempo = 125;
if (mightBeSTK)
{
/* If we're still here at this point and the mightBeSTK flag is set,
** then it's most likely a proper The Ultimate SoundTracker (STK/UST) module.
*/
modFormat = FORMAT_STK;
if (restartPos == 0)
restartPos = 120;
// jjk55.mod by Jesper Kyd has a bogus STK tempo value that should be ignored
if (!strcmp("jjk55", newMod->head.moduleTitle))
restartPos = 120;
// the "restart pos" field in STK is the inital tempo (must be converted to BPM first)
if (restartPos != 120) // 120 is a special case and means 50Hz (125BPM)
{
if (restartPos > 220)
restartPos = 220;
// convert UST tempo to BPM
uint16_t ciaPeriod = (240 - restartPos) * 122;
double dHz = (double)CIA_PAL_CLK / ciaPeriod;
int32_t BPM = (int32_t)((dHz * (125.0 / 50.0)) + 0.5);
newMod->head.initialTempo = (uint16_t)BPM;
}
}
// read orders and count number of patterns
numPatterns = 0;
for (i = 0; i < MOD_ORDERS; i++)
{
newMod->head.order[i] = (int16_t)mgetc(m);
if (newMod->head.order[i] > numPatterns)
numPatterns = newMod->head.order[i];
}
numPatterns++;
if (numPatterns > MAX_PATTERNS)
{
displayErrorMsg("UNSUPPORTED MOD !");
goto modLoadError;
}
// skip magic ID (The Ultimate SoundTracker MODs doesn't have it)
if (modFormat != FORMAT_STK)
mseek(m, 4, SEEK_CUR);
// allocate 100 patterns
for (i = 0; i < MAX_PATTERNS; i++)
{
newMod->patterns[i] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t));
if (newMod->patterns[i] == NULL)
{
statusOutOfMemory();
goto modLoadError;
}
}
// load pattern data
for (i = 0; i < numPatterns; i++)
{
note = newMod->patterns[i];
for (j = 0; j < MOD_ROWS; j++)
{
for (k = 0; k < numChannels; k++, note++)
{
mread(bytes, 1, 4, m);
note->period = ((bytes[0] & 0x0F) << 8) | bytes[1];
note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4);
note->command = bytes[2] & 0x0F;
note->param = bytes[3];
if (modFormat == FORMAT_STK)
{
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;
}
}
}
if (numChannels < AMIGA_VOICES)
note += AMIGA_VOICES-numChannels;
}
}
// pattern command conversion for non-PT formats
if (modFormat == FORMAT_STK || modFormat == FORMAT_FT2 || modFormat == FORMAT_NT || modFormat == FORMAT_HMNT || modFormat == FORMAT_FLT)
{
for (i = 0; i < numPatterns; i++)
{
note = newMod->patterns[i];
for (j = 0; j < MOD_ROWS*4; j++, note++)
{
if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
{
// any Dxx == D00 in NT/HMNT
if (note->command == 0xD)
note->param = 0;
// effect F with param 0x00 does nothing in NT/HMNT
if (note->command == 0xF && note->param == 0)
note->command = 0;
}
else if (modFormat == FORMAT_FLT) // Startrekker (4 channels)
{
if (note->command == 0xE) // remove unsupported "assembly macros" command
{
note->command = 0;
note->param = 0;
}
// Startrekker is always in vblank mode, and limits speed to 0x1F
if (note->command == 0xF && note->param > 0x1F)
note->param = 0x1F;
}
else if (modFormat == FORMAT_STK)
{
// 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;
}
}
}
// effect F with param 0x00 does nothing in UST/STK (I think?)
if (note->command == 0xF && note->param == 0)
note->command = 0;
}
// remove sample-trashing effects that were only present in ProTracker
// remove E8x (Karplus-Strong in ProTracker)
if (note->command == 0xE && (note->param >> 4) == 0x8)
{
note->command = 0;
note->param = 0;
}
// remove EFx (Invert Loop in ProTracker)
if (note->command == 0xE && (note->param >> 4) == 0xF)
{
note->command = 0;
note->param = 0;
}
}
}
}
// allocate sample data (+2 sample slots for overflow safety (Paula and scopes))
newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
if (newMod->sampleData == NULL)
{
statusOutOfMemory();
goto modLoadError;
}
// set sample data offsets (sample data = one huge buffer to rule them all)
for (i = 0; i < MOD_SAMPLES; i++)
newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
// load sample data
numSamples = (modFormat == FORMAT_STK) ? 15 : 31;
s = newMod->samples;
for (i = 0; i < numSamples; i++, s++)
{
uint32_t bytesToSkip = 0;
/* For Ultimate SoundTracker modules, only the loop area of a looped sample is played.
** Skip loading of eventual data present before loop start.
*/
if (modFormat == FORMAT_STK && s->loopStart > 0 && s->loopLength < s->length)
{
s->length -= s->loopStart;
mseek(m, s->loopStart, SEEK_CUR);
s->loopStart = 0;
}
/* We don't support loading samples bigger than 65534 bytes in our PT2 clone,
** so clamp length and skip overflown data.
*/
if (s->length > MAX_SAMPLE_LEN)
{
s->length = MAX_SAMPLE_LEN;
bytesToSkip = s->length - MAX_SAMPLE_LEN;
}
// For Ultimate SoundTracker modules, don't load sample data after loop end
uint16_t loopEnd = s->loopStart + s->loopLength;
if (modFormat == FORMAT_STK && loopEnd > 2 && s->length > loopEnd)
{
bytesToSkip += s->length-loopEnd;
s->length = loopEnd;
}
mread(&newMod->sampleData[s->offset], 1, s->length, m);
if (bytesToSkip > 0)
mseek(m, bytesToSkip, SEEK_CUR);
// clear first two bytes of non-looping samples to prevent beep after sample has been played
if (s->length >= 2 && loopEnd <= 2)
{
newMod->sampleData[s->offset+0] = 0;
newMod->sampleData[s->offset+1] = 0;
}
// some modules are broken like this, adjust sample length if possible (this is ok if we have room)
if (s->length > 0 && 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 allocating 65534 bytes per sample slot
}
else
{
s->loopStart = 0;
s->loopLength = 2;
}
}
}
mclose(&m);
free(modBuffer);
for (i = 0; i < AMIGA_VOICES; i++)
newMod->channels[i].n_chanindex = i;
return newMod;
modLoadError:
if (m != NULL)
mclose(&m);
if (modBuffer != NULL)
free(modBuffer);
if (newMod != NULL)
{
for (i = 0; i < MAX_PATTERNS; i++)
{
if (newMod->patterns[i] != NULL)
free(newMod->patterns[i]);
}
free(newMod);
}
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 MEMFILE *mopen(const uint8_t *src, uint32_t length)
{
MEMFILE *b;
if (src == NULL || length == 0)
return NULL;
b = (MEMFILE *)malloc(sizeof (MEMFILE));
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(MEMFILE **buf)
{
if (*buf != NULL)
{
free(*buf);
*buf = NULL;
}
}
static int32_t mgetc(MEMFILE *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, MEMFILE *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(MEMFILE *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;
}
}
static void mrewind(MEMFILE *buf)
{
mseek(buf, 0, SEEK_SET);
}
/* PowerPacker unpack code taken from Heikki Orsila's amigadepack. Seems to have no license,
** so I'll assume it fits into BSD 3-Clause. If not, feel free to contact me at my email
** address found at the bottom of 16-bits.org.
**
** Modified by 8bitbubsy (me).
*/
#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);
editor.timingMode = TEMPO_MODE_CIA;
modSetSpeed(6);
modSetTempo(modEntry->head.initialTempo); // 125 for normal MODs, custom value for certain STK/UST MODs
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;
}
// +2 sample slots for overflow safety (Paula and scopes)
newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
if (newMod->sampleData == NULL)
goto oom;
newMod->head.orderCount = 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;
}