/* OpenCP Module Player
 * copyright (c) 2022-'26 Stian Sebastian Skjelstad <stian.skjelstad@gmail.com>
 *
 * libsidplay setup config dialog
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "config.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "types.h"
#include "boot/plinkman.h"
#include "boot/psetting.h"
#include "filesel/dirdb.h"
#include "filesel/filesystem.h"
#include "filesel/filesystem-drive.h"
#include "filesel/filesystem-file-dev.h"
#include "filesel/filesystem-setup.h"
#include "filesel/pfilesel.h"
#include "stuff/compat.h"
#include "stuff/err.h"
#include "stuff/imsrtns.h"
#include "stuff/poutput.h"

#include "sidconfig.h"
#include "md5.inc.c"

#ifndef MIN
# define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#ifndef MAX
# define MAX(a,b) (((a)>(b))?(a):(b))
#endif

struct browser_t
{
	int isdir;
	int isparent;
#ifdef _WIN32
	int isdrive;
	char drive;
#endif
	uint32_t dirdb_ref;
	char hash_4096[33];
	char hash_8192[33];
};

static char             *entries_location;
static struct browser_t *entries_data;
static int               entries_count;
static int               entries_size;

static int config_emulator;
static int config_defaultC64;
static int config_forceC64;
static int config_defaultSID;
static int config_forceSID;
static int config_CIA;
static int config_filter;
static int config_filtercurve6581;
static int config_filterrange6581;
static int config_waveoffset6581;
static int config_enableOld6581caps;
static int config_filtercurve8580;
static int config_combinedwaveforms;
static int config_digiboost;
static int config_dacLeakageLevel;
static int config_dcBlockResistor;
static char *config_kernal;
static char *config_basic;
static char *config_chargen;

struct browser_t entry_kernal;
struct browser_t entry_basic;
struct browser_t entry_chargen;

#define SEL_EMULATOR           0
#define SEL_DEFAULTC64MODEL    1
#define SEL_FORCEC64MODEL      2
#define SEL_DEFAULTSID         3
#define SEL_FORCESID           4
#define SEL_CIAMODEL           5
#define SEL_FILTER             6
#define SEL_FILTERCURVE6581    7
#define SEL_FILTERRANGE6581    8
#define SEL_WAVEOFFSET6581     9
#define SEL_ENABLEOLD6581CAPS 10
#define SEL_FILTERCURVE8580   11
#define SEL_CWS               12
#define SEL_DIGIBOOST         13
#define SEL_DACLEAKAGELEVEL   14
#define SEL_DCBLOCKRESISTANCE 15
#define SEL_ROM_KERNAL        16
#define SEL_ROM_BASIC         17
#define SEL_ROM_CHARGEN       18
#define SEL_TOTAL_ENTRIES     19
#define SEL_TOTAL_SINGLELINED SEL_ROM_KERNAL

static void ConfigDrawItems (const int lineno, int xpos, const int width, const char **list, const int listlength, const int selected, const int active, const struct DevInterfaceAPI_t *API)
{
	int i;
	int origxpos = xpos;
	for (i=0; i < listlength; i++)
	{
		int l = strlen (list[i]);
		if (selected == i)
		{
			API->console->DisplayPrintf (lineno, xpos, (active)?0x09:0x01, l + 2, "[%.*o%s%.*o]", (active)?0x0f:0x07, list[i], (active)?0x09:0x01);
		} else {
			API->console->DisplayPrintf (lineno, xpos, 0x00, l + 2, " %.*o%s%.0o ", (active)?0x07:0x08, list[i]);
		}
		xpos += l + 2;
	}
	API->console->Driver->DisplayVoid (lineno, xpos, width - xpos + origxpos);
}

static void ConfigDrawMenuItems (const int lineno, int xpos, const int width, const int number, const char *item, const char **list, const int listlength, const int selected, const int active, const int correctemulator, const struct DevInterfaceAPI_t *API)
{
	API->console->DisplayPrintf (lineno, xpos, 0x07, 22, "%d %.*o%s:", number + 1, correctemulator ? 7 : 8, item);
	ConfigDrawItems (lineno, xpos + 23, width - 23, list, listlength, selected, active, API);
}

static void ConfigDrawBar (const int lineno, int xpos, int width, int scale, const char *suffix, int minlevel, int maxlevel, int level, const int active, const struct DevInterfaceAPI_t *API)
{
	char prefix[11];
	char min[8];
	char max[7];

	unsigned int abslevel;
	unsigned int absmin;

	int pos, p1, p2;

	assert ((scale == 10) || (scale == 100));

	if (scale == 100)
	{
		level    = saturate (level,    -99999, 99999);
		minlevel = saturate (minlevel, -99999, 99999);
		maxlevel = saturate (maxlevel,      0, 99999);
		abslevel = abs(level);
		absmin   = abs(minlevel);

		snprintf (prefix, sizeof (prefix), "%3d.%02d%s",
			level / scale,
			abslevel % scale,
			suffix);

		snprintf (min, sizeof (min), "%3d.%02d",
			minlevel / scale,
			absmin % scale);

		snprintf (max, sizeof (max), "%3d.%02d",
			maxlevel / scale,
			maxlevel % scale);
	} else {
		level    = saturate (level,    -9999, 9999);
		minlevel = saturate (minlevel, -9999, 9999);
		maxlevel = saturate (maxlevel,     0, 9999);
		abslevel = abs(level);
		absmin   = abs(minlevel);

		snprintf (prefix, sizeof (prefix), "%4d.%01d%s",
			level / scale,
			abslevel % scale,
			suffix);

		snprintf (min, sizeof (min), "%4d.%01d",
			minlevel / scale,
			absmin % scale);

		snprintf (max, sizeof (max), "%4d.%01d",
			maxlevel / scale,
			maxlevel % scale);
	}

	pos = (/*(maxlevel - minlevel / 46) - 1 +*/ (level - minlevel) * 22) / (maxlevel - minlevel);

	p1 = pos;
        p2 = 22 - pos;

	API->console->DisplayPrintf (lineno, xpos, (active)?0x07:0x08, width, "%10s%-7s [%*C.#%*C.] %-6s", prefix, min, p1, p2, max);
}

static void ConfigDrawMenuBar (const int lineno, int xpos, int width, const int number, const char *item, int scale, const char *suffix, int minlevel, int maxlevel, int level, const int active, const int correctemulator, const struct DevInterfaceAPI_t *API)
{
	API->console->DisplayPrintf (lineno, xpos, 0x07, 22, "%d %.*o%s:", number + 1, correctemulator ? 7 : 8, item);
	ConfigDrawBar (lineno, xpos + 23, width - 23, scale, suffix, minlevel, maxlevel, level, active, API);
}

static void ConfigDrawMenuRom (const int lineno, int xpos, int width, const int number, const char *item, int active, const char *path, const struct DevInterfaceAPI_t *API)
{
	API->console->DisplayPrintf (lineno, xpos, 0x07, 22, "%d %s:", number + 1, item);
	API->console->DisplayPrintf (lineno, xpos + 23, 0x07, width - 23, "%.*o%*S",
		active?0x0f:0x08,
		width - 23,
		path);
}

struct hash_pairs_t
{
	const char *hash; const char *description;
};

