shithub: ft2-clone

Download patch

ref: 4dfccacdd7d6538d2073a020a1b7f7d1fb4bf774
parent: df5ceec1c72376898da165c8564299d680bb0cb3
author: Olav Sørensen <olav.sorensen@live.no>
date: Tue Dec 17 15:42:09 EST 2019

Pushed v1.04 code

- Fixed rare crash (or strange behaviors) when changing pattern and/or pattern
  length while the song is playing.
- Properly restore channel mute flags when loading a new song (fixes mute bugs)
- Fixed a few bugs with different pattern buttons (Ins./Del., Ln. up/down etc)
- Config: "Hardware mouse" was changed to "Software mouse" (and "Software mouse"
  is now disabled in the default config).
- Added a routine to create scaled FT2 mouse cursors for software mouse mode,
  though the "busy mouse" will stand still and not animate.
  Hopefully the new default "hardware mouse" mode will satisfy some people!
- MacOS: Pass NDEBUG to clang preprocessor defines, to prevent debug code
  from being compiled in release mode (performance increase).
- MacOS/Linux: make scripts had Windows linefeeds and would thus break!

- Add .gitignore to empty directories so that they get included (Silly git...)

diff: cannot open b/release/macos/ft2-clone-macos.app/Contents/MacOS//null: file does not exist: 'b/release/macos/ft2-clone-macos.app/Contents/MacOS//null' diff: cannot open b/release/other//null: file does not exist: 'b/release/other//null'
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@
 *.cache
 vs2019_project/.vs/ft2-clone/v16/.suo
 *.ipch
+.DS_Store
+vs2019_project/ft2-clone/Release/ft2-clone.vcxproj.FileListAbsolute.txt
+vs2019_project/ft2-clone/x64/Debug/ft2-clone.vcxproj.FileListAbsolute.txt
+*.cod
--- a/make-linux.sh
+++ b/make-linux.sh
@@ -7,7 +7,7 @@
 # This will activate 2-tap linear interpolation mixing (blurrier sound) instead
 # of 3-tap quadratic interpolation mixing (sharper sound)
 
-gcc -D__LINUX_ALSA__ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -lSDL2 -lpthread -lasound -lstdc++ -lm -Wshadow -Winit-self -Wall -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -march=native -mtune=native -O3 -o release/other/ft2-clone
+gcc -DNDEBUG -D__LINUX_ALSA__ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -lSDL2 -lpthread -lasound -lstdc++ -lm -Wshadow -Winit-self -Wall -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -march=native -mtune=native -O3 -o release/other/ft2-clone
 
 rm src/rtmidi/*.o src/gfxdata/*.o src/*.o &> /dev/null
 
--- a/make-macos.sh
+++ b/make-macos.sh
@@ -8,7 +8,7 @@
     
     rm release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos &> /dev/null
     
-    clang -mmacosx-version-min=10.7 -arch x86_64 -mmmx -mfpmath=sse -msse2 -I/Library/Frameworks/SDL2.framework/Headers -F/Library/Frameworks -D__MACOSX_CORE__ -stdlib=libc++ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -O3 /usr/lib/libiconv.dylib -lm -Winit-self -Wno-deprecated -Wextra -Wunused -mno-ms-bitfields -Wno-missing-field-initializers -Wswitch-default -framework SDL2 -framework CoreMidi -framework CoreAudio -framework Cocoa -lpthread -lm -lstdc++ -o release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
+    clang -mmacosx-version-min=10.7 -arch x86_64 -mmmx -mfpmath=sse -msse2 -I/Library/Frameworks/SDL2.framework/Headers -F/Library/Frameworks -g0 -DNDEBUG -D__MACOSX_CORE__ -stdlib=libc++ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -O3 /usr/lib/libiconv.dylib -lm -Winit-self -Wno-deprecated -Wextra -Wunused -mno-ms-bitfields -Wno-missing-field-initializers -Wswitch-default -framework SDL2 -framework CoreMidi -framework CoreAudio -framework Cocoa -lpthread -lm -lstdc++ -o release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     strip release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     
--- /dev/null
+++ b/release/macos/ft2-clone-macos.app/Contents/MacOS/.gitignore
@@ -1,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
--- /dev/null
+++ b/release/other/.gitignore
@@ -1,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
\ No newline at end of file
--- a/src/ft2_checkboxes.c
+++ b/src/ft2_checkboxes.c
@@ -90,7 +90,7 @@
 	{ 113,  79, 128, 12, cbConfigLineColors },
 	{ 113,  92, 126, 12, cbConfigChanNums },
 	{ 255,  14, 136, 12, cbConfigShowVolCol },
-	{ 255, 158, 113, 12, cbHardwareMouse },
+	{ 255, 158, 111, 12, cbSoftwareMouse },
 	// ---------------------------------
 	{ 212,   2, 150, 12, cbSampCutToBuff },
 	{ 212,  15, 153, 12, cbPattCutToBuff },
--- a/src/ft2_checkboxes.h
+++ b/src/ft2_checkboxes.h
@@ -67,7 +67,7 @@
 	CB_CONF_LINECOLORS,
 	CB_CONF_CHANNUMS,
 	CB_CONF_SHOW_VOLCOL,
-	CB_CONF_HARDWARE_MOUSE,
+	CB_CONF_SOFTWARE_MOUSE,
 
 	// CONFIG MISCELLANEOUS
 	CB_CONF_SAMP_CUT_TO_BUF,
--- a/src/ft2_config.c
+++ b/src/ft2_config.c
@@ -885,7 +885,7 @@
 	checkBoxes[CB_CONF_LINECOLORS].checked = config.ptnLineLight;
 	checkBoxes[CB_CONF_CHANNUMS].checked = config.ptnChnNumbers;
 	checkBoxes[CB_CONF_SHOW_VOLCOL].checked = config.ptnS3M;
-	checkBoxes[CB_CONF_HARDWARE_MOUSE].checked = (config.specialFlags2 & HARDWARE_MOUSE) ? true : false;
+	checkBoxes[CB_CONF_SOFTWARE_MOUSE].checked = (config.specialFlags2 & HARDWARE_MOUSE) ? false : true;
 
 	showCheckBox(CB_CONF_PATTSTRETCH);
 	showCheckBox(CB_CONF_HEXCOUNT);
@@ -895,7 +895,7 @@
 	showCheckBox(CB_CONF_LINECOLORS);
 	showCheckBox(CB_CONF_CHANNUMS);
 	showCheckBox(CB_CONF_SHOW_VOLCOL);
-	showCheckBox(CB_CONF_HARDWARE_MOUSE);
+	showCheckBox(CB_CONF_SOFTWARE_MOUSE);
 }
 
 static void setConfigLayoutRadioButtonStates(void)
@@ -1237,7 +1237,7 @@
 			textOutShadow(319, 146, PAL_FORGRND, PAL_DSKTOP2, "Std.");
 			textOutShadow(360, 146, PAL_FORGRND, PAL_DSKTOP2, "Lined");
 
-			textOutShadow(272, 160, PAL_FORGRND, PAL_DSKTOP2, "Hardware mouse");
+			textOutShadow(272, 160, PAL_FORGRND, PAL_DSKTOP2, "Software mouse");
 
 			textOutShadow(414,   3, PAL_FORGRND, PAL_DSKTOP2, "Pattern text");
 			textOutShadow(414,  17, PAL_FORGRND, PAL_DSKTOP2, "Block mark");
@@ -1438,7 +1438,7 @@
 	hideCheckBox(CB_CONF_LINECOLORS);
 	hideCheckBox(CB_CONF_CHANNUMS);
 	hideCheckBox(CB_CONF_SHOW_VOLCOL);
-	hideCheckBox(CB_CONF_HARDWARE_MOUSE);
+	hideCheckBox(CB_CONF_SOFTWARE_MOUSE);
 	hidePushButton(PB_CONFIG_PAL_R_DOWN);
 	hidePushButton(PB_CONFIG_PAL_R_UP);
 	hidePushButton(PB_CONFIG_PAL_G_DOWN);
@@ -1743,14 +1743,24 @@
 	redrawPatternEditor();
 }
 
-void cbHardwareMouse(void)
+void cbSoftwareMouse(void)
 {
 	config.specialFlags2 ^= HARDWARE_MOUSE;
+	if (!createMouseCursors())
+		okBox(0, "System message", "Error: Couldn't create/show mouse cursor!");
 
 	if (config.specialFlags2 & HARDWARE_MOUSE)
-		SDL_ShowCursor(true);
+	{
+		checkBoxes[CB_CONF_SOFTWARE_MOUSE].checked = false;
+		drawCheckBox(CB_CONF_SOFTWARE_MOUSE);
+		SDL_ShowCursor(SDL_TRUE);
+	}
 	else
-		SDL_ShowCursor(false);
+	{
+		checkBoxes[CB_CONF_SOFTWARE_MOUSE].checked = true;
+		drawCheckBox(CB_CONF_SOFTWARE_MOUSE);
+		SDL_ShowCursor(SDL_FALSE);
+	}
 }
 
 void rbConfigMouseNice(void)
@@ -1757,6 +1767,7 @@
 {
 	config.mouseType = MOUSE_IDLE_SHAPE_NICE;
 	checkRadioButton(RB_CONFIG_MOUSE_NICE);
+	createMouseCursors();
 	setMouseShape(config.mouseType);
 }
 
@@ -1764,6 +1775,7 @@
 {
 	config.mouseType = MOUSE_IDLE_SHAPE_UGLY;
 	checkRadioButton(RB_CONFIG_MOUSE_UGLY);
+	createMouseCursors();
 	setMouseShape(config.mouseType);
 }
 
@@ -1771,6 +1783,7 @@
 {
 	config.mouseType = MOUSE_IDLE_SHAPE_AWFUL;
 	checkRadioButton(RB_CONFIG_MOUSE_AWFUL);
+	createMouseCursors();
 	setMouseShape(config.mouseType);
 }
 
@@ -1778,6 +1791,7 @@
 {
 	config.mouseType = MOUSE_IDLE_SHAPE_USABLE;
 	checkRadioButton(RB_CONFIG_MOUSE_USABLE);
+	createMouseCursors();
 	setMouseShape(config.mouseType);
 }
 
@@ -2175,7 +2189,7 @@
 {
 	0x46,0x61,0x73,0x74,0x54,0x72,0x61,0x63,0x6B,0x65,0x72,0x20,0x32,0x2E,0x30,0x20,0x63,0x6F,0x6E,0x66,
 	0x69,0x67,0x75,0x72,0x61,0x74,0x69,0x6F,0x6E,0x20,0x66,0x69,0x6C,0x65,0x1A,0x01,0x01,0x80,0xBB,0x00,
-	0x00,0xFF,0x00,0x00,0x01,0xDC,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0xFF,0x00,0x20,0x02,0x01,0x00,
+	0x00,0xFF,0x00,0x00,0x01,0xDC,0x00,0x00,0x00,0x01,0x01,0x00,0x02,0x00,0xFF,0x00,0x20,0x02,0x01,0x00,
 	0x05,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x01,0x01,0x01,0x04,0x00,0x00,0x00,0x00,0x00,
 	0x00,0x00,0x00,0x24,0x2F,0x3F,0x09,0x09,0x10,0x3F,0x3F,0x3F,0x13,0x18,0x26,0x3F,0x3F,0x3F,0x27,0x27,
 	0x27,0x00,0x00,0x00,0x08,0x0A,0x0F,0x20,0x29,0x3F,0x0F,0x0F,0x0F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
--- a/src/ft2_config.h
+++ b/src/ft2_config.h
@@ -246,7 +246,7 @@
 void cbConfigLineColors(void);
 void cbConfigChanNums(void);
 void cbConfigShowVolCol(void);
-void cbHardwareMouse(void);
+void cbSoftwareMouse(void);
 void cbSampCutToBuff(void);
 void cbPattCutToBuff(void);
 void cbKillNotesAtStop(void);
--- a/src/ft2_edit.c
+++ b/src/ft2_edit.c
@@ -252,7 +252,7 @@
 
 	pattLen = pattLens[editor.editPattern];
 	if (playMode == PLAYMODE_EDIT && pattLen >= 1)
-		setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+		setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 
 	if (i == 0) // if we inserted a zero, check if pattern is empty, for killing
 		killPatternIfUnused(editor.editPattern);
@@ -459,7 +459,7 @@
 				{
 					// increase row (only in edit mode)
 					if (pattLen >= 1)
-						setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+						setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 				}
 				else
 				{
@@ -524,7 +524,7 @@
 				{
 					// increase row (only in edit mode)
 					if (pattLen >= 1)
-						setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+						setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 				}
 				else
 				{
@@ -598,7 +598,7 @@
 		// increase row (only in edit mode)
 		pattLen = pattLens[editor.editPattern];
 		if (playMode == PLAYMODE_EDIT && pattLen >= 1)
-			setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+			setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 
 		editor.ui.updatePatternEditor = true;
 		setSongModifiedFlag();
@@ -673,7 +673,7 @@
 	}
 
 	if (playMode == PLAYMODE_EDIT && pattLen >= 1)
-		setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+		setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 
 	killPatternIfUnused(editor.editPattern);
 
@@ -723,35 +723,30 @@
 
 	nr = editor.editPattern;
 
-	if (setPatternLen(nr, pattLens[nr] + config.recTrueInsert)) // config.recTrueInsert is 0 or 1
+	setPatternLen(nr, pattLens[nr] + config.recTrueInsert); // config.recTrueInsert is 0 or 1
+
+	pattPtr = patt[nr];
+	if (pattPtr != NULL)
 	{
-		pattPtr = patt[nr];
-		if (pattPtr != NULL)
-		{
-			pattPos = editor.pattPos;
-			pattLen = pattLens[nr];
+		pattPos = editor.pattPos;
+		pattLen = pattLens[nr];
 
-			if (pattLen > 1)
+		if (pattLen > 1)
+		{
+			for (int32_t i = pattLen-2; i >= pattPos; i--)
 			{
-				for (int32_t i = pattLen-2; i >= pattPos; i--)
-				{
-					for (int32_t j = 0; j < MAX_VOICES; j++)
-						pattPtr[((i + 1) * MAX_VOICES) + j] = pattPtr[(i * MAX_VOICES) + j];
-				}
+				for (int32_t j = 0; j < MAX_VOICES; j++)
+					pattPtr[((i + 1) * MAX_VOICES) + j] = pattPtr[(i * MAX_VOICES) + j];
 			}
-
-			memset(&pattPtr[pattPos * MAX_VOICES], 0, TRACK_WIDTH);
-
-			killPatternIfUnused(nr);
 		}
 
-		editor.ui.updatePatternEditor = true;
-		setSongModifiedFlag();
+		memset(&pattPtr[pattPos * MAX_VOICES], 0, TRACK_WIDTH);
+
+		killPatternIfUnused(nr);
 	}
-	else
-	{
-		okBox(0, "System message", "Not enough memory!");
-	}
+
+	editor.ui.updatePatternEditor = true;
+	setSongModifiedFlag();
 }
 
 void deletePatternNote(void)
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.03"
+#define PROG_VER_STR "1.04"
 
 // do NOT change these! It will only mess things up...
 
@@ -90,7 +90,7 @@
 		bool scopesShown, diskOpShown, nibblesShown, transposeShown, instEditorExtShown;
 		bool sampleEditorExtShown, advEditShown, wavRendererShown, trimScreenShown;
 		bool drawBPMFlag, drawSpeedFlag, drawGlobVolFlag, drawPosEdFlag, drawPattNumLenFlag;
-		bool updatePosSections;
+		bool updatePosSections, updatePosEdScrollBar;
 		uint8_t oldTopLeftScreen;
 
 		// bottom screens
--- a/src/ft2_inst_ed.c
+++ b/src/ft2_inst_ed.c
@@ -220,7 +220,8 @@
 	instrTyp *ins = getCurDispInstr();
 
 	assert(ins->midiChannel <= 15);
-	sprintf(str, "%02d", ins->midiChannel + 1);
+	uint8_t disp = ins->midiChannel + 1;
+	sprintf(str, "%02d", disp);
 	textOutFixed(156, 132, PAL_FORGRND, PAL_DESKTOP, str);
 }
 
--- a/src/ft2_keyboard.c
+++ b/src/ft2_keyboard.c
@@ -347,7 +347,7 @@
 
 				pattLen = pattLens[editor.editPattern];
 				if (playMode == PLAYMODE_EDIT && pattLen >= 1)
-					setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen);
+					setPos(-1, (editor.pattPos + editor.ID_Add) % pattLen, true);
 
 				editor.ui.updatePatternEditor = true;
 				setSongModifiedFlag();
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -120,8 +120,6 @@
 	}
 	SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
 
-	createSDL2Cursors();
-
 	/* Text input is started by default in SDL2, turn it off to remove ~2ms spikes per key press.
 	** We manuallay start it again when a text edit box is activated, and stop it when done.
 	** Ref.: https://bugzilla.libsdl.org/show_bug.cgi?id=4166 */
