shithub: pt2-clone

ref: 46132b259e321547e2fcb4259fd3bf458947b1b3
dir: /src/pt2_modloader.c/

View raw version
// 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;

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

						// TODO: This needs more detection and is NOT correct!
						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 (config.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 (config.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(video.window);
			SDL_RaiseWindow(video.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)
			{
				editor.playMode = PLAY_MODE_NORMAL;
				editor.currMode = MODE_PLAY;

				// start normal playback
				modPlay(DONT_SET_PATTERN, 0, 0);

				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;
}