static struct hash_pairs_t hash_kernal[] =
{
	/* src is libsidplayfp-git/src/romCheck.h */
	{"1ae0ea224f2b291dafa2c20b990bb7d4", "C64 KERNAL first revision"},
	{"7360b296d64e18b88f6cf52289fd99a1", "C64 KERNAL second revision"},
	{"479553fd53346ec84054f0b1c6237397", "C64 KERNAL second revision (Japanese)"},
	{"39065497630802346bce17963f13c092", "C64 KERNAL third revision"},
	{"27e26dbb267c8ebf1cd47105a6ca71e7", "C64 KERNAL third revision (Swedish)"},
	{"27e26dbb267c8ebf1cd47105a6ca71e7", "C64 KERNAL third revision (Swedish C2G007)"},
	{"e4aa56240fe13d8ad8d7d1dc8fec2395", "C64 KERNAL third revision (Danish)"},
	{"174546cf655e874546af4eac5f5bf61b", "C64 KERNAL third revision (Turkish)"},
	{"187b8c713b51931e070872bd390b472a", "Commodore SX-64 KERNAL"},
	{"b7b1a42e11ff8efab4e49afc4faedeee", "Commodore SX-64 KERNAL (Swedish)"},
	{"3abc938cac3d622e1a7041c15b928707", "Cockroach Turbo-ROM"},
	{"631ea2ca0dcda414a90aeefeaf77fe45", "Cockroach Turbo-ROM (SX-64)"},
	{"a9de1832e9be1a8c60f4f979df585681", "Datel DOS-ROM 1.2"},
	{"da43563f218b46ece925f221ef1f4bc2", "Datel Mercury 3 (NTSC)"},
	{"b7dc8ed82170c81773d4f5dc8069a000", "Datel Turbo ROM II (PAL)"},
	{"6b309c76473dcf555c52c598c6a51011", "Dolphin DOS v1.0"},
	{"c3c93b9a46f116acbfe7ee147c338c60", "Dolphin DOS v2.0-1 AU"},
	{"2a441f4abd272d50f94b43c7ff3cc629", "Dolphin DOS v2.0-1"},
	{"c7a175217e67dcb425feca5fcf2a01cc", "Dolphin DOS v2.0-2"},
	{"7a9b1040cfbe769525bb9cdc28427be6", "Dolphin DOS v2.0-3"},
	{"fc8fb5ec89b34ae41c8dc20907447e06", "Dolphin DOS v3.0"},
	{"9a6e1c4b99c6f65323aa96940c7eb7f7", "ExOS v3 fertig"},
	{"3241a4fcf2ba28ba3fc79826bc023814", "ExOS v3"},
	{"cffd2616312801da56bcc6728f0e39ca", "ExOS v4"},
	{"e6e2bb24a0fa414182b0fd149bde689d", "TurboAccess"},
	{"c5c5990f0826fcbd372901e761fab1b7", "TurboTrans v3.0-1"},
	{"042ffc11383849bdf0e600474cefaaaf", "TurboTrans v3.0-2"},
	{"9d62852013fc2c29c3111c765698664b", "Turbo-Process US"},
	{"f9c9838e8d6752dc6066a8c9e6c2e880", "Turbo-Process"},
};
static struct hash_pairs_t hash_basic[] =
{
	{"57af4ae21d4b705c2991d98ed5c1f7b8", "C64 BASIC V2"},
};
static struct hash_pairs_t hash_chargen[] =
{
	{"12a4202f5331d45af846af6c58fba946", "C64 character generator"},
	{"cf32a93c0a693ed359a4f483ef6db53d", "C64 character generator (Japanese)"},
	{"7a1906cd3993ad17a0a0b2b68da9c114", "C64 character generator (Swedish)"},
	{"5973267e85b7b2b574e780874843180b", "C64 character generator (Swedish C2G007)"},
	{"81a1a8e6e334caeadd1b8468bb7728d3", "C64 character generator (Spanish)"},
	{"b3ad62b41b5f919fc56c3a40e636ec29", "C64 character generator (Danish)"},
	{"7d82b1f8f750665b5879c16b03c617d9", "C64 character generator (Turkish)"},
};

static void ConfigDrawHashInfo (const int lineno, int xpos, int width, const char *hash_8192, const char *hash_4096, int expect, const struct DevInterfaceAPI_t *API)
{
	const uint8_t COK = 0x02;
	const uint8_t CERR = 0x04;
	int j;

	for (j=0; j < (sizeof (hash_kernal) / sizeof (hash_kernal[0])); j++)
	{
		if (!strcmp (hash_8192, hash_kernal[j].hash))
		{
			API->console->Driver->DisplayStr (lineno, xpos, (expect==0)?COK:CERR, hash_kernal[j].description, width);
			return;
		}
	}
	for (j=0; j < (sizeof (hash_basic) / sizeof (hash_basic[0])); j++)
	{
		if (!strcmp (hash_8192, hash_basic[j].hash))
		{
			API->console->Driver->DisplayStr (lineno, xpos, (expect==1)?COK:CERR, hash_basic[j].description, width);
			return;
		}
	}
	for (j=0; j < (sizeof (hash_chargen) / sizeof (hash_chargen[0])); j++)
	{
		if (!strcmp (hash_4096, hash_chargen[j].hash))
		{
			API->console->Driver->DisplayStr (lineno, xpos, (expect==2)?COK:CERR, hash_chargen[j].description, width);
			return;
		}
	}

	API->console->Driver->DisplayStr (lineno, xpos, CERR, "Unknown ROM file", width); return;
}

static void ConfigDrawHashMenuInfo (const int lineno, int xpos, int width, const char *hash_8192, const char *hash_4096, int expect,  const struct DevInterfaceAPI_t *API)
{
	ConfigDrawHashInfo (lineno, xpos + 24, width - 24, hash_8192, hash_4096, expect, API);
}