@@ -233,18 +231,18 @@
 {
 	int32_t i;
 
-	cpu.hasSSE  = SDL_HasSSE();
+	cpu.hasSSE = SDL_HasSSE();
 	cpu.hasSSE2 = SDL_HasSSE2();
 
 	// clear common structs
-	memset(&video,    0, sizeof (video));
-	memset(&keyb,     0, sizeof (keyb));
-	memset(&mouse,    0, sizeof (mouse));
-	memset(&editor,   0, sizeof (editor));
+	memset(&video, 0, sizeof (video));
+	memset(&keyb, 0, sizeof (keyb));
+	memset(&mouse, 0, sizeof (mouse));
+	memset(&editor, 0, sizeof (editor));
 	memset(&pattMark, 0, sizeof (pattMark));
 	memset(&pattSync, 0, sizeof (pattSync));
-	memset(&chSync,   0, sizeof (chSync));
-	memset(&song,     0, sizeof (song));
+	memset(&chSync, 0, sizeof (chSync));
+	memset(&song, 0, sizeof (song));
 
 	for (i = 0; i < MAX_VOICES; i++)
 	{
@@ -307,7 +305,7 @@
 	freeMidiInputDeviceList();
 	windUpFTHelp();
 	freeTextBoxes();
-	freeSDL2Cursors();
+	freeMouseCursors();
 
 	if (midi.inputDeviceName != NULL)
 	{
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -349,7 +349,7 @@
 
 	for (a = 0; a <= b; a++)
 	{
-		pattTmp[a] = (tonTyp *)calloc(64 * MAX_VOICES, sizeof (tonTyp));
+		pattTmp[a] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (pattTmp[a] == NULL)
 		{
 			okBoxThreadSafe(0, "System message", "Not enough memory!");
@@ -674,7 +674,7 @@
 	ap = h_STM.ap;
 	for (i = 0; i < ap; i++)
 	{
-		pattTmp[i] = (tonTyp *)calloc(64 * MAX_VOICES, sizeof (tonTyp));
+		pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (pattTmp[i] == NULL)
 		{
 			okBoxThreadSafe(0, "System message", "Not enough memory!");
@@ -1099,7 +1099,7 @@
 
 		if (j > 0 && j <= 12288)
 		{
-			pattTmp[i] = (tonTyp *)calloc(64 * MAX_VOICES, sizeof (tonTyp));
+			pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 			if (pattTmp[i] == NULL)
 			{
 				okBoxThreadSafe(0, "System message", "Not enough memory!");
@@ -2215,9 +2215,7 @@
 
 		if (ph.dataLen > 0)
 		{
-			a = ph.pattLen * TRACK_WIDTH;
-
-			pattTmp[i] = (tonTyp *)malloc(a + 16); // + 16 = a little extra for safety
+			pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 			if (pattTmp[i] == NULL)
 			{
 				okBoxThreadSafe(0, "System message", "Not enough memory!");
@@ -2224,6 +2222,8 @@
 				return false;
 			}
 
+			a = ph.pattLen * TRACK_WIDTH;
+
 			pattPtr = (uint8_t *)pattTmp[i];
 			memset(pattPtr, 0, a);
 
@@ -2321,7 +2321,7 @@
 
 	resetChannels();
 	refreshScopes();
-	setPos(0, 0);
+	setPos(0, 0, false);
 	setSpeed(song.speed);
 
 	editor.tmpPattern = editor.editPattern; // set kludge variable
--- a/src/ft2_mouse.c
+++ b/src/ft2_mouse.c
@@ -19,40 +19,145 @@
 #include "ft2_gfxdata.h"
 #include "ft2_audioselector.h"
 #include "ft2_midi.h"
+#include "ft2_gfxdata.h"
 
+#define NUM_CURSORS 6
+
 static bool mouseBusyGfxBackwards;
 static int16_t mouseShape;
 static int32_t mouseModeGfxOffs, mouseBusyGfxFrame;
+static SDL_Cursor *cursors[NUM_CURSORS];
 
-static SDL_Cursor *cArrow, *cIBeam, *cBusy;
+static bool setSystemCursor(SDL_Cursor *cursor)
+{
+	if (cursor == NULL)
+	{
+		SDL_SetCursor(SDL_GetDefaultCursor());
+		return false;
+	}
 
-void freeSDL2Cursors(void)
+	SDL_SetCursor(cursor);
+	return true;
+}
+
+void freeMouseCursors(void)
 {
-	if (cArrow != NULL)
+	SDL_SetCursor(SDL_GetDefaultCursor());
+	for (uint32_t i = 0; i < NUM_CURSORS; i++)
 	{
-		SDL_FreeCursor(cArrow);
-		cArrow = NULL;
+		if (cursors[i] != NULL)
+		{
+			SDL_FreeCursor(cursors[i]);
+			cursors[i] = NULL;
+		}
 	}
+}
 
-	if (cIBeam != NULL)
+bool createMouseCursors(void) // creates scaled SDL surfaces for current mouse pointer shape
+{
+	freeMouseCursors();
+
+	const uint8_t *cursorsSrc = mouseCursors;
+	switch (config.mouseType)
 	{
-		SDL_FreeCursor(cIBeam);
-		cIBeam = NULL;
+		case MOUSE_IDLE_SHAPE_NICE:   cursorsSrc += 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+		case MOUSE_IDLE_SHAPE_UGLY:   cursorsSrc += 1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+		case MOUSE_IDLE_SHAPE_AWFUL:  cursorsSrc += 2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+		case MOUSE_IDLE_SHAPE_USABLE: cursorsSrc += 3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+		default: break;
 	}
 
-	if (cBusy != NULL)
+	for (uint32_t i = 0; i < NUM_CURSORS; i++)
 	{
-		SDL_FreeCursor(cBusy);
-		cBusy = NULL;
+		SDL_Surface *surface = SDL_CreateRGBSurface(0, MOUSE_CURSOR_W*video.yScale, MOUSE_CURSOR_H*video.yScale, 32, 0, 0, 0, 0);
+		if (surface == NULL)
+		{
+			freeMouseCursors();
+			config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
+			return false;
+		}
+
+		uint32_t colorkey = SDL_MapRGB(surface->format, 0x00, 0xFF, 0x00); // colorkey
+		uint32_t fg = SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF); // foreground
+		uint32_t border = SDL_MapRGB(surface->format, 0x00, 0x00, 0x00); // border
+
+		SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
+		SDL_SetColorKey(surface, SDL_TRUE, colorkey);
+		SDL_SetSurfaceRLE(surface, SDL_TRUE);
+
+		const uint8_t *srcPixels8;
+		if (i == 3) // text edit cursor
+			srcPixels8 = &mouseCursors[12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
+		else if (i == 4) // mouse busy (wall clock)
+			srcPixels8 = &mouseCursorBusyClock[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
+		else if (i == 5) // mouse busy (hourglass)
+			srcPixels8 = &mouseCursorBusyGlass[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
+		else // normal idle cursor + disk op. "delete/rename" cursors
+			srcPixels8 = &cursorsSrc[i * (4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H))];
+
+		SDL_LockSurface(surface);
+
+		uint32_t *dstPixels32 = (uint32_t *)surface->pixels;
+
+		for (int32_t k = 0; k < surface->w*surface->h; k++) // fill surface with colorkey pixels
+			dstPixels32[k] = colorkey;
+
+		// blit upscaled cursor to surface
+		for (uint32_t y = 0; y < MOUSE_CURSOR_H; y++)
+		{
+			uint32_t *outX = &dstPixels32[(y * video.yScale) * surface->w];
+			for (uint32_t yScale = 0; yScale < video.yScale; yScale++)
+			{
+				for (uint32_t x = 0; x < MOUSE_CURSOR_W; x++)
+				{
+					uint8_t srcPix = srcPixels8[(y * MOUSE_CURSOR_W) + x];
+					if (srcPix != PAL_TRANSPR)
+					{
+						uint32_t pixel = colorkey;
+						if (srcPix == PAL_MOUSEPT)
+							pixel = fg;
+						else if (srcPix == PAL_BCKGRND)
+							pixel = border;
+
+						for (uint32_t xScale = 0; xScale < video.yScale; xScale++)
+							outX[xScale] = pixel;
+					}
+
+					outX += video.xScale;
+				}
+			}
+		}
+		SDL_UnlockSurface(surface);
+
+		uint32_t hotX = 0;
+		uint32_t hotY = 0;
+
+		if (i == 3) // text edit cursor bias
+		{
+			hotX = 2 * video.xScale;
+			hotY = 6 * video.yScale;
+		}
+
+		cursors[i] = SDL_CreateColorCursor(surface, hotX, hotY);
+		if (cursors[i] == NULL)
+		{
+			SDL_FreeSurface(surface);
+			freeMouseCursors();
+			config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
+			return false;
+		}
+
+		SDL_FreeSurface(surface);
 	}
 
-}
+	if (config.specialFlags2 & HARDWARE_MOUSE)
+	{
+		     if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
+		else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
+		else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
+	}
 
-void createSDL2Cursors(void)
-{
-	cArrow = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
-	cIBeam = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
-	cBusy = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
+	return true;
 }
 
 void setMousePosToCenter(void)
@@ -75,6 +180,12 @@
 {
 	if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK)
 	{
+		if (config.specialFlags2 & HARDWARE_MOUSE)
+		{
+			setSystemCursor(cursors[4]);
+			return;
+		}
+
 		if ((editor.framesPassed % 7) == 6)
 		{
 			if (mouseBusyGfxBackwards)
@@ -100,6 +211,12 @@
 	}
 	else
 	{
+		if (config.specialFlags2 & HARDWARE_MOUSE)
+		{
+			setSystemCursor(cursors[5]);
+			return;
+		}
+
 		if ((editor.framesPassed % 5) == 4)
 		{
 			mouseBusyGfxFrame = (mouseBusyGfxFrame + 1) % MOUSE_GLASS_ANI_FRAMES;
@@ -126,11 +243,11 @@
 		gfxPtr = &mouseCursors[mouseModeGfxOffs];
 		switch (shape)
 		{
-			case MOUSE_IDLE_SHAPE_NICE:    gfxPtr += 0  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
-			case MOUSE_IDLE_SHAPE_UGLY:    gfxPtr += 1  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
-			case MOUSE_IDLE_SHAPE_AWFUL:   gfxPtr += 2  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
-			case MOUSE_IDLE_SHAPE_USABLE:  gfxPtr += 3  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
-			case MOUSE_IDLE_TEXT_EDIT:     gfxPtr += 12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+			case MOUSE_IDLE_SHAPE_NICE:   gfxPtr += 0  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+			case MOUSE_IDLE_SHAPE_UGLY:   gfxPtr += 1  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+			case MOUSE_IDLE_SHAPE_AWFUL:  gfxPtr += 2  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+			case MOUSE_IDLE_SHAPE_USABLE: gfxPtr += 3  * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
+			case MOUSE_IDLE_TEXT_EDIT:    gfxPtr += 12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
 			default: return;
 		}
 	}
@@ -137,6 +254,13 @@
 
 	mouseShape = shape;
 	changeSpriteData(SPRITE_MOUSE_POINTER, gfxPtr);
+
+	if (config.specialFlags2 & HARDWARE_MOUSE)
+	{
+		     if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
+		else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
+		else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
+	}
 }
 
 static void setTextEditMouse(void)
@@ -145,8 +269,8 @@
 	mouse.xBias = -2;
 	mouse.yBias = -6;
 
-	if (config.specialFlags2 & HARDWARE_MOUSE && cIBeam != NULL)
-		SDL_SetCursor(cIBeam);
+	if (config.specialFlags2 & HARDWARE_MOUSE)
+		setSystemCursor(cursors[3]);
 }
 
 static void clearTextEditMouse(void)
@@ -155,8 +279,8 @@
 	mouse.xBias = 0;
 	mouse.yBias = 0;
 
-	if (config.specialFlags2 & HARDWARE_MOUSE && cArrow != NULL)
-		SDL_SetCursor(cArrow);
+	if (config.specialFlags2 & HARDWARE_MOUSE)
+		setSystemCursor(cursors[0]);
 }
 
 static void changeCursorIfOverTextBoxes(void)
@@ -238,8 +362,8 @@
 	editor.busy = true;
 	setMouseShape(config.mouseAnimType);
 
-	if (config.specialFlags2 & HARDWARE_MOUSE && cBusy != NULL)
-		SDL_SetCursor(cBusy);
+	//if (config.specialFlags2 & HARDWARE_MOUSE && cBusy != NULL)
+	//	SDL_SetCursor(cBusy);
 }
 
 void mouseAnimOff(void)
@@ -250,8 +374,8 @@
 	editor.busy = false;
 	setMouseShape(config.mouseType);
 
-	if (config.specialFlags2 & HARDWARE_MOUSE && cArrow != NULL)
-		SDL_SetCursor(cArrow);
+	//if (config.specialFlags2 & HARDWARE_MOUSE && cArrow != NULL)
+	//	SDL_SetCursor(cArrow);
 }
 
 static void mouseWheelDecRow(void)
@@ -265,7 +389,7 @@
 	if (pattPos < 0)
 		pattPos = pattLens[editor.editPattern] - 1;
 
-	setPos(-1, pattPos);
+	setPos(-1, pattPos, true);
 }
 
 static void mouseWheelIncRow(void)
@@ -279,7 +403,7 @@
 	if (pattPos > (pattLens[editor.editPattern] - 1))
 		pattPos = 0;
 
-	setPos(-1, pattPos);
+	setPos(-1, pattPos, true);
 }
 
 void mouseWheelHandler(bool directionUp)
@@ -633,13 +757,8 @@
 
 void updateMouseScaling(void)
 {
-	double dScaleX, dScaleY;
-
-	dScaleX = video.renderW / (double)SCREEN_W;
-	dScaleY = video.renderH / (double)SCREEN_H;
-
-	video.xScaleMul = (dScaleX == 0.0) ? 65536 : (uint32_t)round(65536.0 / dScaleX);
-	video.yScaleMul = (dScaleY == 0.0) ? 65536 : (uint32_t)round(65536.0 / dScaleY);
+	video.dMouseXMul = (double)SCREEN_W / video.renderW;
+	video.dMouseYMul = (double)SCREEN_H / video.renderH;
 }
 
 void readMouseXY(void)
@@ -693,9 +812,9 @@
 	if (mx < 0) mx = 0;
 	if (my < 0) mx = 0;
 
-	// multiply coords by video scaling factors
-	mx = (((uint32_t)mx * video.xScaleMul) + (1 << 15)) >> 16; // rounded
-	my = (((uint32_t)my * video.yScaleMul) + (1 << 15)) >> 16;
+	// multiply coords by video upscaling factors (don't round)
+	mx = (uint32_t)(mx * video.dMouseXMul);
+	my = (uint32_t)(my * video.dMouseYMul);
 
 	if (mx >= SCREEN_W) mx = SCREEN_W - 1;
 	if (my >= SCREEN_H) my = SCREEN_H - 1;
--- a/src/ft2_mouse.h
+++ b/src/ft2_mouse.h
@@ -30,8 +30,8 @@
 #define MOUSE_GLASS_ANI_FRAMES 22
 #define MOUSE_CLOCK_ANI_FRAMES 5
 
-void freeSDL2Cursors(void);
-void createSDL2Cursors(void);
+void freeMouseCursors(void);
+bool createMouseCursors(void);
 
 void setMousePosToCenter(void);
 void setMouseShape(int16_t shape);
--- a/src/ft2_palette.c
+++ b/src/ft2_palette.c
@@ -129,6 +129,13 @@
 		return;
 	}
 
+	if ((config.specialFlags2 & HARDWARE_MOUSE) && cfg_ColorNr == 3)
+	{
+		updatePaletteEditor(); // resets colors/contrast vars
+		okBox(0, "System message", "Mouse color can only be changed when \"Software mouse\" is enabled.");
+		return;
+	}
+
 	nr = FTC_EditOrder[cfg_ColorNr];
 	p = (uint8_t)config.cfg_StdPalNr;
 
--- a/src/ft2_pattern_draw.c
+++ b/src/ft2_pattern_draw.c
@@ -75,6 +75,8 @@
 	else if (chans == 10 && !config.ptnS3M)
 		chans = 12;
 
+	assert(chans >= 2 && chans <= 12);
+
 	chanWidth = chanWidths[(chans / 2) - 1] + 2;
 
 	// fill scrollbar framework (if needed)
@@ -359,9 +361,11 @@
 {
 	xPos += 3;
 
+	assert(ton >= 0 && ton <= 97);
+
 	if (editor.ui.numChannelsShown <= 4)
 	{
-		if (ton == 0)
+		if (ton <= 0 || ton > 97)
 			drawEmptyNoteBig(xPos, yPos, pal);
 		else if (ton == 97)
 			drawKeyOffBig(xPos, yPos, pal);
@@ -370,7 +374,7 @@
 	}
 	else
 	{
-		if (ton == 0)
+		if (ton <= 0 || ton > 97)
 			drawEmptyNoteMedium(xPos, yPos, pal);
 		else if (ton == 97)
 			drawKeyOffMedium(xPos, yPos, pal);
@@ -499,9 +503,11 @@
 {
 	xPos += 3;
 
+	assert(ton >= 0 && ton <= 97);
+
 	if (editor.ui.numChannelsShown <= 6)
 	{
-		if (ton == 0)
+		if (ton <= 0 || ton > 97)
 			drawEmptyNoteBig(xPos, yPos, pal);
 		else if (ton == 97)
 			drawKeyOffBig(xPos, yPos, pal);
@@ -510,7 +516,7 @@
 	}
 	else if (editor.ui.numChannelsShown <= 8)
 	{
-		if (ton == 0)
+		if (ton <= 0 || ton > 97)
 			drawEmptyNoteMedium(xPos, yPos, pal);
 		else if (ton == 97)
 			drawKeyOffMedium(xPos, yPos, pal);
@@ -519,7 +525,7 @@
 	}
 	else
 	{
-		if (ton == 0)
+		if (ton <= 0 || ton > 97)
 			drawEmptyNoteSmall(xPos, yPos, pal);
 		else if (ton == 97)
 			drawKeyOffSmall(xPos, yPos, pal);
@@ -668,7 +674,8 @@
 	void (*drawVolEfx)(uint8_t, uint16_t, uint16_t, uint8_t);
 	void (*drawEfx)(uint8_t, uint16_t, uint16_t, uint8_t, uint8_t);
 
-	// we're too lazy to erase things, just render the whole pattern framework first (fast enough on modern PCs)
+	/* We're too lazy to carefully erase things as needed, just render
+	** the whole pattern framework first (fast enough on modern PCs) */
 	drawPatternBorders();
 
 	chans = editor.ui.numChannelsShown;
@@ -675,6 +682,8 @@
 	if (chans > editor.ui.maxVisibleChannels)
 		chans = editor.ui.maxVisibleChannels;
 
+	assert(chans >= 2 && chans <= 12);
+
 	// get channel width
 	chanWidth = chanWidths[(chans / 2) - 1];
 	editor.ui.patternChannelWidth = chanWidth + 3;
@@ -1067,8 +1076,6 @@
 	uint8_t note;
 	uint32_t *dstPtr, pixVal, fontOffset, char1, char2, char3;
 
-	assert(ton >= 1 && ton <= 97);
-
 	ton--;
 
 	note  =  ton % 12;
@@ -1157,8 +1164,6 @@
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
 	uint8_t note;
 	uint32_t *dstPtr, pixVal, fontOffset, char1, char2, char3;
-
-	assert(ton >= 1 && ton <= 97);
 
 	ton--;
 
--- a/src/ft2_pattern_ed.c
+++ b/src/ft2_pattern_ed.c
@@ -46,7 +46,6 @@
 bool allocatePattern(uint16_t nr) // for tracker use only, not in loader!
 {
 	bool audioWasntLocked;
-	int16_t pattLen;
 
 	if (patt[nr] == NULL)
 	{
@@ -54,9 +53,13 @@
 		if (audioWasntLocked)
 			lockAudio();
 
-		pattLen = pattLens[nr];
+		/* Original FT2 allocates only the amount of rows needed, but we don't
+		 * do that to avoid out of bondary row look-up between out-of-sync replayer
+		 * state and tracker state (yes it used to happen, rarely). We're not wasting
+		 * too much RAM for a modern computer anyway. Worst case: 256 allocated
+		 * patterns would be ~10MB. */
 
-		patt[nr] = (tonTyp *)calloc(pattLen * TRACK_WIDTH, sizeof (tonTyp));
+		patt[nr] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (patt[nr] == NULL)
 		{
 			if (audioWasntLocked)
@@ -65,8 +68,8 @@
 			return false;
 		}
 
-		if (song.pattNr == nr)
-			song.pattLen = pattLen;
+		// XXX: Do we really need this? Sounds redundant.
+		song.pattLen = pattLens[nr];
 
 		if (audioWasntLocked)
 			unlockAudio();
@@ -122,6 +125,8 @@
 	if (editor.ui.numChannelsShown > editor.ui.maxVisibleChannels)
 		editor.ui.numChannelsShown = editor.ui.maxVisibleChannels;
 
+	assert(editor.ui.numChannelsShown >= 2 && editor.ui.numChannelsShown <= 12);
+
 	editor.ui.patternChannelWidth = chanWidths[(editor.ui.numChannelsShown / 2) - 1] + 3;
 }
 
@@ -925,7 +930,7 @@
 		{
 			pattLen = pattLens[editor.editPattern];
 			if (editor.pattPos > 0)
-				setPos(-1, editor.pattPos - 1);
+				setPos(-1, editor.pattPos - 1, true);
 
 			forceMarking = true;
 			editor.ui.updatePatternEditor = true;
@@ -934,7 +939,7 @@
 		{
 			pattLen = pattLens[editor.editPattern];
 			if (editor.pattPos < (pattLen - 1))
-				setPos(-1, editor.pattPos + 1);
+				setPos(-1, editor.pattPos + 1, true);
 
 			forceMarking = true;
 			editor.ui.updatePatternEditor = true;
@@ -1521,7 +1526,7 @@
 		lockAudio();
 
 	if (song.songPos != (int16_t)pos)
-		setPos((int16_t)pos, 0);
+		setPos((int16_t)pos, 0, true);
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -1534,7 +1539,7 @@
 		lockAudio();
 
 	if (song.songPos < song.len-1)
-		setPos(song.songPos + 1, 0);
+		setPos(song.songPos + 1, 0, true);
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -1547,7 +1552,7 @@
 		lockAudio();
 
 	if (song.songPos > 0)
-		setPos(song.songPos - 1, 0);
+		setPos(song.songPos - 1, 0, true);
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -1568,12 +1573,9 @@
 	song.songTab[song.songPos] = oldPatt;
 
 	song.len++;
-	setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
 
-	drawPosEdNums(song.songPos);
-	drawSongLength();
-
 	editor.ui.updatePosSections = true;
+	editor.ui.updatePosEdScrollBar = true;
 	setSongModifiedFlag();
 
 	unlockMixerCallback();
@@ -1581,34 +1583,29 @@
 
 void pbPosEdDel(void)
 {
-	if (song.len == 0)
+	if (song.len <= 1)
 		return;
 
 	lockMixerCallback();
 
-	for (uint16_t i = 0; i < 254-song.songPos; i++)
-		song.songTab[song.songPos+i] = song.songTab[song.songPos+1+i];
-	song.len--;
-
-	if (song.repS >= song.len)
+	if (song.songPos < 254)
 	{
-		song.repS =  song.len - 1;
-		drawSongRepS();
+		for (uint16_t i = 0; i < 254-song.songPos; i++)
+			song.songTab[song.songPos+i] = song.songTab[song.songPos+1+i];
 	}
 
+	song.len--;
+	if (song.repS >= song.len)
+		song.repS = song.len - 1;
+
 	if (song.songPos > song.len-1)
 	{
-		song.songPos = song.len-1;
-		setPos(song.songPos, -1);
-
+		editor.songPos = song.songPos = song.len-1;
+		setPos(song.songPos, -1, false);
 	}
 
-	setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
-
-	drawPosEdNums(song.songPos);
-	drawSongLength();
-
-	setPos(song.songPos, -1);
+	editor.ui.updatePosSections = true;
+	editor.ui.updatePosEdScrollBar = true;
 	setSongModifiedFlag();
 
 	unlockMixerCallback();
@@ -1616,6 +1613,9 @@
 
 void pbPosEdPattUp(void)
 {
+	if (song.songTab[song.songPos] == 255)
+		return;
+
 	lockMixerCallback();
 	if (song.songTab[song.songPos] < 255)
 	{
@@ -1624,11 +1624,8 @@
 		editor.editPattern = (uint8_t)song.pattNr;
 		song.pattLen = pattLens[editor.editPattern];
 
-		drawPosEdNums(song.songPos);
-		drawEditPattern(editor.editPattern);
-		drawPatternLength(editor.editPattern);
-
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 	unlockMixerCallback();
@@ -1636,6 +1633,9 @@
 
 void pbPosEdPattDown(void)
 {
+	if (song.songTab[song.songPos] == 0)
+		return;
+
 	lockMixerCallback();
 	if (song.songTab[song.songPos] > 0)
 	{
@@ -1644,11 +1644,8 @@
 		editor.editPattern = (uint8_t)song.pattNr;
 		song.pattLen = pattLens[editor.editPattern];
 
-		drawPosEdNums(song.songPos);
-		drawEditPattern(editor.editPattern);
-		drawPatternLength(editor.editPattern);
-
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 	unlockMixerCallback();
@@ -1667,10 +1664,8 @@
 
 	song.len++;
 
-	drawPosEdNums(song.songPos);
-	drawSongLength();
-
-	setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
+	editor.ui.updatePosSections = true;
+	editor.ui.updatePosEdScrollBar = true;
 	setSongModifiedFlag();
 
 	if (audioWasntLocked)
@@ -1691,22 +1686,16 @@
 	song.len--;
 
 	if (song.repS >= song.len)
-	{
 		song.repS = song.len - 1;
-		drawSongRepS();
-	}
 
 	if (song.songPos >= song.len)
 	{
 		song.songPos = song.len - 1;
-		setScrollBarPos(SB_POS_ED, song.songPos, false);
-		setPos(song.songPos, -1);
+		setPos(song.songPos, -1, false);
 	}
 
-	drawPosEdNums(song.songPos);
-	drawSongLength();
-
-	setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
+	editor.ui.updatePosSections = true;
+	editor.ui.updatePosEdScrollBar = true;
 	setSongModifiedFlag();
 
 	if (audioWasntLocked)
@@ -1722,7 +1711,7 @@
 	if (song.repS < song.len-1)
 	{
 		song.repS++;
-		drawSongRepS();
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 
@@ -1739,7 +1728,7 @@
 	if (song.repS > 0)
 	{
 		song.repS--;
-		drawSongRepS();
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 
@@ -1749,6 +1738,9 @@
 
 void pbBPMUp(void)
 {
+	if (song.speed == 255)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1772,6 +1764,9 @@
 
 void pbBPMDown(void)
 {
+	if (song.speed == 32)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1795,6 +1790,9 @@
 
 void pbSpeedUp(void)
 {
+	if (song.tempo == 31)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1817,6 +1815,9 @@
 
 void pbSpeedDown(void)
 {
+	if (song.tempo == 0)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1910,6 +1911,9 @@
 
 void pbEditPattUp(void)
 {
+	if (editor.editPattern == 255)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1921,15 +1925,8 @@
 		song.pattNr = editor.editPattern;
 		updatePtnLen();
 
-		if (!editor.ui.aboutScreenShown && !editor.ui.configScreenShown &&
-			!editor.ui.diskOpShown      && !editor.ui.helpScreenShown   &&
-			!editor.ui.nibblesShown)
-		{
-			drawEditPattern(editor.editPattern);
-			drawPatternLength(editor.editPattern);
-		}
-
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 	}
 
 	if (audioWasntLocked)
@@ -1938,6 +1935,9 @@
 
 void pbEditPattDown(void)
 {
+	if (editor.editPattern == 0)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1949,15 +1949,8 @@
 		song.pattNr = editor.editPattern;
 		updatePtnLen();
 
-		if (!editor.ui.aboutScreenShown && !editor.ui.configScreenShown &&
-			!editor.ui.diskOpShown      && !editor.ui.helpScreenShown   &&
-			!editor.ui.nibblesShown)
-		{
-			drawEditPattern(editor.editPattern);
-			drawPatternLength(editor.editPattern);
-		}
-
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 	}
 
 	if (audioWasntLocked)
@@ -1969,6 +1962,9 @@
 	bool audioWasntLocked;
 	uint16_t pattLen;
 
+	if (pattLens[editor.editPattern] >= 256)
+		return;
+
 	audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -1979,8 +1975,8 @@
 		setPatternLen(editor.editPattern, pattLen + 1);
 		checkMarkLimits();
 
-		drawPatternLength(editor.editPattern);
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 
@@ -1993,6 +1989,9 @@
 	bool audioWasntLocked;
 	uint16_t pattLen;
 
+	if (pattLens[editor.editPattern] <= 1)
+		return;
+
 	audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
@@ -2003,12 +2002,8 @@
 		setPatternLen(editor.editPattern, pattLen - 1);
 		checkMarkLimits();
 
-		drawPatternLength(editor.editPattern);
-
-		if (song.pattPos >= song.pattLen)
-			song.pattPos--;
-
 		editor.ui.updatePatternEditor = true;
+		editor.ui.updatePosSections = true;
 		setSongModifiedFlag();
 	}
 
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -111,6 +111,8 @@
 		ch->oldPan = 128;
 		ch->outPan = 128;
 		ch->finalPan = 128;
+
+		ch->stOff = !editor.chnMode[i]; // set channel mute flag from global mute flag
 	}
 
 	if (audioWasntLocked)
@@ -148,79 +150,31 @@
 	}
 }
 
-bool setPatternLen(uint16_t nr, int16_t len)
+void setPatternLen(uint16_t nr, int16_t len)
 {
 	bool audioWasntLocked;
-	tonTyp *newPtr;
 
 	assert(nr < MAX_PATTERNS);
 
-	len = CLAMP(len, 1, MAX_PATT_LEN);
-	if (len == pattLens[nr])
-		return true;
+	if ((len < 1 || len > MAX_PATT_LEN) || len == pattLens[nr])
+		return;
 
 	audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
 
-	if (patt[nr] == NULL)
-	{
-		pattLens[nr] = len;
+	pattLens[nr] = len;
 
-		if (editor.editPattern == song.pattNr)
-			song.pattLen = pattLens[nr];
+	if (patt[nr] != NULL)
+		killPatternIfUnused(nr);
 
-		if (song.pattPos >= pattLens[nr])
-			song.pattPos = pattLens[nr] - 1;
-
+	song.pattLen = pattLens[nr];
+	if (song.pattPos >= song.pattLen)
+	{
+		song.pattPos = song.pattLen - 1;
 		editor.pattPos = song.pattPos;
-
-		checkMarkLimits();
-
-		if (audioWasntLocked)
-			unlockAudio();
-
-		editor.ui.updatePatternEditor = true;
-		editor.ui.updatePosSections = true;
-
-		return true;
 	}
 
-	newPtr = (tonTyp *)realloc(patt[nr], len * TRACK_WIDTH);
-	if (newPtr == NULL)
-	{
-		okBox(0, "Status message", "Not enough memory!");
-
-		if (audioWasntLocked)
-			unlockAudio();
-
-		return false;
-	}
-
-	patt[nr] = newPtr;
-
-	// if we enlarged the pattern length, wipe the new data
-	if (len >= pattLens[nr])
-	{
-		if (len > pattLens[nr])
-			memset(&patt[nr][pattLens[nr] * MAX_VOICES], 0, (len - pattLens[nr]) * TRACK_WIDTH);
-
-		pattLens[nr] = len;
-	}
-	else
-	{
-		pattLens[nr] = len;
-		killPatternIfUnused(nr);
-	}
-
-	if (editor.editPattern == song.pattNr)
-		song.pattLen = pattLens[nr];
-
-	if (song.pattPos >= pattLens[nr])
-		song.pattPos = pattLens[nr] - 1;
-
-	editor.pattPos = song.pattPos;
-
 	checkMarkLimits();
 
 	if (audioWasntLocked)
@@ -228,8 +182,6 @@
 
 	editor.ui.updatePatternEditor = true;
 	editor.ui.updatePosSections = true;
-
-	return true;
 }
 
 int16_t getUsedSamples(int16_t nr)
@@ -2203,7 +2155,7 @@
 	if (audioWasntLocked)
 		unlockAudio();
 
-	setPos(0, 0);
+	setPos(0, 0, false);
 
 	if (!songPlaying)
 	{
@@ -2212,7 +2164,7 @@
 	}
 }
 
-void setPos(int16_t songPos, int16_t pattPos)
+void setPos(int16_t songPos, int16_t pattPos, bool resetTimer)
 {
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
@@ -2255,7 +2207,8 @@
 		}
 	}
 
-	song.timer = 1;
+	if (resetTimer)
+		song.timer = 1;
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -2836,7 +2789,7 @@
 	song.initialTempo = song.tempo;
 
 	setFrqTab(true);
-	setPos(0, 0);
+	setPos(0, 0, true);
 
 	if (!allocateInstr(0))
 	{
@@ -2876,9 +2829,9 @@
 	assert(mode != PLAYMODE_IDLE && mode != PLAYMODE_EDIT);
 
 	if (mode == PLAYMODE_PATT || mode == PLAYMODE_RECPATT)
-		setPos(-1, row);
+		setPos(-1, row, true);
 	else
-		setPos(editor.songPos, row);
+		setPos(editor.songPos, row, true);
 
 	playMode = mode;
 	songPlaying = true;
@@ -3181,12 +3134,15 @@
 
 void decSongPos(void)
 {
+	if (song.songPos == 0)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
 
 	if (song.songPos > 0)
-		setPos(song.songPos - 1, 0);
+		setPos(song.songPos - 1, 0, true);
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -3194,12 +3150,15 @@
 
 void incSongPos(void)
 {
+	if (song.songPos == song.len-1)
+		return;
+
 	bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
 		lockAudio();
 
 	if (song.songPos < song.len-1)
-		setPos(song.songPos + 1, 0);
+		setPos(song.songPos + 1, 0, true);
 
 	if (audioWasntLocked)
 		unlockAudio();
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -278,7 +278,7 @@
 void startPlaying(int8_t mode, int16_t row);
 void stopPlaying(void);
 void stopVoices(void);
-void setPos(int16_t songPos, int16_t pattPos);
+void setPos(int16_t songPos, int16_t pattPos, bool resetTimer);
 void pauseMusic(void); // stops reading pattern data
 void resumeMusic(void); // starts reading pattern data
 void setSongModifiedFlag(void);
@@ -291,7 +291,7 @@
 void conv16BitSample(int8_t *p, int32_t len, bool stereo);
 void delta2Samp(int8_t *p, int32_t len, uint8_t typ);
 void samp2Delta(int8_t *p, int32_t len, uint8_t typ);
-bool setPatternLen(uint16_t nr, int16_t len);
+void setPatternLen(uint16_t nr, int16_t len);
 void setFrqTab(bool linear);
 void mainPlayer(void); // periodically called from audio callback
 void resetChannels(void);
--- a/src/ft2_sample_ed.c
+++ b/src/ft2_sample_ed.c
@@ -300,7 +300,7 @@
 	// replayer is shifting the finetune to the right by 3
 	dFTune = (s->fine >> 3) / (128.0 / (double)(1 << 3));
 
-	return (uint32_t)round(8363.0 * pow(2.0, (s->relTon + dFTune) / 12.0));
+	return (uint32_t)round(8363.0 * exp2((s->relTon + dFTune) / 12.0));
 }
 
 int32_t getSampleRangeStart(void)
--- a/src/ft2_sample_ed_features.c
+++ b/src/ft2_sample_ed_features.c
@@ -84,7 +84,7 @@
 	int8_t *p1, *p2, *src8, *dst8;
 	int16_t *src16, *dst16;
 	uint32_t newLen, mask, resampleLen;
-	uint64_t posfrac64, delta64;
+	uint64_t posFrac64, delta64;
 	double dNewLen, dLenMul;
 	sampleTyp *s;
 
@@ -96,7 +96,7 @@
 	s = &instr[editor.curInstr]->samp[editor.curSmp];
 
 	mask = (s->typ & 16) ? 0xFFFFFFFE : 0xFFFFFFFF;
-	dLenMul = pow(2.0, smpEd_RelReSmp * (1.0 / 12.0));
+	dLenMul = exp2(smpEd_RelReSmp / 12.0);
 
 	dNewLen = s->len * dLenMul;
 	if (dNewLen > (double)MAX_SAMPLE_LEN)
@@ -116,9 +116,9 @@
 	p1 = s->pek;
 
 	// don't use the potentially clamped newLen value here
-	delta64 = ((uint64_t)s->len << 32) / (uint64_t)(s->len * dLenMul);
+	delta64 = ((uint64_t)s->len << 32) / (uint64_t)(s->len * dLenMul); // 32.32 fixed point delta
 
-	posfrac64 = 0;
+	posFrac64 = 0; // 32.32 fixed point position.fraction
 
 	pauseAudio();
 	restoreSample(s);
@@ -133,8 +133,8 @@
 			resampleLen = newLen / 2;
 			for (uint32_t i = 0; i < resampleLen; i++)
 			{
-				dst16[i] = src16[posfrac64 >> 32];
-				posfrac64 += delta64;
+				dst16[i] = src16[posFrac64 >> 32];
+				posFrac64 += delta64;
 			}
 		}
 		else
@@ -144,8 +144,8 @@
 
 			for (uint32_t i = 0; i < newLen; i++)
 			{
-				dst8[i] = src8[posfrac64 >> 32];
-				posfrac64 += delta64;
+				dst8[i] = src8[posFrac64 >> 32];
+				posFrac64 += delta64;
 			}
 		}
 	}
@@ -159,8 +159,8 @@
 	s->repS = (int32_t)(s->repS * dLenMul) & mask;
 	s->repL = (int32_t)(s->repL * dLenMul) & mask;
 
-	if (s->repS > s->len)
-		s->repS = s->len;
+	if (s->repS >= s->len)
+		s->repS = s->len - 1;
 
 	if (s->repS+s->repL > s->len)
 		s->repL = s->len - s->repS;
@@ -172,7 +172,7 @@
 		s->repL &= 0xFFFFFFFE;
 	}
 
-	if (s->repL == 0)
+	if (s->repL <= 0)
 		s->typ &= ~3; // disable loop
 
 	fixSample(s);
@@ -228,7 +228,7 @@
 	s = &instr[editor.curInstr]->samp[editor.curSmp];
 
 	mask = (s->typ & 16) ? 0xFFFFFFFE : 0xFFFFFFFF;
-	dLenMul = pow(2.0, smpEd_RelReSmp * (1.0 / 12.0));
+	dLenMul = exp2(smpEd_RelReSmp / 12.0);
 
 	dNewLen = s->len * dLenMul;
 	if (dNewLen > (double)MAX_SAMPLE_LEN)
--- a/src/ft2_trim.c
+++ b/src/ft2_trim.c
@@ -99,7 +99,7 @@
 		i--;
 
 	/* Yes, 'i' can be -1 here, and will be set to at least 0
-	** because of ins->ta values. Possibly an FT2 bug... */
+	 * because of ins->ta values. Possibly an FT2 bug... */
 	for (j = 0; j < 96; j++)
 	{
 		if (ins->ta[j] > i)
@@ -655,7 +655,7 @@
 				return false;
 		}
 
-		scanPtr += sizeof (tonTyp) * MAX_VOICES;
+		scanPtr += TRACK_WIDTH;
 	}
 
 	return true;
@@ -970,7 +970,7 @@
 void trimThreadDone(void)
 {
 	if (removePatt)
-		setPos(song.songPos, song.pattPos);
+		setPos(song.songPos, song.pattPos, false);
 
 	if (removeInst)
 	{
--- a/src/ft2_video.c
+++ b/src/ft2_video.c
@@ -266,6 +266,11 @@
 		video.renderX = 0;
 		video.renderY = 0;
 	}
+
+	// for mouse cursor creation
+	video.xScale = (uint32_t)round(video.renderW / (double)SCREEN_W);
+	video.yScale = (uint32_t)round(video.renderH / (double)SCREEN_H);
+	createMouseCursors();
 }
 
 void enterFullscreen(void)
@@ -887,8 +892,8 @@
 		video.vsync60HzPresent = false;
 
 	video.window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
-					SCREEN_W * video.upscaleFactor, SCREEN_H * video.upscaleFactor,
-					windowFlags);
+		SCREEN_W * video.upscaleFactor, SCREEN_H * video.upscaleFactor,
+		windowFlags);
 
 	if (video.window == NULL)
 	{
@@ -909,7 +914,7 @@
 {
 	uint32_t rendererFlags;
 
-	rendererFlags = 0;
+	rendererFlags = SDL_RENDERER_ACCELERATED;
 	if (video.vsync60HzPresent)
 		rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
 
@@ -1016,6 +1021,13 @@
 						else if (playMode == PLAYMODE_RECPATT) textOut(115, 80, PAL_FORGRND, "> Rec. ptn. <");
 					}
 				}
+			}
+
+			if (editor.ui.updatePosEdScrollBar)
+			{
+				editor.ui.updatePosEdScrollBar = false;
+				setScrollBarPos(SB_POS_ED, song.songPos, false);
+				setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
 			}
 
 			if (!editor.ui.extended)
--- a/src/ft2_video.h
+++ b/src/ft2_video.h
@@ -22,8 +22,8 @@
 	bool fullscreen, vsync60HzPresent, showFPSCounter;
 	int32_t renderX, renderY, renderW, renderH, displayW, displayH;
 	uint32_t *frameBuffer, palette[PAL_NUM], vblankTimeLen, vblankTimeLenFrac;
-	uint32_t xScaleMul, yScaleMul;
-	double dMonitorRefreshRate;
+	uint32_t xScale, yScale;
+	double dMonitorRefreshRate, dMouseXMul, dMouseYMul;
 #ifdef _WIN32
 	HWND hWnd;
 #endif
--- a/src/ft2_wav_renderer.c
+++ b/src/ft2_wav_renderer.c
@@ -169,7 +169,7 @@
 
 	editor.wavIsRendering = true;
 
-	setPos(songPos, 0);
+	setPos(songPos, 0, true);
 	playMode = PLAYMODE_SONG;
 	songPlaying = true;
 
--- a/src/helpdata/FT2.HLP
+++ b/src/helpdata/FT2.HLP
@@ -864,9 +864,10 @@
 macOS/OS X: Change ALT+F4/ALT+F5 keys in the OS to something else. Also for GNU/Linux.
 >@X020
 >@C001Q: The mouse cursor is delayed/laggy!
->@C002A: Enable "VSync off" in Config -> Miscellaneous. This however, will
->@X035introduce stuttering because the rendering rate is not exact to your
->monitor's rate. Alternatively, enable "Hardware mouse" in Config -> Layout.
+>@C002A: Make sure "Software mouse" is disabled in Config -> Layout.
+>@X035Alternatively, you can enable "VSync off" in Config -> Miscellaneous.
+>This however, will introduce stuttering because the rendering rate is
+>not exact to your monitor's rate.
 >@X020
 >@C001Q: Will you implement MIDI out functionality?
 >@C002A: No, sorry. This is very difficult to implement correctly when having
@@ -911,6 +912,8 @@
 I only poll input once per frame (60Hz), so the frequency is a tad low. It has
 to be like this for several reasons, though...
 >@X010
+>@C002- macOS / OS X: "Hardware mode" mouse looks blurry on retina Macs
+>@X010
 >- macOS / OS X: Holding down a mouse button won't trap the mouse cursor
 >@X021inside the window. This is related to a kludge that simply doesn't work
 very well in macOS. The mouse movement would freeze for some time after a mouse
@@ -929,9 +932,5 @@
 >@X021you may experience visual stuttering because VSync will not be used then.
 I highly recommend running your monitor at 60Hz if you're a hardcore user
 of this program.
->
->@X010- macOS / OS X: If "Hardware mouse" is enabled, the mouse cursor won't
->@X021change when the program is busy. The macOS "wait" cursor is missing in
-SDL2...
 
 END
--- a/src/helpdata/ft2_help_data.h
+++ b/src/helpdata/ft2_help_data.h
@@ -3,9 +3,9 @@
 
 #include <stdint.h>
 
-#define HELP_DATA_LEN 27975
+#define HELP_DATA_LEN 27914
 
-const uint8_t helpData[27975] =
+const uint8_t helpData[27914] =
 {
 	0x4C,0x3B,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
 	0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,0x2A,
@@ -2060,26 +2060,28 @@
 	0x30,0x31,0x51,0x3A,0x20,0x54,0x68,0x65,0x20,0x6D,0x6F,0x75,
 	0x73,0x65,0x20,0x63,0x75,0x72,0x73,0x6F,0x72,0x20,0x69,0x73,
 	0x20,0x64,0x65,0x6C,0x61,0x79,0x65,0x64,0x2F,0x6C,0x61,0x67,
-	0x67,0x79,0x21,0x4A,0x3E,0x40,0x43,0x30,0x30,0x32,0x41,0x3A,
-	0x20,0x45,0x6E,0x61,0x62,0x6C,0x65,0x20,0x22,0x56,0x53,0x79,
-	0x6E,0x63,0x20,0x6F,0x66,0x66,0x22,0x20,0x69,0x6E,0x20,0x43,
-	0x6F,0x6E,0x66,0x69,0x67,0x20,0x2D,0x3E,0x20,0x4D,0x69,0x73,
-	0x63,0x65,0x6C,0x6C,0x61,0x6E,0x65,0x6F,0x75,0x73,0x2E,0x20,
-	0x54,0x68,0x69,0x73,0x20,0x68,0x6F,0x77,0x65,0x76,0x65,0x72,
-	0x2C,0x20,0x77,0x69,0x6C,0x6C,0x4A,0x3E,0x40,0x58,0x30,0x33,
-	0x35,0x69,0x6E,0x74,0x72,0x6F,0x64,0x75,0x63,0x65,0x20,0x73,
+	0x67,0x79,0x21,0x44,0x3E,0x40,0x43,0x30,0x30,0x32,0x41,0x3A,
+	0x20,0x4D,0x61,0x6B,0x65,0x20,0x73,0x75,0x72,0x65,0x20,0x22,
+	0x53,0x6F,0x66,0x74,0x77,0x61,0x72,0x65,0x20,0x6D,0x6F,0x75,
+	0x73,0x65,0x22,0x20,0x69,0x73,0x20,0x64,0x69,0x73,0x61,0x62,
+	0x6C,0x65,0x64,0x20,0x69,0x6E,0x20,0x43,0x6F,0x6E,0x66,0x69,
+	0x67,0x20,0x2D,0x3E,0x20,0x4C,0x61,0x79,0x6F,0x75,0x74,0x2E,
+	0x4B,0x3E,0x40,0x58,0x30,0x33,0x35,0x41,0x6C,0x74,0x65,0x72,
+	0x6E,0x61,0x74,0x69,0x76,0x65,0x6C,0x79,0x2C,0x20,0x79,0x6F,
+	0x75,0x20,0x63,0x61,0x6E,0x20,0x65,0x6E,0x61,0x62,0x6C,0x65,
+	0x20,0x22,0x56,0x53,0x79,0x6E,0x63,0x20,0x6F,0x66,0x66,0x22,
+	0x20,0x69,0x6E,0x20,0x43,0x6F,0x6E,0x66,0x69,0x67,0x20,0x2D,
+	0x3E,0x20,0x4D,0x69,0x73,0x63,0x65,0x6C,0x6C,0x61,0x6E,0x65,
+	0x6F,0x75,0x73,0x2E,0x46,0x3E,0x54,0x68,0x69,0x73,0x20,0x68,
+	0x6F,0x77,0x65,0x76,0x65,0x72,0x2C,0x20,0x77,0x69,0x6C,0x6C,
+	0x20,0x69,0x6E,0x74,0x72,0x6F,0x64,0x75,0x63,0x65,0x20,0x73,
 	0x74,0x75,0x74,0x74,0x65,0x72,0x69,0x6E,0x67,0x20,0x62,0x65,
 	0x63,0x61,0x75,0x73,0x65,0x20,0x74,0x68,0x65,0x20,0x72,0x65,
 	0x6E,0x64,0x65,0x72,0x69,0x6E,0x67,0x20,0x72,0x61,0x74,0x65,
-	0x20,0x69,0x73,0x20,0x6E,0x6F,0x74,0x20,0x65,0x78,0x61,0x63,
-	0x74,0x20,0x74,0x6F,0x20,0x79,0x6F,0x75,0x72,0x4C,0x3E,0x6D,
+	0x20,0x69,0x73,0x22,0x3E,0x6E,0x6F,0x74,0x20,0x65,0x78,0x61,
+	0x63,0x74,0x20,0x74,0x6F,0x20,0x79,0x6F,0x75,0x72,0x20,0x6D,
 	0x6F,0x6E,0x69,0x74,0x6F,0x72,0x27,0x73,0x20,0x72,0x61,0x74,
-	0x65,0x2E,0x20,0x41,0x6C,0x74,0x65,0x72,0x6E,0x61,0x74,0x69,
-	0x76,0x65,0x6C,0x79,0x2C,0x20,0x65,0x6E,0x61,0x62,0x6C,0x65,
-	0x20,0x22,0x48,0x61,0x72,0x64,0x77,0x61,0x72,0x65,0x20,0x6D,
-	0x6F,0x75,0x73,0x65,0x22,0x20,0x69,0x6E,0x20,0x43,0x6F,0x6E,
-	0x66,0x69,0x67,0x20,0x2D,0x3E,0x20,0x4C,0x61,0x79,0x6F,0x75,
-	0x74,0x2E,0x06,0x3E,0x40,0x58,0x30,0x32,0x30,0x33,0x3E,0x40,
+	0x65,0x2E,0x06,0x3E,0x40,0x58,0x30,0x32,0x30,0x33,0x3E,0x40,
 	0x43,0x30,0x30,0x31,0x51,0x3A,0x20,0x57,0x69,0x6C,0x6C,0x20,
 	0x79,0x6F,0x75,0x20,0x69,0x6D,0x70,0x6C,0x65,0x6D,0x65,0x6E,
 	0x74,0x20,0x4D,0x49,0x44,0x49,0x20,0x6F,0x75,0x74,0x20,0x66,
@@ -2248,97 +2250,90 @@
 	0x65,0x20,0x74,0x68,0x69,0x73,0x20,0x66,0x6F,0x72,0x20,0x73,
 	0x65,0x76,0x65,0x72,0x61,0x6C,0x20,0x72,0x65,0x61,0x73,0x6F,
 	0x6E,0x73,0x2C,0x20,0x74,0x68,0x6F,0x75,0x67,0x68,0x2E,0x2E,
-	0x2E,0x06,0x3E,0x40,0x58,0x30,0x31,0x30,0x48,0x3E,0x2D,0x20,
-	0x6D,0x61,0x63,0x4F,0x53,0x20,0x2F,0x20,0x4F,0x53,0x20,0x58,
-	0x3A,0x20,0x48,0x6F,0x6C,0x64,0x69,0x6E,0x67,0x20,0x64,0x6F,
-	0x77,0x6E,0x20,0x61,0x20,0x6D,0x6F,0x75,0x73,0x65,0x20,0x62,
-	0x75,0x74,0x74,0x6F,0x6E,0x20,0x77,0x6F,0x6E,0x27,0x74,0x20,
-	0x74,0x72,0x61,0x70,0x20,0x74,0x68,0x65,0x20,0x6D,0x6F,0x75,
-	0x73,0x65,0x20,0x63,0x75,0x72,0x73,0x6F,0x72,0x4D,0x3E,0x40,
-	0x58,0x30,0x32,0x31,0x69,0x6E,0x73,0x69,0x64,0x65,0x20,0x74,
-	0x68,0x65,0x20,0x77,0x69,0x6E,0x64,0x6F,0x77,0x2E,0x20,0x54,
-	0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x72,0x65,0x6C,0x61,0x74,
-	0x65,0x64,0x20,0x74,0x6F,0x20,0x61,0x20,0x6B,0x6C,0x75,0x64,
-	0x67,0x65,0x20,0x74,0x68,0x61,0x74,0x20,0x73,0x69,0x6D,0x70,
-	0x6C,0x79,0x20,0x64,0x6F,0x65,0x73,0x6E,0x27,0x74,0x20,0x77,
-	0x6F,0x72,0x6B,0x4F,0x76,0x65,0x72,0x79,0x20,0x77,0x65,0x6C,
-	0x6C,0x20,0x69,0x6E,0x20,0x6D,0x61,0x63,0x4F,0x53,0x2E,0x20,
-	0x54,0x68,0x65,0x20,0x6D,0x6F,0x75,0x73,0x65,0x20,0x6D,0x6F,
-	0x76,0x65,0x6D,0x65,0x6E,0x74,0x20,0x77,0x6F,0x75,0x6C,0x64,
-	0x20,0x66,0x72,0x65,0x65,0x7A,0x65,0x20,0x66,0x6F,0x72,0x20,
-	0x73,0x6F,0x6D,0x65,0x20,0x74,0x69,0x6D,0x65,0x20,0x61,0x66,
-	0x74,0x65,0x72,0x20,0x61,0x20,0x6D,0x6F,0x75,0x73,0x65,0x15,
-	0x62,0x75,0x74,0x74,0x6F,0x6E,0x20,0x77,0x61,0x73,0x20,0x68,
-	0x65,0x6C,0x64,0x20,0x64,0x6F,0x77,0x6E,0x2E,0x06,0x3E,0x40,
-	0x58,0x30,0x31,0x30,0x4B,0x3E,0x40,0x43,0x30,0x30,0x32,0x2D,
-	0x20,0x54,0x68,0x65,0x20,0x22,0x63,0x6C,0x65,0x61,0x72,0x20,
-	0x73,0x61,0x6D,0x70,0x6C,0x65,0x22,0x20,0x73,0x68,0x6F,0x72,
-	0x74,0x63,0x75,0x74,0x20,0x28,0x73,0x68,0x69,0x66,0x74,0x20,
-	0x2B,0x20,0x6E,0x75,0x6D,0x2D,0x70,0x61,0x64,0x20,0x44,0x65,
-	0x6C,0x2F,0x27,0x2C,0x27,0x29,0x20,0x6F,0x6E,0x6C,0x79,0x20,
-	0x77,0x6F,0x72,0x6B,0x73,0x20,0x69,0x66,0x37,0x3E,0x40,0x58,
-	0x30,0x32,0x31,0x6E,0x75,0x6D,0x20,0x6C,0x6F,0x63,0x6B,0x20,
-	0x69,0x73,0x20,0x6F,0x66,0x66,0x2E,0x20,0x54,0x68,0x65,0x72,
-	0x65,0x27,0x73,0x20,0x6E,0x6F,0x20,0x77,0x61,0x79,0x20,0x49,
-	0x20,0x63,0x61,0x6E,0x20,0x66,0x69,0x78,0x20,0x74,0x68,0x69,
-	0x73,0x2E,0x2E,0x2E,0x06,0x3E,0x40,0x58,0x30,0x31,0x30,0x0C,
-	0x3E,0x40,0x43,0x30,0x30,0x31,0x56,0x69,0x64,0x65,0x6F,0x3A,
-	0x06,0x3E,0x40,0x43,0x30,0x30,0x32,0x4A,0x3E,0x40,0x58,0x30,
-	0x31,0x30,0x2D,0x20,0x54,0x68,0x65,0x20,0x73,0x63,0x6F,0x70,
-	0x65,0x73,0x20,0x63,0x61,0x6E,0x20,0x6D,0x69,0x6C,0x64,0x6C,
-	0x79,0x20,0x66,0x6C,0x69,0x63,0x6B,0x65,0x72,0x20,0x64,0x65,
-	0x70,0x65,0x6E,0x64,0x69,0x6E,0x67,0x20,0x6F,0x6E,0x20,0x74,
-	0x68,0x65,0x20,0x77,0x61,0x76,0x65,0x66,0x6F,0x72,0x6D,0x20,
-	0x61,0x6E,0x64,0x20,0x70,0x69,0x74,0x63,0x68,0x2E,0x4D,0x3E,
-	0x40,0x58,0x30,0x32,0x31,0x54,0x68,0x69,0x73,0x20,0x69,0x73,
-	0x20,0x62,0x65,0x63,0x61,0x75,0x73,0x65,0x20,0x74,0x68,0x65,
-	0x69,0x72,0x20,0x66,0x72,0x65,0x71,0x75,0x65,0x6E,0x63,0x79,
-	0x20,0x69,0x73,0x20,0x6E,0x6F,0x74,0x20,0x63,0x6C,0x6F,0x63,
-	0x6B,0x65,0x64,0x20,0x74,0x6F,0x20,0x65,0x78,0x61,0x63,0x74,
-	0x6C,0x79,0x20,0x74,0x68,0x65,0x20,0x73,0x61,0x6D,0x65,0x20,
-	0x72,0x61,0x74,0x65,0x4D,0x3E,0x61,0x74,0x20,0x77,0x68,0x69,
-	0x63,0x68,0x20,0x74,0x68,0x65,0x20,0x73,0x63,0x6F,0x70,0x65,
-	0x73,0x20,0x61,0x72,0x65,0x20,0x72,0x65,0x6E,0x64,0x65,0x72,
-	0x65,0x64,0x2E,0x20,0x49,0x74,0x27,0x73,0x20,0x63,0x6C,0x6F,
-	0x73,0x65,0x2C,0x20,0x77,0x68,0x69,0x63,0x68,0x20,0x63,0x61,
-	0x75,0x73,0x65,0x73,0x20,0x61,0x20,0x66,0x6C,0x69,0x63,0x6B,
-	0x65,0x72,0x20,0x65,0x66,0x66,0x65,0x63,0x74,0x2E,0x01,0x3E,
-	0x52,0x3E,0x40,0x58,0x30,0x31,0x30,0x2D,0x20,0x4E,0x6F,0x74,
-	0x20,0x61,0x20,0x62,0x75,0x67,0x2C,0x20,0x62,0x75,0x74,0x20,
-	0x69,0x66,0x20,0x79,0x6F,0x75,0x72,0x20,0x6D,0x6F,0x6E,0x69,
-	0x74,0x6F,0x72,0x27,0x73,0x20,0x72,0x65,0x66,0x72,0x65,0x73,
-	0x68,0x20,0x72,0x61,0x74,0x65,0x20,0x69,0x73,0x20,0x6E,0x6F,
-	0x74,0x20,0x73,0x65,0x74,0x20,0x74,0x6F,0x20,0x36,0x30,0x48,
-	0x7A,0x20,0x28,0x6F,0x72,0x20,0x35,0x39,0x48,0x7A,0x29,0x4F,
-	0x3E,0x40,0x58,0x30,0x32,0x31,0x79,0x6F,0x75,0x20,0x6D,0x61,
-	0x79,0x20,0x65,0x78,0x70,0x65,0x72,0x69,0x65,0x6E,0x63,0x65,
-	0x20,0x76,0x69,0x73,0x75,0x61,0x6C,0x20,0x73,0x74,0x75,0x74,
-	0x74,0x65,0x72,0x69,0x6E,0x67,0x20,0x62,0x65,0x63,0x61,0x75,
-	0x73,0x65,0x20,0x56,0x53,0x79,0x6E,0x63,0x20,0x77,0x69,0x6C,
-	0x6C,0x20,0x6E,0x6F,0x74,0x20,0x62,0x65,0x20,0x75,0x73,0x65,
-	0x64,0x20,0x74,0x68,0x65,0x6E,0x2E,0x49,0x49,0x20,0x68,0x69,
-	0x67,0x68,0x6C,0x79,0x20,0x72,0x65,0x63,0x6F,0x6D,0x6D,0x65,
-	0x6E,0x64,0x20,0x72,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x79,
-	0x6F,0x75,0x72,0x20,0x6D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,
-	0x61,0x74,0x20,0x36,0x30,0x48,0x7A,0x20,0x69,0x66,0x20,0x79,
-	0x6F,0x75,0x27,0x72,0x65,0x20,0x61,0x20,0x68,0x61,0x72,0x64,
-	0x63,0x6F,0x72,0x65,0x20,0x75,0x73,0x65,0x72,0x10,0x6F,0x66,
-	0x20,0x74,0x68,0x69,0x73,0x20,0x70,0x72,0x6F,0x67,0x72,0x61,
-	0x6D,0x2E,0x01,0x3E,0x4C,0x3E,0x40,0x58,0x30,0x31,0x30,0x2D,
-	0x20,0x6D,0x61,0x63,0x4F,0x53,0x20,0x2F,0x20,0x4F,0x53,0x20,
-	0x58,0x3A,0x20,0x49,0x66,0x20,0x22,0x48,0x61,0x72,0x64,0x77,
-	0x61,0x72,0x65,0x20,0x6D,0x6F,0x75,0x73,0x65,0x22,0x20,0x69,
-	0x73,0x20,0x65,0x6E,0x61,0x62,0x6C,0x65,0x64,0x2C,0x20,0x74,
-	0x68,0x65,0x20,0x6D,0x6F,0x75,0x73,0x65,0x20,0x63,0x75,0x72,
-	0x73,0x6F,0x72,0x20,0x77,0x6F,0x6E,0x27,0x74,0x4C,0x3E,0x40,
-	0x58,0x30,0x32,0x31,0x63,0x68,0x61,0x6E,0x67,0x65,0x20,0x77,
-	0x68,0x65,0x6E,0x20,0x74,0x68,0x65,0x20,0x70,0x72,0x6F,0x67,
-	0x72,0x61,0x6D,0x20,0x69,0x73,0x20,0x62,0x75,0x73,0x79,0x2E,
-	0x20,0x54,0x68,0x65,0x20,0x6D,0x61,0x63,0x4F,0x53,0x20,0x22,
-	0x77,0x61,0x69,0x74,0x22,0x20,0x63,0x75,0x72,0x73,0x6F,0x72,
-	0x20,0x69,0x73,0x20,0x6D,0x69,0x73,0x73,0x69,0x6E,0x67,0x20,
-	0x69,0x6E,0x07,0x53,0x44,0x4C,0x32,0x2E,0x2E,0x2E,0x00,0x03,
-	0x45,0x4E,0x44
+	0x2E,0x06,0x3E,0x40,0x58,0x30,0x31,0x30,0x47,0x3E,0x40,0x43,
+	0x30,0x30,0x32,0x2D,0x20,0x6D,0x61,0x63,0x4F,0x53,0x20,0x2F,
+	0x20,0x4F,0x53,0x20,0x58,0x3A,0x20,0x22,0x48,0x61,0x72,0x64,
+	0x77,0x61,0x72,0x65,0x20,0x6D,0x6F,0x64,0x65,0x22,0x20,0x6D,
+	0x6F,0x75,0x73,0x65,0x20,0x6C,0x6F,0x6F,0x6B,0x73,0x20,0x62,
+	0x6C,0x75,0x72,0x72,0x79,0x20,0x6F,0x6E,0x20,0x72,0x65,0x74,
+	0x69,0x6E,0x61,0x20,0x4D,0x61,0x63,0x73,0x06,0x3E,0x40,0x58,
+	0x30,0x31,0x30,0x48,0x3E,0x2D,0x20,0x6D,0x61,0x63,0x4F,0x53,
+	0x20,0x2F,0x20,0x4F,0x53,0x20,0x58,0x3A,0x20,0x48,0x6F,0x6C,
+	0x64,0x69,0x6E,0x67,0x20,0x64,0x6F,0x77,0x6E,0x20,0x61,0x20,
+	0x6D,0x6F,0x75,0x73,0x65,0x20,0x62,0x75,0x74,0x74,0x6F,0x6E,
+	0x20,0x77,0x6F,0x6E,0x27,0x74,0x20,0x74,0x72,0x61,0x70,0x20,
+	0x74,0x68,0x65,0x20,0x6D,0x6F,0x75,0x73,0x65,0x20,0x63,0x75,
+	0x72,0x73,0x6F,0x72,0x4D,0x3E,0x40,0x58,0x30,0x32,0x31,0x69,
+	0x6E,0x73,0x69,0x64,0x65,0x20,0x74,0x68,0x65,0x20,0x77,0x69,
+	0x6E,0x64,0x6F,0x77,0x2E,0x20,0x54,0x68,0x69,0x73,0x20,0x69,
+	0x73,0x20,0x72,0x65,0x6C,0x61,0x74,0x65,0x64,0x20,0x74,0x6F,
+	0x20,0x61,0x20,0x6B,0x6C,0x75,0x64,0x67,0x65,0x20,0x74,0x68,
+	0x61,0x74,0x20,0x73,0x69,0x6D,0x70,0x6C,0x79,0x20,0x64,0x6F,
+	0x65,0x73,0x6E,0x27,0x74,0x20,0x77,0x6F,0x72,0x6B,0x4F,0x76,
+	0x65,0x72,0x79,0x20,0x77,0x65,0x6C,0x6C,0x20,0x69,0x6E,0x20,
+	0x6D,0x61,0x63,0x4F,0x53,0x2E,0x20,0x54,0x68,0x65,0x20,0x6D,
+	0x6F,0x75,0x73,0x65,0x20,0x6D,0x6F,0x76,0x65,0x6D,0x65,0x6E,
+	0x74,0x20,0x77,0x6F,0x75,0x6C,0x64,0x20,0x66,0x72,0x65,0x65,
+	0x7A,0x65,0x20,0x66,0x6F,0x72,0x20,0x73,0x6F,0x6D,0x65,0x20,
+	0x74,0x69,0x6D,0x65,0x20,0x61,0x66,0x74,0x65,0x72,0x20,0x61,
+	0x20,0x6D,0x6F,0x75,0x73,0x65,0x15,0x62,0x75,0x74,0x74,0x6F,
+	0x6E,0x20,0x77,0x61,0x73,0x20,0x68,0x65,0x6C,0x64,0x20,0x64,
+	0x6F,0x77,0x6E,0x2E,0x06,0x3E,0x40,0x58,0x30,0x31,0x30,0x4B,
+	0x3E,0x40,0x43,0x30,0x30,0x32,0x2D,0x20,0x54,0x68,0x65,0x20,
+	0x22,0x63,0x6C,0x65,0x61,0x72,0x20,0x73,0x61,0x6D,0x70,0x6C,
+	0x65,0x22,0x20,0x73,0x68,0x6F,0x72,0x74,0x63,0x75,0x74,0x20,
+	0x28,0x73,0x68,0x69,0x66,0x74,0x20,0x2B,0x20,0x6E,0x75,0x6D,
+	0x2D,0x70,0x61,0x64,0x20,0x44,0x65,0x6C,0x2F,0x27,0x2C,0x27,
+	0x29,0x20,0x6F,0x6E,0x6C,0x79,0x20,0x77,0x6F,0x72,0x6B,0x73,
+	0x20,0x69,0x66,0x37,0x3E,0x40,0x58,0x30,0x32,0x31,0x6E,0x75,
+	0x6D,0x20,0x6C,0x6F,0x63,0x6B,0x20,0x69,0x73,0x20,0x6F,0x66,
+	0x66,0x2E,0x20,0x54,0x68,0x65,0x72,0x65,0x27,0x73,0x20,0x6E,
+	0x6F,0x20,0x77,0x61,0x79,0x20,0x49,0x20,0x63,0x61,0x6E,0x20,
+	0x66,0x69,0x78,0x20,0x74,0x68,0x69,0x73,0x2E,0x2E,0x2E,0x06,
+	0x3E,0x40,0x58,0x30,0x31,0x30,0x0C,0x3E,0x40,0x43,0x30,0x30,
+	0x31,0x56,0x69,0x64,0x65,0x6F,0x3A,0x06,0x3E,0x40,0x43,0x30,
+	0x30,0x32,0x4A,0x3E,0x40,0x58,0x30,0x31,0x30,0x2D,0x20,0x54,
+	0x68,0x65,0x20,0x73,0x63,0x6F,0x70,0x65,0x73,0x20,0x63,0x61,
+	0x6E,0x20,0x6D,0x69,0x6C,0x64,0x6C,0x79,0x20,0x66,0x6C,0x69,
+	0x63,0x6B,0x65,0x72,0x20,0x64,0x65,0x70,0x65,0x6E,0x64,0x69,
+	0x6E,0x67,0x20,0x6F,0x6E,0x20,0x74,0x68,0x65,0x20,0x77,0x61,
+	0x76,0x65,0x66,0x6F,0x72,0x6D,0x20,0x61,0x6E,0x64,0x20,0x70,
+	0x69,0x74,0x63,0x68,0x2E,0x4D,0x3E,0x40,0x58,0x30,0x32,0x31,
+	0x54,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x62,0x65,0x63,0x61,
+	0x75,0x73,0x65,0x20,0x74,0x68,0x65,0x69,0x72,0x20,0x66,0x72,
+	0x65,0x71,0x75,0x65,0x6E,0x63,0x79,0x20,0x69,0x73,0x20,0x6E,
+	0x6F,0x74,0x20,0x63,0x6C,0x6F,0x63,0x6B,0x65,0x64,0x20,0x74,
+	0x6F,0x20,0x65,0x78,0x61,0x63,0x74,0x6C,0x79,0x20,0x74,0x68,
+	0x65,0x20,0x73,0x61,0x6D,0x65,0x20,0x72,0x61,0x74,0x65,0x4D,
+	0x3E,0x61,0x74,0x20,0x77,0x68,0x69,0x63,0x68,0x20,0x74,0x68,
+	0x65,0x20,0x73,0x63,0x6F,0x70,0x65,0x73,0x20,0x61,0x72,0x65,
+	0x20,0x72,0x65,0x6E,0x64,0x65,0x72,0x65,0x64,0x2E,0x20,0x49,
+	0x74,0x27,0x73,0x20,0x63,0x6C,0x6F,0x73,0x65,0x2C,0x20,0x77,
+	0x68,0x69,0x63,0x68,0x20,0x63,0x61,0x75,0x73,0x65,0x73,0x20,
+	0x61,0x20,0x66,0x6C,0x69,0x63,0x6B,0x65,0x72,0x20,0x65,0x66,
+	0x66,0x65,0x63,0x74,0x2E,0x01,0x3E,0x52,0x3E,0x40,0x58,0x30,
+	0x31,0x30,0x2D,0x20,0x4E,0x6F,0x74,0x20,0x61,0x20,0x62,0x75,
+	0x67,0x2C,0x20,0x62,0x75,0x74,0x20,0x69,0x66,0x20,0x79,0x6F,
+	0x75,0x72,0x20,0x6D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x27,0x73,
+	0x20,0x72,0x65,0x66,0x72,0x65,0x73,0x68,0x20,0x72,0x61,0x74,
+	0x65,0x20,0x69,0x73,0x20,0x6E,0x6F,0x74,0x20,0x73,0x65,0x74,
+	0x20,0x74,0x6F,0x20,0x36,0x30,0x48,0x7A,0x20,0x28,0x6F,0x72,
+	0x20,0x35,0x39,0x48,0x7A,0x29,0x4F,0x3E,0x40,0x58,0x30,0x32,
+	0x31,0x79,0x6F,0x75,0x20,0x6D,0x61,0x79,0x20,0x65,0x78,0x70,
+	0x65,0x72,0x69,0x65,0x6E,0x63,0x65,0x20,0x76,0x69,0x73,0x75,
+	0x61,0x6C,0x20,0x73,0x74,0x75,0x74,0x74,0x65,0x72,0x69,0x6E,
+	0x67,0x20,0x62,0x65,0x63,0x61,0x75,0x73,0x65,0x20,0x56,0x53,
+	0x79,0x6E,0x63,0x20,0x77,0x69,0x6C,0x6C,0x20,0x6E,0x6F,0x74,
+	0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64,0x20,0x74,0x68,0x65,
+	0x6E,0x2E,0x49,0x49,0x20,0x68,0x69,0x67,0x68,0x6C,0x79,0x20,
+	0x72,0x65,0x63,0x6F,0x6D,0x6D,0x65,0x6E,0x64,0x20,0x72,0x75,
+	0x6E,0x6E,0x69,0x6E,0x67,0x20,0x79,0x6F,0x75,0x72,0x20,0x6D,
+	0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,0x61,0x74,0x20,0x36,0x30,
+	0x48,0x7A,0x20,0x69,0x66,0x20,0x79,0x6F,0x75,0x27,0x72,0x65,
+	0x20,0x61,0x20,0x68,0x61,0x72,0x64,0x63,0x6F,0x72,0x65,0x20,
+	0x75,0x73,0x65,0x72,0x10,0x6F,0x66,0x20,0x74,0x68,0x69,0x73,
+	0x20,0x70,0x72,0x6F,0x67,0x72,0x61,0x6D,0x2E,0x00,0x03,0x45,
+	0x4E,0x44
 };
 
 #endif