#if 0
 +--------------------- libsidplayfp configuration ---------------------------\
 |  Navigate with <U>,<D>,<L>,<R> and <ENTER>; hit <ESC> to save and exit.    |
 +----------------------------------------------------------------------------+
 |  1: emulator:        [resid] [residfp]                                     | fp=floating point - better but slower
 |  2: default C64:     [PAL] [NTSC] [OLD-NTSC] [DREAN] [PAL-M]               | for SID files that does not specify C64 model
 |  3: force C64 model: [yes] [no]                                            | ignore information in SID files?
 |  4: default SID:     [MOS6581] [MOS8580]                                   | for SID files that does not specify SID model
 |  5: force SID:       [yes] [no]                                            | ignore information in SID files?
 |  6: CIA:             [MOS6526] [MOS6526W4485] [MOS8521]                    | MOS6526 is the classic chip, MOS6526W4485 is a specific batch while MOS8521 is the modern chip
 |  7: filter:          [yes] [no]                                            |
 |  8: filtercurve6581:    0.50      0.0 [           #           ]   1.0      | Default is 0.5
 |  9: filterrange6581:    0.50      0.0 [           #           ]   1.0      | Default is 0.5
 | 10: waveoffset6581:     0.50      0.0 {           #           ]   1.0      | Default is 1.0
 | 11: enable 6581OldCaps yes] no]
 | 12: filtercurve8580:    0.50      0.0 [           #           ]   1.0      | Default is 0.5
 | 13: digiboost:       [yes] [no]                                            |
 | 14: DAC leakage:        1.00      0.0 [                      #]   1.0      | Default is 1.0
 | 15: DC Block Resitor    0.0       0.0 [#                      ]   1.0      | Default is 0.0
 | 16: kernal.rom:      sadfasdfsdfsadf/sdfasdf/sdfasdf/sdfasdf.ROM           | KERNEL.ROM images can be found online. Some SID files requires this file in order to play correctly
 |                           sadflojhsdflkhjasdf                              |
 | 17: basic.rom:       sadfasdfsdfsadf/sdfasdf/sdfasdf/sdfasdf.ROM           | BASIC.ROM images can be found online. Some SID files requires this file in order to play correctly
 |                           sadflojhsdflkhjasdf                              |
 | 18: chargen.rom:     sadfasdfsdfsadf/sdfasdf/sdfasdf/sdfasdf.ROM           | CHARGEN.ROM images can be found online. Some SID files requires this file in order to play correctly
 |                           sadflojhsdflkhjas                                |
 +----------------------------------------------------------------------------|
 |                                                                            |
 |                                                                            |
 \----------------------------------------------------------------------------/
#endif

static void sidConfigDraw (int EditPos, const struct DevInterfaceAPI_t *API)
{
	const int LINES_NOT_AVAILABLE_START = 3;
	const int LINES_NOT_AVAILABLE_STOP = 4;
	const int LINES_NOT_AVAILABLE = LINES_NOT_AVAILABLE_START + LINES_NOT_AVAILABLE_STOP;
	const int maxcontentheight = SEL_TOTAL_ENTRIES + 3 /* 3 of the entries are double-lined */;
	      int contentheight = MIN(maxcontentheight, MAX(API->console->TextHeight - 1 - LINES_NOT_AVAILABLE, LINES_NOT_AVAILABLE + 1));
	      int mlHeight = contentheight + LINES_NOT_AVAILABLE;
	      int mlTop, mlLeft, mlWidth;
	const char *offon[] = {"off", "on"};
	const char *emulators[] = {"residfp", "crSID"};
	const char *C64models[] = {"PAL", "NTSC", "OLD-NTSC", "DREAN", "PAL-M"};
	const char *SIDmodels[] = {"MOS6581", "MOS8580"};
	const char *CIAmodels[] = {"MOS6526", "MOS6526W4485", "MOS8521"};
	const char *combinedwaveforms[] = {"Average", "Weak", "Strong" };

	const int Pos = EditPos + ((EditPos > SEL_TOTAL_SINGLELINED) ? (EditPos - SEL_TOTAL_SINGLELINED) * 2 : 0); /* 3 last lines count double speed */
	int half;
	int skip;
	int dot;

#if (CONSOLE_MIN_Y < 8)
# error sidConfigDraw() requires CONSOLE_MIN_Y >= 8
#endif

	half = contentheight / 2;
	if (contentheight >= maxcontentheight)
	{ /* all entries can fit */
		skip = 0;
		dot = -1;
	} else if (Pos < half)
	{ /* we are in the top part */
		skip = 0;
		dot = 3;
	} else if (Pos >= (maxcontentheight - half))
	{ /* we are at the bottom part */
		skip = maxcontentheight - contentheight;
		dot = contentheight - 1 + 3;
	} else {
		skip = Pos - half;
		dot = skip * (contentheight) / (maxcontentheight - (contentheight)) + 3;
	}

	mlWidth = MIN (78 + (API->console->TextWidth - 80) * 2 / 3, 120);
	mlTop = MAX(1,(API->console->TextHeight - mlHeight) / 2);
	mlLeft = (API->console->TextWidth - mlWidth) / 2;

	API->console->DisplayFrame (mlTop++, mlLeft++, mlHeight, mlWidth, DIALOG_COLOR_FRAME, "libsidplayfp configuration", dot, 2, mlHeight - 4);
	mlHeight -= 2;
	mlWidth  -= 2;
	API->console->DisplayPrintf (mlTop++, mlLeft, 0x0f, mlWidth, " Navigate with %.15o<\x18>%.7o,%.15o<\x19>%.7o,%.15o<\x1a>%.7o,%.15o<\x1b>%.7o and %.15o<ENTER>%.7o; hit %.15o<ESC>%.7o to save and exit.");

	mlTop++; // 2: horizontal bar

#undef _A
#undef _B
#define _A \
if (skip)                   \
{                           \
  skip --;                  \
} else if (contentheight) {

#define _B \
  contentheight--;          \
}

	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_EMULATOR,          "emulator", emulators, 2, config_emulator,                  EditPos==SEL_EMULATOR,          1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_DEFAULTC64MODEL,   "default C64", C64models, 5, config_defaultC64,             EditPos==SEL_DEFAULTC64MODEL,   1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_FORCEC64MODEL,     "force C64 model", offon, 2, config_forceC64,               EditPos==SEL_FORCEC64MODEL,     1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_DEFAULTSID,        "default SID", SIDmodels, 2, config_defaultSID,             EditPos==SEL_DEFAULTSID,        1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_FORCESID,          "force SID", offon, 2, config_forceSID,                     EditPos==SEL_FORCESID,          1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_CIAMODEL,          "CIA", CIAmodels, 3, config_CIA,                            EditPos==SEL_CIAMODEL,          1,                    API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_FILTER,            "filter", offon, 2, config_filter,                          EditPos==SEL_FILTER,            1,                    API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_FILTERCURVE6581,   "filtercurve6581", 100, "", 0, 100, config_filtercurve6581, EditPos==SEL_FILTERCURVE6581,   config_emulator == 0, API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_FILTERRANGE6581,   "filterrange6581", 100, "", 0, 100, config_filterrange6581, EditPos==SEL_FILTERRANGE6581,   config_emulator == 0, API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_WAVEOFFSET6581,    "waveoffset6581", 100, "", 0, 100, config_waveoffset6581,   EditPos==SEL_WAVEOFFSET6581,    config_emulator == 0, API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_ENABLEOLD6581CAPS, "Old 6581 Cap val", offon, 2, config_enableOld6581caps,     EditPos==SEL_ENABLEOLD6581CAPS, config_emulator == 0, API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_FILTERCURVE8580,   "filtercurve8580", 100, "", 0, 100, config_filtercurve8580, EditPos==SEL_FILTERCURVE8580,   config_emulator == 0, API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_CWS,               "CWS", combinedwaveforms, 3, config_combinedwaveforms,      EditPos==SEL_CWS,               config_emulator == 0, API); _B
	_A ConfigDrawMenuItems    (mlTop++, mlLeft, mlWidth, SEL_DIGIBOOST,         "digiboost", offon, 2, config_digiboost,                    EditPos==SEL_DIGIBOOST,         1,                    API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_DACLEAKAGELEVEL,   "DAC Leakage lvl", 100, "", 0, 100, config_dacLeakageLevel, EditPos==SEL_DACLEAKAGELEVEL,   config_emulator == 0, API); _B
	_A ConfigDrawMenuBar      (mlTop++, mlLeft, mlWidth, SEL_DCBLOCKRESISTANCE, "DC Block Res", 100, "", 0, 100, config_dcBlockResistor,    EditPos==SEL_DCBLOCKRESISTANCE, config_emulator == 0, API); _B
	_A ConfigDrawMenuRom      (mlTop++, mlLeft, mlWidth, SEL_ROM_KERNAL,        "kernal.rom:", EditPos==SEL_ROM_KERNAL, config_kernal, API);   _B
	_A ConfigDrawHashMenuInfo (mlTop++, mlLeft, mlWidth, entry_kernal.hash_8192, entry_kernal.hash_4096, 0, API);                              _B
	_A ConfigDrawMenuRom      (mlTop++, mlLeft, mlWidth, SEL_ROM_BASIC,         "basic.rom:", EditPos==SEL_ROM_BASIC, config_basic, API);      _B
	_A ConfigDrawHashMenuInfo (mlTop++, mlLeft, mlWidth, entry_basic.hash_8192, entry_basic.hash_4096, 1, API);                                _B
	_A ConfigDrawMenuRom      (mlTop++, mlLeft, mlWidth, SEL_ROM_CHARGEN,       "chargen.rom", EditPos==SEL_ROM_CHARGEN, config_chargen, API); _B
	_A ConfigDrawHashMenuInfo (mlTop++, mlLeft, mlWidth, entry_chargen.hash_8192, entry_chargen.hash_4096, 2, API);                            _B

#undef _A
#undef _B

	mlTop++; // mlHeight - 4: Horizontal bar

	switch (EditPos)
	{
		case SEL_EMULATOR:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " residfp = floating point emulator.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " crSID   = fixed point emulator (sidlite)");
			break;
		case SEL_DEFAULTC64MODEL:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " For SID files that does not specify C64 model.");
			mlTop++;
			break;
		case SEL_FORCEC64MODEL:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Override C64 model specified in SID files (if specified).");
			mlTop++;
			break;
		case SEL_DEFAULTSID:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " For SID files that does not specify SID model.");
			mlTop++;
			break;
		case SEL_FORCESID:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Override SID model specified in SID files (if specified).");
			mlTop++;
			break;
		case SEL_CIAMODEL:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Specify which CIA chip to emulate. MOS6526 is the classic model where");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " MOS6526W4485 is a specific batch. MOS8521 is the modern chip model.");
			break;
		case SEL_FILTER:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Enable post-processing filtering.");
			mlTop++;
			break;
		case SEL_FILTERCURVE6581:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Value to use if SID is MOS6581.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Default is 0.5");
			break;
		case SEL_FILTERRANGE6581:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". MOS6581 only: 0=\"bright\", 1=\"dark\".");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Default is 0.5%");
			break;
		case SEL_WAVEOFFSET6581:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Affects the volume of digi samples.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Range is between 0 (faint digis) and 1 (loud digis). Default is 1.0");
			break;
		case SEL_ENABLEOLD6581CAPS:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Enable old value of 2200pF capacitors instead of");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " 470pF, matching C64 Assembly 326298. Default is off.");
			break;
		case SEL_FILTERCURVE8580:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Value to use if SID is MOS8580.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Default is 0.5");
			break;
		case SEL_CWS:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Combined waveforms strength.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Default is average.");
			break;
		case SEL_DIGIBOOST:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Digi-Boost is a hardware feature only available on MOS8580, where the");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " digital playback would be boosted.");
			break;
		case SEL_DACLEAKAGELEVEL:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Affects the envelope and waveforms.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Range is between 0 (no leakage) and 1 (full leakage). Default is 1.0");
			break;
		case SEL_DCBLOCKRESISTANCE:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Only used by \"residfp\". Affects the highpass cutoff frequency.");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " Range is between 0 (1.6Hz) and 1 (16Hz). Default is 0.0");
			break;
		case SEL_ROM_KERNAL:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " KERNEL.ROM images can be found online. Some SID files requires this file");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " in order to play correctly.");
			break;
		case SEL_ROM_BASIC:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " BASIC.ROM images can be found online. Some SID files requires this file");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " in order to play correctly.");
			break;
		case SEL_ROM_CHARGEN:
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " CHARGEN.ROM images can be found online. Some SID files requires this file");
			API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, " in order to play correctly.");
			break;
		default: /* should not be reachable */
			mlTop++;
			mlTop++;
			break;
	}
}

static int emulator_to_int (const char *src)
{
	if (!strcasecmp (src, "residfp")) return 0;
	if (!strcasecmp (src, "crSID")) return 1;

	return 1;
}

static const char *emulator_from_int (const int src)
{
	switch (src)
	{
		default:
		case 0: return "residfp";
		case 1: return "crSID";
	}
}

static int defaultC64_to_int (const char *src)
{
	if (!strcasecmp (src, "PAL")) return 0;
	if (!strcasecmp (src, "NTSC")) return 1;
	if (!strcasecmp (src, "OLD-NTSC")) return 2;
	if (!strcasecmp (src, "DREAN")) return 3;
	if (!strcasecmp (src, "PAL-M")) return 4;
	return 0;
}

static const char *defaultC64_from_int (const int src)
{
	switch (src)
	{
		default:
		case 0: return "PAL";
		case 1: return "NTSC";
		case 2: return "OLD-NTSC";
		case 3: return "DREAN";
		case 4: return "PAL-M";
	}
}

static int defaultSID_to_int (const char *src)
{
	if (!strcasecmp (src, "MOS6581")) return 0;
	if (!strcasecmp (src, "MOS8580")) return 1;
	return 0;
}

static const char *defaultSID_from_int (const int src)
{
	switch (src)
	{
		default:
		case 0: return "MOS6581";
		case 1: return "MOS8580";
	}
}

static int CIA_to_int (const char *src)
{
	if (!strcasecmp (src, "MOS6526")) return 0;
	if (!strcasecmp (src, "MOS6526W4485")) return 1;
	if (!strcasecmp (src, "MOS8521")) return 2;
	return 0;
}

static const char *CIA_from_int (const int src)
{
	switch (src)
	{
		default:
		case 0: return "MOS6526";
		case 1: return "MOS6526W4485";
		case 2:	return "MOS8521";
	}
}

static int CWS_to_int (const char *src)
{
	if (!strcasecmp (src, "AVERAGE")) return 0;
	if (!strcasecmp (src, "WEAK")) return 1;
	if (!strcasecmp (src, "STRONG")) return 2;
	return 0;
}

static const char *CWS_from_int (const int src)
{
	switch (src)
	{
		default:
		case 0: return "Average";
		case 1: return "Weak";
		case 2: return "Strong";
	}
}

#if 0
static const char *int_to_float10x(int src)
{
	static char retval[32];
	int src_abs = (src >= 0) ? src : -src;
	snprintf (retval, sizeof (retval), "%d.%01d", src / 10, src_abs % 10);
	return retval;
}
#endif

static const char *int_to_float100x(int src)
{
	static char retval[32];
	int src_abs = (src >= 0) ? src : -src;
	snprintf (retval, sizeof (retval), "%d.%02d", src / 100, src_abs % 100);
	return retval;
}

#if 0
static int float10x_to_int(const char *src)
{
	int retval = atoi (src) * 10;
	char *r = strchr (src, '.');
	if (r)
	{
		if (r[1] >= '0' && (r[1] <= '9'))
		{
			retval += r[1] - '0';
		}
	}
	return retval;
}
#endif

static int float100x_to_int(const char *src)
{
	int retval = atoi (src) * 100;
	const char *r = strchr (src, '.');
	if (r)
	{
		if (r[1] >= '0' && (r[1] <= '9'))
		{
			retval += (r[1] - '0') * 10;
			if (r[2] >= '0' && (r[2] <= '9'))
			{
				retval += (r[2] - '0');
			}
		}
	}
	return retval;
}

static void rom_md5s (char md5_4096[33], char md5_8192[33], uint32_t dirdb_ref, const struct DevInterfaceAPI_t *API)
{
	struct ocpfile_t *file = 0;
	struct ocpfilehandle_t *h;
	uint8_t buffer[8192];
	int fill = 0;

	md5_4096[0] = '-';
	md5_4096[1] = 0;
	md5_4096[32] = 0;

	md5_8192[0] = '-';
	md5_8192[1] = 0;
	md5_8192[32] = 0;

	API->filesystem_resolve_dirdb_file (dirdb_ref, 0, &file);
	if (!file)
	{
		return;
	}
	h = file->open (file);
	if (h)
	{
		fill = h->read (h, buffer, 8192);
		h->unref (h);
		h = 0;
	}

	file->unref (file);
	file = 0;

	if (fill >= 4096)
	{
		MD5_CTX ctx;
		MD5Init (&ctx);
		MD5Update (&ctx, buffer, 4096);
		MD5Final (md5_4096, &ctx);
	}
	if (fill >= 8192)
	{
		MD5_CTX ctx;
		MD5Init (&ctx);
		MD5Update (&ctx, buffer, 8192);
		MD5Final (md5_8192, &ctx);
	}
}


static void sidDrawDir (const int esel, const int expect, const struct DevInterfaceAPI_t *API)
{
	int contentheight = entries_count;
	int i;
	int files = 0;
	int dirs = 0;
#ifdef _WIN32
	int drives = 0;
#endif

	int half;
	int skip;
	int dot;
	const char *title;

	const int LINES_NOT_AVAILABLE = 4;
	int mlHeight = MIN(API->console->TextHeight, 24);
	int mlTop = (API->console->TextHeight - mlHeight) / 2;
	int mlWidth = MIN (78 + (API->console->TextWidth - 80) * 2 / 3, 120);
	int mlLeft = (API->console->TextWidth - mlWidth) / 2;

#if (CONSOLE_MIN_Y < 7)
# error sidDrawDir() requires CONSOLE_MIN_Y >= 7
#endif

	switch (expect)
	{
		case 0:  title = "Select a KERNEL ROM";  break;
		case 1:  title = "Select a BASIC ROM";   break;
		default: title = "Select a CHARGEN ROM"; break;
	}

	for (i=0; i < entries_count; i++)
	{
		if (entries_data[i].isdir)
		{
			dirs++;
#ifdef _WIN32
		} else if (entries_data[i].isdrive)
		{
			drives++;
#endif
		} else {
			files++;
		}
	}
	contentheight = dirs + files;
#ifdef _WIN32
	contentheight += drives;
#endif

	half = (mlHeight - LINES_NOT_AVAILABLE) / 2;

	if (contentheight <= (mlHeight - LINES_NOT_AVAILABLE))
	{ /* all entries can fit */
		skip = 0;
		dot = 0;
	} else if (esel < half)
	{ /* we are in the top part */
		skip = 0;
		dot = 3;
	} else if (esel >= (contentheight - half))
	{ /* we are at the bottom part */
		skip = contentheight - (mlHeight - LINES_NOT_AVAILABLE);
		dot = mlHeight - LINES_NOT_AVAILABLE - 1 + 3;
	} else {
		skip = esel - half;
		dot = skip * (mlHeight - LINES_NOT_AVAILABLE) / (contentheight - (mlHeight - LINES_NOT_AVAILABLE)) + 3;
	}

	API->console->DisplayFrame (mlTop++, mlLeft++, mlHeight, mlWidth, DIALOG_COLOR_FRAME, title, dot, 2, 0);
	mlHeight -= 2;
	mlWidth  -= 2;

	API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, "%*S", mlWidth, entries_location ? entries_location : "");

	mlTop++; // 2: horizontal bar

	for (i = 0; i < (mlHeight - 2); i++)
	{
		int masterindex = skip + i;
		if (masterindex >= entries_count)
		{
			mlTop++;
		} else if (entries_data[masterindex].isdir)
		{
			if (entries_data[masterindex].isparent)
			{
				API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, "%*.*o..", (masterindex==esel)?0x6:0x0, (masterindex==esel)?0x1:0x1);
			} else {
				const char *dirname;
				API->dirdb->GetName_internalstr (entries_data[masterindex].dirdb_ref, &dirname);
				API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, "%*.*o%*S", (masterindex==esel)?0x6:0x0, (masterindex==esel)?0x1:0x1, mlWidth, dirname);
			}
#ifdef _WIN32
		} else if (entries_data[masterindex].isdrive)
		{
				API->console->DisplayPrintf (mlTop++, mlLeft, 0x07, mlWidth, "%*.*o%c:", (masterindex==esel)?0x6:0x0, (masterindex==esel)?0x1:0x1, entries_data[masterindex].drive);
#endif
		} else {
			const char *filename;
			API->dirdb->GetName_internalstr (entries_data[masterindex].dirdb_ref, &filename);
			API->console->DisplayPrintf (mlTop,   mlLeft,                0x07, mlWidth - 42, "%*.*o%*S%0.9o=", (masterindex==esel)?0x6:0x0, (masterindex==esel)?0x0:0x7, mlWidth - 43, filename);
			ConfigDrawHashInfo          (mlTop++, mlLeft + mlWidth - 42,                 42, entries_data[masterindex].hash_8192, entries_data[masterindex].hash_4096, expect, API);
		}
	}
}

static void entries_clear (const struct DevInterfaceAPI_t *API)
{
	int i;
	free (entries_location);
	for (i=0; i < entries_count; i++)
	{
		API->dirdb->Unref (entries_data[i].dirdb_ref, dirdb_use_file);
	}
	free (entries_data);
	entries_location = 0;
	entries_count = 0;
	entries_size = 0;
	entries_data = 0;
}

static const struct DevInterfaceAPI_t *cmp_API;
static int cmp(const void *a, const void *b)
{
	struct browser_t *p1 = (struct browser_t *)a;
	struct browser_t *p2 = (struct browser_t *)b;

	const char *n1;
	const char *n2;

	if (p1->isparent)
	{
		return -1;
	}
	if (p2->isparent)
	{
		return 1;
	}
	if (p1->isdir && (!p2->isdir))
	{
		return -1;
	}
	if (p2->isdir && (!p1->isdir))
	{
		return 1;
	}
#ifdef _WIN32
	if ((p1->isdrive) && (!p2->isdrive))
	{
		return -1;
	}
	if (p2->isdrive && (!p1->isdrive))
	{
		return 1;
	}
	if (p1->isdrive)
	{
		return p1->drive - p2->drive;
	}
#endif

	cmp_API->dirdb->GetName_internalstr (p1->dirdb_ref, &n1);
	cmp_API->dirdb->GetName_internalstr (p2->dirdb_ref, &n2);
	return strcmp (n1, n2);
}

static void entries_sort (const struct DevInterfaceAPI_t *API)
{
	cmp_API = API;
	qsort (entries_data, entries_count, sizeof (entries_data[0]), cmp);
	cmp_API = 0;
}

static int entries_append (void)
{
	void *temp;
	if (entries_count < entries_size)
	{
		return 0;
	}
	temp = realloc (entries_data, sizeof (entries_data[0]) * (entries_size + 16));
	if (!temp)
	{
		return -1;
	}
	entries_data = temp;
	entries_size += 16;
	return 0;
}

static void entries_append_parent (uint32_t ref, const struct DevInterfaceAPI_t *API)
{
	if (entries_append())
	{
		return;
	}
	entries_data[entries_count].isdir = 1;
	entries_data[entries_count].isparent = 1;
#ifdef _WIN32
	entries_data[entries_count].isdrive = 0;
#endif
	API->dirdb->Ref (ref, dirdb_use_file);
	entries_data[entries_count].dirdb_ref = ref;
	entries_data[entries_count].hash_4096[0] = 0;
	entries_data[entries_count].hash_8192[0] = 0;
	entries_count++;
}

static void entries_append_dir (uint32_t ref, const struct DevInterfaceAPI_t *API)
{
	if (entries_append())
	{
		return;
	}
	entries_data[entries_count].isdir = 1;
	entries_data[entries_count].isparent = 0;
#ifdef _WIN32
	entries_data[entries_count].isdrive = 0;
#endif
	API->dirdb->Ref (ref, dirdb_use_file);
	entries_data[entries_count].dirdb_ref = ref;
	entries_data[entries_count].hash_4096[0] = 0;
	entries_data[entries_count].hash_8192[0] = 0;
	entries_count++;
}

static void entries_append_file (uint32_t ref, const char *hash_4096, const char *hash_8192, const struct DevInterfaceAPI_t *API)
{
	if (entries_append())
	{
		return;
	}
	entries_data[entries_count].isdir = 0;
	entries_data[entries_count].isparent = 0;
#ifdef _WIN32
	entries_data[entries_count].isdrive = 0;
#endif
	API->dirdb->Ref (ref, dirdb_use_file);
	entries_data[entries_count].dirdb_ref = ref;
	strcpy (entries_data[entries_count].hash_4096, hash_4096);
	strcpy (entries_data[entries_count].hash_8192, hash_8192);
	entries_count++;
}

#ifdef _WIN32
static void entries_append_drive (uint32_t ref, const struct DevInterfaceAPI_t *API, char drive)
{
	if (ref == DIRDB_CLEAR)
	{
		return;
	}
	if (entries_append())
	{
		return;
	}
	entries_data[entries_count].isdir = 0;
	entries_data[entries_count].isparent = 0;
	entries_data[entries_count].isdrive = 1;
	entries_data[entries_count].drive = drive;
	API->dirdb->Ref(ref, dirdb_use_file);
	entries_data[entries_count].dirdb_ref = ref;
	entries_data[entries_count].hash_4096[0] = 0;
	entries_data[entries_count].hash_8192[0] = 0;
	entries_count++;
}
#endif

static void refresh_dir_add_file (void *token, struct ocpfile_t *file)
{
	const struct DevInterfaceAPI_t *API = token;
	const char *filename = 0;
	size_t len;
	API->dirdb->GetName_internalstr (file->dirdb_ref, &filename);
	if (!filename)
	{
		return;
	}
	len = strlen (filename);
	if (len <= 4)
	{
		return;
	}
	if ((!strcasecmp (filename + len - 4, ".rom")) ||
	    (!strcasecmp (filename + len - 4, ".bin")))
	{
		char md5_4096[33];
		char md5_8192[33];
		rom_md5s (md5_4096, md5_8192, file->dirdb_ref, API);
		entries_append_file (file->dirdb_ref, md5_4096, md5_8192, API);
	}
}

static void refresh_dir_add_dir (void *token, struct ocpdir_t *dir)
{
	const struct DevInterfaceAPI_t *API = token;
	const char *dirname = 0;
	API->dirdb->GetName_internalstr (dir->dirdb_ref, &dirname);
	if (!dirname)
	{
		return;
	}
	if ((strcmp (dirname, ".")) &&
	    (strcmp (dirname, "..")))
	{
		entries_append_dir (dir->dirdb_ref, API);
	}
}

static void refresh_dir (uint32_t ref, uint32_t old, int *esel, const struct DevInterfaceAPI_t *API)
{
	struct ocpdir_t *dir = 0;
	//DIR *d;
	int i;

	*esel = 0;

	entries_clear (API);

#ifdef _WIN32
	API->dirdb->GetFullname_malloc (ref, &entries_location, DIRDB_FULLNAME_DRIVE | DIRDB_FULLNAME_BACKSLASH);
#else
	API->dirdb->GetFullname_malloc (ref, &entries_location, DIRDB_FULLNAME_NODRIVE);
#endif

	API->filesystem_resolve_dirdb_dir (ref, 0, &dir);
	if (dir)
	{
		ocpdirhandle_pt iter;
		if (dir->parent)
		{
			entries_append_parent (dir->parent->dirdb_ref, API);
		}
		iter = dir->readdir_start (dir, refresh_dir_add_file, refresh_dir_add_dir, (void *)API);
		while (dir->readdir_iterate (iter))
		{
		}
		dir->readdir_cancel (iter);
		dir->unref (dir);
		dir = 0;
	} else {
		uint32_t dir_ref = API->dirdb->GetParentAndRef (ref, dirdb_use_file);
		if (dir_ref != UINT32_MAX)
		{
			entries_append_parent (dir_ref, API);
		}
		 API->dirdb->Unref (dir_ref, dirdb_use_file);
	}

#ifdef _WIN32
	for (i=0; i < 26; i++)
	{
		if (API->dmDriveLetters[i])
		{
			entries_append_drive (API->dmDriveLetters[i]->cwd->dirdb_ref, API, i + 'A');
		}
	}
#endif
	entries_sort (API);
	for (i=0; i < entries_count; i++)
	{
		if (entries_data[i].dirdb_ref == old)
		{
			*esel = i;
			return;
		}
	}
}

static void sidConfigRun (void **token, const struct DevInterfaceAPI_t *API)
{
	int esel = 0;
	uint32_t dirdb_base = API->configAPI->DataHomeDir->dirdb_ref;

	config_emulator = emulator_to_int         (API->configAPI->GetProfileString ("libsidplayfp", "emulator",        "residfp"));
	config_defaultC64 = defaultC64_to_int     (API->configAPI->GetProfileString ("libsidplayfp", "defaultC64",      "PAL"));
	config_forceC64 =                          API->configAPI->GetProfileBool   ("libsidplayfp", "forceC64",        0, 0);
	config_defaultSID = defaultSID_to_int     (API->configAPI->GetProfileString ("libsidplayfp", "defaultSID",      "MOS6581"));
	config_forceSID =                          API->configAPI->GetProfileBool   ("libsidplayfp", "forceSID",        0, 0);
	config_CIA = CIA_to_int                   (API->configAPI->GetProfileString ("libsidplayfp", "CIA",             "MOS6526"));
	config_filter =                            API->configAPI->GetProfileBool   ("libsidplayfp", "filter",          1, 1);
	config_filtercurve6581 = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "filtercurve6581", "0.5"));
	config_filterrange6581 = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "filterrange6581", "0.5"));
	config_waveoffset6581  = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "waveoffset6581",  "1.0"));
	config_enableOld6581caps =                 API->configAPI->GetProfileBool   ("libsidplayfp", "enableOld6581caps", 0, 0);
	config_filtercurve8580 = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "filtercurve8580", "0.5"));
	config_combinedwaveforms = CWS_to_int     (API->configAPI->GetProfileString ("libsidplayfp", "combinedwaveforms", "Strong"));
	config_digiboost =                         API->configAPI->GetProfileBool   ("libsidplayfp", "digiboost",       0, 0);
	config_dacLeakageLevel = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "dacLeakageLevel", "1.0"));
	config_dcBlockResistor = float100x_to_int (API->configAPI->GetProfileString ("libsidplayfp", "dcBlockResistor", "0.0"));
	config_kernal = strdup                    (API->configAPI->GetProfileString ("libsidplayfp", "kernal",          "KERNEL.ROM"));
	config_basic = strdup                     (API->configAPI->GetProfileString ("libsidplayfp", "basic",           "BASIC.ROM"));
	config_chargen = strdup                   (API->configAPI->GetProfileString ("libsidplayfp", "chargen",         "CHARGEN.ROM"));

	if (config_filtercurve6581 <   0) config_filtercurve6581 =   0;
	if (config_filtercurve6581 > 100) config_filtercurve6581 = 100;
	if (config_filterrange6581 <   0) config_filterrange6581 =   0;
	if (config_filterrange6581 > 100) config_filterrange6581 = 100;
	if (config_waveoffset6581  <   0) config_waveoffset6581  =   0;
	if (config_waveoffset6581  > 100) config_waveoffset6581  = 100;
	if (config_filtercurve8580 <   0) config_filtercurve8580 =   0;
	if (config_filtercurve8580 > 100) config_filtercurve8580 = 100;
	if (config_dacLeakageLevel <   0) config_dacLeakageLevel =   0;
	if (config_dacLeakageLevel > 100) config_dacLeakageLevel = 100;
	if (config_dcBlockResistor <   0) config_dcBlockResistor =   0;
	if (config_dcBlockResistor > 100) config_dcBlockResistor = 100;

#ifdef _WIN32
	entry_kernal.dirdb_ref  = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_kernal,  DIRDB_RESOLVE_DRIVE | DIRDB_RESOLVE_TILDE_HOME | DIRDB_RESOLVE_WINDOWS_SLASH, dirdb_use_file);
	entry_basic.dirdb_ref   = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_basic,   DIRDB_RESOLVE_DRIVE | DIRDB_RESOLVE_TILDE_HOME | DIRDB_RESOLVE_WINDOWS_SLASH, dirdb_use_file);
	entry_chargen.dirdb_ref = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_chargen, DIRDB_RESOLVE_DRIVE | DIRDB_RESOLVE_TILDE_HOME | DIRDB_RESOLVE_WINDOWS_SLASH, dirdb_use_file);
#else
	entry_kernal.dirdb_ref  = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_kernal,  DIRDB_RESOLVE_TILDE_HOME, dirdb_use_file);
	entry_basic.dirdb_ref   = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_basic,   DIRDB_RESOLVE_TILDE_HOME, dirdb_use_file);
	entry_chargen.dirdb_ref = API->dirdb->ResolvePathWithBaseAndRef (dirdb_base, config_chargen, DIRDB_RESOLVE_TILDE_HOME, dirdb_use_file);
#endif

	rom_md5s (entry_kernal .hash_4096, entry_kernal .hash_8192, entry_kernal .dirdb_ref, API);
	rom_md5s (entry_basic  .hash_4096, entry_basic  .hash_8192, entry_basic  .dirdb_ref, API);
	rom_md5s (entry_chargen.hash_4096, entry_chargen.hash_8192, entry_chargen.dirdb_ref, API);

	while (1)
	{
		API->fsDraw();
		sidConfigDraw (esel, API);
		while (API->console->KeyboardHit())
		{
			int key = API->console->KeyboardGetChar();
			static uint32_t lastpress = 0;
			static uint16_t lastkey = 0;
			static int repeat;
			if ((key != KEY_LEFT) && (key != KEY_RIGHT) && (key != lastkey))
			{
				lastkey = key;
				lastpress = 0;
				repeat = 1;
			} else {
				uint32_t newpress = clock_ms();
				if ((newpress-lastpress) > 150) /* ms */
				{
					repeat = 1;
				} else {
					if (repeat < 4)
					{
						repeat += 1;
					}
				}
				lastpress = newpress;
			}

			switch (key)
			{
				case _KEY_ENTER:
					if ((esel == SEL_ROM_KERNAL) || (esel == SEL_ROM_BASIC) || (esel == SEL_ROM_CHARGEN))
					{
						uint32_t dir_ref;
						int dsel = 0;
						int inner = 1;

						if (esel == SEL_ROM_KERNAL)
						{
							dir_ref = API->dirdb->GetParentAndRef (entry_kernal.dirdb_ref, dirdb_use_dir);
							refresh_dir (dir_ref, entry_kernal.dirdb_ref, &dsel, API);
						} else if (esel == SEL_ROM_BASIC)
						{
							dir_ref = API->dirdb->GetParentAndRef (entry_basic.dirdb_ref, dirdb_use_dir);
							refresh_dir (dir_ref, entry_basic.dirdb_ref, &dsel, API);
						} else { /* SEL_ROM_CHARGEN */
							dir_ref = API->dirdb->GetParentAndRef (entry_chargen.dirdb_ref, dirdb_use_dir);
							refresh_dir (dir_ref, entry_chargen.dirdb_ref, &dsel, API);
						}

#if 0
						{ /* preselect dsel */
							const char *configfile = API->configAPI->GetProfileString ("timidity", "configfile", "");
							int i;
							if (configfile[0])
							{
								for (i=0; i < global_timidity_count; i++)
								{
									if (!strcmp (configfile, global_timidity_path[i]))
									{
										dsel = i + 1;
										break;
									}
								}
								if (!dsel)
								{
									for (i=0; i < sf2_files_count; i++)
									{
										if (!strcmp (configfile, sf2_files_path[i]))
										{
											dsel = i + 1 + global_timidity_count;
										}
									}
								}
							}
						}
#endif
						while (inner)
						{
							API->fsDraw();
							sidDrawDir (dsel, esel - 13, API);
							while (inner && API->console->KeyboardHit())
							{
								int key = API->console->KeyboardGetChar();
								switch (key)
								{
									case KEY_DOWN:
										if ((dsel + 1) < entries_count)
										{
											dsel++;
										}
										break;
									case KEY_UP:
										if (dsel)
										{
											dsel--;
										}
										break;
									case KEY_EXIT:
									case KEY_ESC:
										inner = 0;
										break;

									case _KEY_ENTER:
										if (
											entries_data[dsel].isdir
#ifdef _WIN32
											|| entries_data[dsel].isdrive
#endif
										)
										{
											uint32_t dir = entries_data[dsel].dirdb_ref;
											API->dirdb->Ref (dir, dirdb_use_dir);
											refresh_dir (dir, dir_ref, &dsel, API);
											API->dirdb->Unref (dir_ref, dirdb_use_dir);
											dir_ref = dir;
										} else {
											char *newpath = 0;
											char *config = 0;
											char *path = 0;
#ifdef _WIN32
											API->dirdb->GetFullname_malloc (entries_data[dsel].dirdb_ref, &path, DIRDB_FULLNAME_DRIVE | DIRDB_FULLNAME_BACKSLASH);
											API->dirdb->GetFullname_malloc (dirdb_base, &config, DIRDB_FULLNAME_DRIVE | DIRDB_FULLNAME_ENDSLASH | DIRDB_FULLNAME_BACKSLASH);
#else
											API->dirdb->GetFullname_malloc (entries_data[dsel].dirdb_ref, &path, DIRDB_FULLNAME_NODRIVE);
											API->dirdb->GetFullname_malloc (dirdb_base, &config, DIRDB_FULLNAME_NODRIVE | DIRDB_FULLNAME_ENDSLASH);
#endif
											if (!strncmp (path, config, strlen (config)))
											{
												newpath = malloc (1 + strlen (path) - strlen (config));
												if (newpath)
												{
													sprintf (newpath, "%s", path + strlen (config));
												}
#ifdef _WIN32
											} else if (!strncasecmp (path, API->configAPI->HomePath, strlen (API->configAPI->HomePath)))
#else
											} else if (!strncmp (path, API->configAPI->HomePath, strlen (API->configAPI->HomePath)))
#endif
											{
												newpath = malloc (3 + strlen (path) - strlen (API->configAPI->HomePath));
												if (newpath)
												{
#ifdef _WIN32
													sprintf (newpath, "~\%s", path + strlen (API->configAPI->HomePath));
#else
													sprintf (newpath, "~/%s", path + strlen (API->configAPI->HomePath));
#endif
												}
											} else {
												newpath = path;
												path = 0;
											}
											if (newpath)
											{
												if (esel == SEL_ROM_KERNAL)
												{
													API->dirdb->Unref (entry_kernal.dirdb_ref, dirdb_use_file);
													entry_kernal = entries_data[dsel];
													API->dirdb->Ref (entry_kernal.dirdb_ref, dirdb_use_file);
													free (config_kernal);
													config_kernal = newpath;
												} else if (esel == SEL_ROM_BASIC)
												{
													API->dirdb->Unref (entry_basic.dirdb_ref, dirdb_use_file);
													entry_basic = entries_data[dsel];
													API->dirdb->Ref (entry_basic.dirdb_ref, dirdb_use_file);
													free (config_basic);
													config_basic = newpath;
												} else { /* esel == SEL_ROM_CHARGEN */
													API->dirdb->Unref (entry_chargen.dirdb_ref, dirdb_use_file);
													entry_chargen = entries_data[dsel];
													API->dirdb->Ref (entry_chargen.dirdb_ref, dirdb_use_file);
													free (config_chargen);
													config_chargen = newpath;
												}
											}
											free (path);
											free (config);
											inner = 0;
										}
										break;

								}
							}
							API->console->FrameLock ();
						}
						entries_clear (API);
						API->dirdb->Unref (dir_ref, dirdb_use_dir);
					}
					break;
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					esel = key - '1';
					break;
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
					esel = key - 'a' + 9;
					break;
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
				case 'G':
				case 'H':
				case 'I':
				case 'J':
					esel = key - 'A' + 9;
					break;

				case KEY_LEFT:
					switch (esel)
					{
						case SEL_EMULATOR:        config_emulator = 0; break;
						case SEL_DEFAULTC64MODEL: config_defaultC64 -= (!!config_defaultC64); break;
						case SEL_FORCEC64MODEL:   config_forceC64 = 0; break;
						case SEL_DEFAULTSID:      config_defaultSID -= (!!config_defaultSID); break;
						case SEL_FORCESID:        config_forceSID = 0; break;
						case SEL_CIAMODEL:        config_CIA -= (!!config_CIA); break;
						case SEL_FILTER:          config_filter = 0; break;
						case SEL_FILTERCURVE6581:
							config_filtercurve6581 -= repeat;
							if (config_filtercurve6581 < 0) config_filtercurve6581 = 0;
							break;
						case SEL_FILTERRANGE6581:
							config_filterrange6581 -= repeat;
							if (config_filterrange6581 < 0) config_filterrange6581 = 0;
							break;
						case SEL_WAVEOFFSET6581:
							config_waveoffset6581 -= repeat;
							if (config_waveoffset6581 < 0) config_waveoffset6581 = 0;
							break;
						case SEL_ENABLEOLD6581CAPS:
							config_enableOld6581caps = 0;
							break;
						case SEL_FILTERCURVE8580:
							config_filtercurve8580 -= repeat;
							if (config_filtercurve8580 < 0) config_filtercurve8580 = 0;
							break;
						case SEL_CWS:
							config_combinedwaveforms -= 1;
							if (config_combinedwaveforms < 0) config_combinedwaveforms = 0;
							break;
						case SEL_DIGIBOOST:       config_digiboost = 0; break;
						case SEL_DACLEAKAGELEVEL:
							config_dacLeakageLevel -= repeat;
							if (config_dacLeakageLevel < 0) config_dacLeakageLevel = 0;
							break;
						case SEL_DCBLOCKRESISTANCE:
							config_dcBlockResistor -= repeat;
							if (config_dcBlockResistor < 0) config_dcBlockResistor = 0;
							break;
						case SEL_ROM_KERNAL:
						case SEL_ROM_BASIC:
						case SEL_ROM_CHARGEN:
							break;
					}
					break;
				case KEY_RIGHT:
					switch (esel)
					{
						case SEL_EMULATOR:        config_emulator = 1; break;
						case SEL_DEFAULTC64MODEL: config_defaultC64 += (config_defaultC64 != 4); break;
						case SEL_FORCEC64MODEL:   config_forceC64 = 1; break;
						case SEL_DEFAULTSID:      config_defaultSID = 1; break;
						case SEL_FORCESID:        config_forceSID = 1; break;
						case SEL_CIAMODEL:        config_CIA += (config_CIA != 2); break;
						case SEL_FILTER:          config_filter = 1; break;
						case SEL_FILTERCURVE6581:
							config_filtercurve6581 += repeat;
							if (config_filtercurve6581 > 100) config_filtercurve6581 = 100;
							break;
						case SEL_FILTERRANGE6581:
							config_filterrange6581 += repeat;
							if (config_filterrange6581 > 100) config_filterrange6581 = 100;
							break;
						case SEL_WAVEOFFSET6581:
							config_waveoffset6581 += repeat;
							if (config_waveoffset6581 > 100) config_waveoffset6581 = 100;
							break;
						case SEL_ENABLEOLD6581CAPS:
							config_enableOld6581caps = 1;
							break;
						case SEL_FILTERCURVE8580:
							config_filtercurve8580 += repeat;
							if (config_filtercurve8580 > 100) config_filtercurve8580 = 100;
							break;
						case SEL_CWS:
							config_combinedwaveforms += 1;
							if (config_combinedwaveforms > 2) config_combinedwaveforms = 2;
							break;
						case SEL_DIGIBOOST:       config_digiboost = 1; break;
						case SEL_DACLEAKAGELEVEL:
							config_dacLeakageLevel += repeat;
							if (config_dacLeakageLevel > 100) config_dacLeakageLevel = 100;
							break;
						case SEL_DCBLOCKRESISTANCE:
							config_dcBlockResistor += repeat;
							if (config_dcBlockResistor > 100) config_dcBlockResistor = 100;
							break;
						case SEL_ROM_KERNAL:
						case SEL_ROM_BASIC:
						case SEL_ROM_CHARGEN:
							break;
					}
					break;
				case KEY_DOWN:
					if (esel < 18)
					{
						esel++;
					}
					break;
				case KEY_UP:
					if (esel)
					{
						esel--;
					}
					break;
				case KEY_EXIT:
				case KEY_ESC:
					API->configAPI->StoreConfig();
					goto superexit;
					break;
			}
		}
		API->console->FrameLock ();
	}

superexit:

	API->configAPI->SetProfileString ("libsidplayfp", "emulator", emulator_from_int (config_emulator));
	API->configAPI->SetProfileString ("libsidplayfp", "defaultC64", defaultC64_from_int (config_defaultC64));
	API->configAPI->SetProfileBool   ("libsidplayfp", "forceC64", config_forceC64);
	API->configAPI->SetProfileString ("libsidplayfp", "defaultSID", defaultSID_from_int (config_defaultSID));
	API->configAPI->SetProfileBool   ("libsidplayfp", "forceSID", config_forceSID);
	API->configAPI->SetProfileString ("libsidplayfp", "CIA", CIA_from_int (config_CIA));
	API->configAPI->SetProfileBool   ("libsidplayfp", "filter", config_filter);
	API->configAPI->SetProfileString ("libsidplayfp", "filtercurve6581", int_to_float100x(config_filtercurve6581));
	API->configAPI->SetProfileString ("libsidplayfp", "filterrange6581", int_to_float100x(config_filterrange6581));
	API->configAPI->SetProfileString ("libsidplayfp", "waveoffset6581", int_to_float100x(config_waveoffset6581));
	API->configAPI->SetProfileBool   ("libsidplayfp", "enableOld6581caps", config_enableOld6581caps);
	API->configAPI->SetProfileString ("libsidplayfp", "filtercurve8580", int_to_float100x(config_filtercurve8580));
	API->configAPI->SetProfileString ("libsidplayfp", "combinedwaveforms", CWS_from_int (config_combinedwaveforms));
	API->configAPI->SetProfileBool   ("libsidplayfp", "digiboost", config_digiboost);
	API->configAPI->SetProfileString ("libsidplayfp", "dacLeakageLevel", int_to_float100x(config_dacLeakageLevel));
	API->configAPI->SetProfileString ("libsidplayfp", "dcBlockResistor", int_to_float100x(config_dcBlockResistor));
	API->configAPI->SetProfileString ("libsidplayfp", "kernal",    config_kernal); free (config_kernal);
	API->configAPI->SetProfileString ("libsidplayfp", "basic",     config_basic); free (config_basic);
	API->configAPI->SetProfileString ("libsidplayfp", "chargen",   config_chargen); free (config_chargen);
	API->configAPI->StoreConfig ();

	API->dirdb->Unref (entry_kernal.dirdb_ref, dirdb_use_file);
	API->dirdb->Unref (entry_basic.dirdb_ref, dirdb_use_file);
	API->dirdb->Unref (entry_chargen.dirdb_ref, dirdb_use_file);
}

static struct ocpfile_t *sidconfig; // needs to overlay an dialog above filebrowser, and after that the file is "finished"   Special case of DEVv

static void sidConfigRun (void **token, const struct DevInterfaceAPI_t *API);

OCP_INTERNAL int sid_config_init (struct PluginInitAPI_t *API)
{
	sidconfig = API->dev_file_create (
		API->dmSetup->basedir,
		"sidconfig.dev",
		"libsidplayfp Configuration",
		"",
		0, /* token */
		0, /* Init */
		sidConfigRun,
		0, /* Close */
		0  /* Destructor */
	);

	API->filesystem_setup_register_file (sidconfig);

	return errOk;
}

OCP_INTERNAL void sid_config_done (struct PluginCloseAPI_t *API)
{
	if (sidconfig)
	{
		API->filesystem_setup_unregister_file (sidconfig);
		sidconfig->unref (sidconfig);
		sidconfig = 0;
	}
}
