/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */

#ifndef MPT_STRING_TRANSCODE_TRANSCODE_HPP
#define MPT_STRING_TRANSCODE_TRANSCODE_HPP



#include "mpt/base/detect.hpp"
#include "mpt/base/integer.hpp"
#include "mpt/base/macros.hpp"
#include "mpt/base/namespace.hpp"
#include "mpt/base/saturate_cast.hpp"
#include "mpt/detect/mfc.hpp"
#include "mpt/string/types.hpp"
#include "mpt/string/utility.hpp"

#include <array>
#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
#include <locale>
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
#include <optional>
#include <stdexcept>
#include <string>
#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
#include <type_traits>
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
#include <utility>
#include <vector>

#include <cstddef>

#if MPT_OS_DJGPP
#include <cstring>
#endif // MPT_OS_DJGPP

#if MPT_OS_WINDOWS
#include <windows.h>
#endif // MPT_OS_WINDOWS

#if MPT_OS_DJGPP
#include <dpmi.h>
#endif // MPT_OS_DJGPP



namespace mpt {
inline namespace MPT_INLINE_NS {





inline constexpr char32_t UCS4ud = 0xffffffffu;

// default 1:1 mapping
inline constexpr char32_t CharsetTableISO8859_1[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
	0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};

inline constexpr char32_t CharsetTableISO8859_15[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
	0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20ac, 0x00a5, 0x0160, 0x00a7, 0x0161, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x017d, 0x00b5, 0x00b6, 0x00b7, 0x017e, 0x00b9, 0x00ba, 0x00bb, 0x0152, 0x0153, 0x0178, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};

inline constexpr char32_t CharsetTableWindows1252[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
	0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};

// cat CP437.TXT | grep -v '^#' | head -n 256 | awk '{print $2;}' | ./tablerize.py > cp437.h
/*
#!/usr/bin/env python3

import fileinput

i = 0
for line in fileinput.input():
		if i % 16 == 0:
				print("\t", end='')
		print(line.strip(), end='')
		i = i + 1
		if i % 256 != 0:
				print(",", end='')
		if i % 16 != 0:
				print(" ", end='')
		if i % 16 == 0:
				if i % 256 == 0:
						print("};\n", end='')
				else:
						print("\n", end='')
*/

inline constexpr char32_t CharsetTableCP437[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP737[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0,
	0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8,
	0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03c9, 0x03ac, 0x03ad, 0x03ae, 0x03ca, 0x03af, 0x03cc, 0x03cd, 0x03cb, 0x03ce, 0x0386, 0x0388, 0x0389, 0x038a, 0x038c, 0x038e,
	0x038f, 0x00b1, 0x2265, 0x2264, 0x03aa, 0x03ab, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP775[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0106, 0x00fc, 0x00e9, 0x0101, 0x00e4, 0x0123, 0x00e5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012b, 0x0179, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x014d, 0x00f6, 0x0122, 0x00a2, 0x015a, 0x015b, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x00a4,
	0x0100, 0x012a, 0x00f3, 0x017b, 0x017c, 0x017a, 0x201d, 0x00a6, 0x00a9, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x0141, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010c, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255d, 0x012e, 0x0160, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x0172, 0x016a, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x017d,
	0x0105, 0x010d, 0x0119, 0x0117, 0x012f, 0x0161, 0x0173, 0x016b, 0x017e, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x00d3, 0x00df, 0x014c, 0x0143, 0x00f5, 0x00d5, 0x00b5, 0x0144, 0x0136, 0x0137, 0x013b, 0x013c, 0x0146, 0x0112, 0x0145, 0x2019,
	0x00ad, 0x00b1, 0x201c, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x201e, 0x00b0, 0x2219, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP850[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4,
	0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580,
	0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4,
	0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP852[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x016f, 0x0107, 0x00e7, 0x0142, 0x00eb, 0x0150, 0x0151, 0x00ee, 0x0179, 0x00c4, 0x0106,
	0x00c9, 0x0139, 0x013a, 0x00f4, 0x00f6, 0x013d, 0x013e, 0x015a, 0x015b, 0x00d6, 0x00dc, 0x0164, 0x0165, 0x0141, 0x00d7, 0x010d,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x0104, 0x0105, 0x017d, 0x017e, 0x0118, 0x0119, 0x00ac, 0x017a, 0x010c, 0x015f, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x011a, 0x015e, 0x2563, 0x2551, 0x2557, 0x255d, 0x017b, 0x017c, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x0102, 0x0103, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4,
	0x0111, 0x0110, 0x010e, 0x00cb, 0x010f, 0x0147, 0x00cd, 0x00ce, 0x011b, 0x2518, 0x250c, 0x2588, 0x2584, 0x0162, 0x016e, 0x2580,
	0x00d3, 0x00df, 0x00d4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00da, 0x0155, 0x0170, 0x00fd, 0x00dd, 0x0163, 0x00b4,
	0x00ad, 0x02dd, 0x02db, 0x02c7, 0x02d8, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x02d9, 0x0171, 0x0158, 0x0159, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP855[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408,
	0x0459, 0x0409, 0x045a, 0x040a, 0x045b, 0x040b, 0x045c, 0x040c, 0x045e, 0x040e, 0x045f, 0x040f, 0x044e, 0x042e, 0x044a, 0x042a,
	0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255d, 0x0439, 0x0419, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x043a, 0x041a, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4,
	0x043b, 0x041b, 0x043c, 0x041c, 0x043d, 0x041d, 0x043e, 0x041e, 0x043f, 0x2518, 0x250c, 0x2588, 0x2584, 0x041f, 0x044f, 0x2580,
	0x042f, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044c, 0x042c, 0x2116,
	0x00ad, 0x044b, 0x042b, 0x0437, 0x0417, 0x0448, 0x0428, 0x044d, 0x042d, 0x0449, 0x0429, 0x0447, 0x0427, 0x00a7, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP857[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x0131, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x0130, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x015e, 0x015f,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x011e, 0x011f, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4,
	0x00ba, 0x00aa, 0x00ca, 0x00cb, 0x00c8, UCS4ud, 0x00cd, 0x00ce, 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580,
	0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, UCS4ud, 0x00d7, 0x00da, 0x00db, 0x00d9, 0x00ec, 0x00ff, 0x00af, 0x00b4,
	0x00ad, 0x00b1, UCS4ud, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP860[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e3, 0x00e0, 0x00c1, 0x00e7, 0x00ea, 0x00ca, 0x00e8, 0x00cd, 0x00d4, 0x00ec, 0x00c3, 0x00c2,
	0x00c9, 0x00c0, 0x00c8, 0x00f4, 0x00f5, 0x00f2, 0x00da, 0x00f9, 0x00cc, 0x00d5, 0x00dc, 0x00a2, 0x00a3, 0x00d9, 0x20a7, 0x00d3,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00d2, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP861[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00d0, 0x00f0, 0x00de, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00fe, 0x00fb, 0x00dd, 0x00fd, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00c1, 0x00cd, 0x00d3, 0x00da, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP862[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df,
	0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, 0x05e8, 0x05e9, 0x05ea, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP863[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00c2, 0x00e0, 0x00b6, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x2017, 0x00c0, 0x00a7,
	0x00c9, 0x00c8, 0x00ca, 0x00f4, 0x00cb, 0x00cf, 0x00fb, 0x00f9, 0x00a4, 0x00d4, 0x00dc, 0x00a2, 0x00a3, 0x00d9, 0x00db, 0x0192,
	0x00a6, 0x00b4, 0x00f3, 0x00fa, 0x00a8, 0x00b8, 0x00b3, 0x00af, 0x00ce, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00be, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP864[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x066a, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00b0, 0x00b7, 0x2219, 0x221a, 0x2592, 0x2500, 0x2502, 0x253c, 0x2524, 0x252c, 0x251c, 0x2534, 0x2510, 0x250c, 0x2514, 0x2518,
	0x03b2, 0x221e, 0x03c6, 0x00b1, 0x00bd, 0x00bc, 0x2248, 0x00ab, 0x00bb, 0xfef7, 0xfef8, UCS4ud, UCS4ud, 0xfefb, 0xfefc, UCS4ud,
	0x00a0, 0x00ad, 0xfe82, 0x00a3, 0x00a4, 0xfe84, UCS4ud, UCS4ud, 0xfe8e, 0xfe8f, 0xfe95, 0xfe99, 0x060c, 0xfe9d, 0xfea1, 0xfea5,
	0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0xfed1, 0x061b, 0xfeb1, 0xfeb5, 0xfeb9, 0x061f,
	0x00a2, 0xfe80, 0xfe81, 0xfe83, 0xfe85, 0xfeca, 0xfe8b, 0xfe8d, 0xfe91, 0xfe93, 0xfe97, 0xfe9b, 0xfe9f, 0xfea3, 0xfea7, 0xfea9,
	0xfeab, 0xfead, 0xfeaf, 0xfeb3, 0xfeb7, 0xfebb, 0xfebf, 0xfec1, 0xfec5, 0xfecb, 0xfecf, 0x00a6, 0x00ac, 0x00f7, 0x00d7, 0xfec9,
	0x0640, 0xfed3, 0xfed7, 0xfedb, 0xfedf, 0xfee3, 0xfee7, 0xfeeb, 0xfeed, 0xfeef, 0xfef3, 0xfebd, 0xfecc, 0xfece, 0xfecd, 0xfee1,
	0xfe7d, 0x0651, 0xfee5, 0xfee9, 0xfeec, 0xfef0, 0xfef2, 0xfed0, 0xfed5, 0xfef5, 0xfef6, 0xfedd, 0xfed9, 0xfef1, 0x25a0, UCS4ud};

inline constexpr char32_t CharsetTableCP865[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00a4,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP866[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
	0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
	0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
	0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040e, 0x045e, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x2116, 0x00a4, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP869[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, 0x0386, UCS4ud, 0x00b7, 0x00ac, 0x00a6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389,
	0x038a, 0x03aa, 0x038c, UCS4ud, UCS4ud, 0x038e, 0x03ab, 0x00a9, 0x038f, 0x00b2, 0x00b3, 0x03ac, 0x00a3, 0x03ad, 0x03ae, 0x03af,
	0x03ca, 0x0390, 0x03cc, 0x03cd, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00bd, 0x0398, 0x0399, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039a, 0x039b, 0x039c, 0x039d, 0x2563, 0x2551, 0x2557, 0x255d, 0x039e, 0x039f, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x03a0, 0x03a1, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x03a3,
	0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x03b1, 0x03b2, 0x03b3, 0x2518, 0x250c, 0x2588, 0x2584, 0x03b4, 0x03b5, 0x2580,
	0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x0384,
	0x00ad, 0x00b1, 0x03c5, 0x03c6, 0x03c7, 0x00a7, 0x03c8, 0x0385, 0x00b0, 0x00a8, 0x03c9, 0x03cb, 0x03b0, 0x03ce, 0x25a0, 0x00a0};

inline constexpr char32_t CharsetTableCP874[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
	0x20AC, UCS4ud, UCS4ud, UCS4ud, UCS4ud, 0x2026, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud,
	UCS4ud, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud, UCS4ud,
	0x00A0, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F,
	0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F,
	0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27, 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F,
	0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, 0x0E38, 0x0E39, 0x0E3A, UCS4ud, UCS4ud, UCS4ud, UCS4ud, 0x0E3F,
	0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F,
	0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, UCS4ud, UCS4ud, UCS4ud, UCS4ud};

// <https://de.wikipedia.org/wiki/Commodore_Amiga_(Zeichensatz)>
inline constexpr char32_t CharsetTableAmiga[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2592,
	0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
	0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x2013, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};

// Based on RISCOSI.TXT from <https://www.unicode.org/L2/L2019/19025-terminals-prop.pdf>,
// with gaps filled in from standard set at <https://en.wikipedia.org/wiki/RISC_OS_character_set>.
inline constexpr char32_t CharsetTableRISC_OS[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x20AC, 0x0174, 0x0175, 0x25f0, 0x1fbc0, 0x0176, 0x0177, 0xfffd, 0x21e6, 0x21e8, 0x21e9, 0x21e7, 0x2026, 0x2122, 0x2030, 0x2022,
	0x2018, 0x2019, 0x2039, 0x203A, 0x201C, 0x201D, 0x201E, 0x2013, 0x2014, 0x2212, 0x0152, 0x0153, 0x2020, 0x2021, 0xFB01, 0xFB02,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};

// <https://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/ATARIST.TXT>
inline constexpr char32_t CharsetTableAtariST[256] = {
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x00DF, 0x0192,
	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
	0x00E3, 0x00F5, 0x00D8, 0x00F8, 0x0153, 0x0152, 0x00C0, 0x00C3, 0x00D5, 0x00A8, 0x00B4, 0x2020, 0x00B6, 0x00A9, 0x00AE, 0x2122,
	0x0133, 0x0132, 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0,
	0x05E1, 0x05E2, 0x05E4, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05DF, 0x05DA, 0x05DD, 0x05E3, 0x05E5, 0x00A7, 0x2227, 0x221E,
	0x03B1, 0x03B2, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x222E, 0x03C6, 0x2208, 0x2229,
	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x00B3, 0x00AF};

template <typename Tsrcstring>
inline mpt::widestring decode_8bit(const Tsrcstring & str, const char32_t (&table)[256], mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	mpt::widestring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		std::size_t c = static_cast<std::size_t>(mpt::char_value(str[i]));
		if (c < std::size(table)) {
			res.push_back(static_cast<mpt::widechar>(table[c]));
		} else {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tdststring>
inline Tdststring encode_8bit(const mpt::widestring & str, const char32_t (&table)[256], char replacement = '?') {
	Tdststring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		char32_t c = static_cast<char32_t>(str[i]);
		bool found = false;
		// Try non-control characters first.
		// In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets),
		// characters in the common range are preferred this way.
		for (std::size_t x = 0x20; x < std::size(table); ++x) {
			if (c == table[x]) {
				res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
				found = true;
				break;
			}
		}
		if (!found) {
			// try control characters
			for (std::size_t x = 0x00; x < std::size(table) && x < 0x20; ++x) {
				if (c == table[x]) {
					res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
					found = true;
					break;
				}
			}
		}
		if (!found) {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tsrcstring>
inline mpt::widestring decode_8bit_no_c1(const Tsrcstring & str, const char32_t (&table)[256], mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	mpt::widestring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		std::size_t c = static_cast<std::size_t>(mpt::char_value(str[i]));
		if ((0x80 <= c) && (c <= 0x9f)) {
			res.push_back(replacement);
		} else if (c < std::size(table)) {
			res.push_back(static_cast<mpt::widechar>(table[c]));
		} else {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tdststring>
inline Tdststring encode_8bit_no_c1(const mpt::widestring & str, const char32_t (&table)[256], char replacement = '?') {
	Tdststring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		char32_t c = static_cast<char32_t>(str[i]);
		bool found = false;
		// Try non-control characters first.
		// In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets),
		// characters in the common range are preferred this way.
		for (std::size_t x = 0x20; x < std::size(table); ++x) {
			if ((0x80 <= c) && (c <= 0x9f)) {
				continue;
			}
			if (c == table[x]) {
				res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
				found = true;
				break;
			}
		}
		if (!found) {
			// try control characters
			for (std::size_t x = 0x00; x < std::size(table) && x < 0x20; ++x) {
				if (c == table[x]) {
					res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
					found = true;
					break;
				}
			}
		}
		if (!found) {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tsrcstring>
inline mpt::widestring decode_ascii(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	mpt::widestring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		uint8 c = mpt::char_value(str[i]);
		if (c <= 0x7f) {
			res.push_back(static_cast<mpt::widechar>(static_cast<uint32>(c)));
		} else {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tdststring>
inline Tdststring encode_ascii(const mpt::widestring & str, char replacement = '?') {
	Tdststring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		char32_t c = static_cast<char32_t>(str[i]);
		if (c <= 0x7f) {
			res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(c)));
		} else {
			res.push_back(replacement);
		}
	}
	return res;
}

template <typename Tsrcstring>
inline mpt::widestring decode_iso8859_1(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	MPT_UNUSED(replacement);
	mpt::widestring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		uint8 c = mpt::char_value(str[i]);
		res.push_back(static_cast<mpt::widechar>(static_cast<uint32>(c)));
	}
	return res;
}

template <typename Tdststring>
inline Tdststring encode_iso8859_1(const mpt::widestring & str, char replacement = '?') {
	Tdststring res;
	res.reserve(str.length());
	for (std::size_t i = 0; i < str.length(); ++i) {
		char32_t c = static_cast<char32_t>(str[i]);
		if (c <= 0xff) {
			res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(c)));
		} else {
			res.push_back(replacement);
		}
	}
	return res;
}



template <typename Tsrcstring>
inline mpt::widestring decode_utf8(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	const Tsrcstring & in = str;
	mpt::widestring out;
	// state:
	std::size_t charsleft = 0;
	char32_t ucs4 = 0;
	for (uint8 c : in) {
		if (charsleft == 0) {
			if ((c & 0x80) == 0x00) {
				out.push_back(static_cast<mpt::widechar>(c));
			} else if ((c & 0xE0) == 0xC0) {
				ucs4 = c & 0x1F;
				charsleft = 1;
			} else if ((c & 0xF0) == 0xE0) {
				ucs4 = c & 0x0F;
				charsleft = 2;
			} else if ((c & 0xF8) == 0xF0) {
				ucs4 = c & 0x07;
				charsleft = 3;
			} else {
				out.push_back(replacement);
				ucs4 = 0;
				charsleft = 0;
			}
		} else {
			if ((c & 0xC0) != 0x80) {
				out.push_back(replacement);
				ucs4 = 0;
				charsleft = 0;
			}
			ucs4 <<= 6;
			ucs4 |= c & 0x3F;
			charsleft--;
			if (charsleft == 0) {
				if constexpr (sizeof(mpt::widechar) == 2) {
					if (ucs4 > 0x1fffff) {
						out.push_back(replacement);
						ucs4 = 0;
						charsleft = 0;
					}
					if (ucs4 <= 0xffff) {
						out.push_back(static_cast<mpt::widechar>(ucs4));
					} else {
						uint32 surrogate = static_cast<uint32>(ucs4) - 0x10000;
						uint16 hi_sur = static_cast<uint16>((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1)));
						uint16 lo_sur = static_cast<uint16>((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1)));
						out.push_back(hi_sur);
						out.push_back(lo_sur);
					}
				} else {
					out.push_back(static_cast<mpt::widechar>(ucs4));
				}
				ucs4 = 0;
			}
		}
	}
	if (charsleft != 0) {
		out.push_back(replacement);
		ucs4 = 0;
		charsleft = 0;
	}
	return out;
}

template <typename Tdststring>
inline Tdststring encode_utf8(const mpt::widestring & str, typename Tdststring::value_type replacement = static_cast<typename Tdststring::value_type>(mpt::char_value('?'))) {
	const mpt::widestring & in = str;
	Tdststring out;
	for (std::size_t i = 0; i < in.length(); i++) {
		mpt::widechar wc = in[i];
		char32_t ucs4 = 0;
		if constexpr (sizeof(mpt::widechar) == 2) {
			uint16 c = static_cast<uint16>(wc);
			if (i + 1 < in.length()) {
				// check for surrogate pair
				uint16 hi_sur = in[i + 0];
				uint16 lo_sur = in[i + 1];
				if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) {
					// surrogate pair
					++i;
					hi_sur &= (1 << 10) - 1;
					lo_sur &= (1 << 10) - 1;
					ucs4 = (static_cast<uint32>(hi_sur) << 10) | (static_cast<uint32>(lo_sur) << 0);
				} else {
					// no surrogate pair
					ucs4 = static_cast<char32_t>(c);
				}
			} else {
				// no surrogate possible
				ucs4 = static_cast<char32_t>(c);
			}
		} else {
			ucs4 = static_cast<char32_t>(wc);
		}
		if (ucs4 > 0x1fffff) {
			out.push_back(replacement);
			continue;
		}
		uint8 utf8[6];
		std::size_t numchars = 0;
		for (numchars = 0; numchars < 6; numchars++) {
			utf8[numchars] = ucs4 & 0x3F;
			ucs4 >>= 6;
			if (ucs4 == 0) {
				break;
			}
		}
		numchars++;
		if (numchars == 1) {
			out.push_back(utf8[0]);
			continue;
		}
		if (numchars == 2 && utf8[numchars - 1] == 0x01) {
			// generate shortest form
			out.push_back(utf8[0] | 0x40);
			continue;
		}
		std::size_t charsleft = numchars;
		while (charsleft > 0) {
			if (charsleft == numchars) {
				out.push_back(static_cast<uint8>(utf8[charsleft - 1] | (((1 << numchars) - 1) << (8 - numchars))));
			} else {
				// cppcheck false-positive
				// cppcheck-suppress arrayIndexOutOfBounds
				out.push_back(static_cast<uint8>(utf8[charsleft - 1] | 0x80u));
			}
			charsleft--;
		}
	}
	return out;
}



template <typename Tdststring, typename Tsrcstring>
inline Tdststring utf32_from_utf16(const Tsrcstring & in, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	static_assert(sizeof(typename Tsrcstring::value_type) == 2);
	static_assert(sizeof(typename Tdststring::value_type) == 4);
	MPT_UNUSED(replacement);
	Tdststring out;
	out.reserve(in.length());
	for (std::size_t i = 0; i < in.length(); i++) {
		char16_t wc = static_cast<char16_t>(static_cast<uint16>(in[i]));
		char32_t ucs4 = 0;
		uint16 c = static_cast<uint16>(wc);
		if (i + 1 < in.length()) {
			// check for surrogate pair
			uint16 hi_sur = in[i + 0];
			uint16 lo_sur = in[i + 1];
			if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) {
				// surrogate pair
				++i;
				hi_sur &= (1 << 10) - 1;
				lo_sur &= (1 << 10) - 1;
				ucs4 = (static_cast<uint32>(hi_sur) << 10) | (static_cast<uint32>(lo_sur) << 0);
			} else {
				// no surrogate pair
				ucs4 = static_cast<char32_t>(c);
			}
		} else {
			// no surrogate possible
			ucs4 = static_cast<char32_t>(c);
		}
		out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint32>(ucs4)));
	}
	return out;
}

template <typename Tdststring, typename Tsrcstring>
inline Tdststring utf16_from_utf32(const Tsrcstring & in, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
	static_assert(sizeof(typename Tsrcstring::value_type) == 4);
	static_assert(sizeof(typename Tdststring::value_type) == 2);
	Tdststring out;
	out.reserve(in.length());
	for (std::size_t i = 0; i < in.length(); i++) {
		char32_t ucs4 = static_cast<char32_t>(static_cast<uint32>(in[i]));
		if (ucs4 > 0x1fffff) {
			out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint16>(replacement)));
			ucs4 = 0;
		}
		if (ucs4 <= 0xffff) {
			out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint16>(ucs4)));
		} else {
			uint32 surrogate = static_cast<uint32>(ucs4) - 0x10000;
			uint16 hi_sur = static_cast<uint16>((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1)));
			uint16 lo_sur = static_cast<uint16>((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1)));
			out.push_back(static_cast<typename Tdststring::value_type>(hi_sur));
			out.push_back(static_cast<typename Tdststring::value_type>(lo_sur));
		}
	}
	return out;
}



#if MPT_OS_WINDOWS

inline bool has_codepage(UINT cp) {
	return IsValidCodePage(cp) ? true : false;
}

inline bool windows_has_encoding(common_encoding encoding) {
	bool result = false;
	switch (encoding) {
		case common_encoding::utf8:
			result = has_codepage(CP_UTF8);
			break;
		case common_encoding::ascii:
			result = has_codepage(20127);
			break;
		case common_encoding::iso8859_1:
			result = has_codepage(28591);
			break;
		case common_encoding::iso8859_15:
			result = has_codepage(28605);
			break;
		case common_encoding::cp437:
			result = has_codepage(437);
			break;
		case common_encoding::cp737:
			result = has_codepage(737);
			break;
		case common_encoding::cp775:
			result = has_codepage(775);
			break;
		case common_encoding::cp850:
			result = has_codepage(850);
			break;
		case common_encoding::cp852:
			result = has_codepage(852);
			break;
		case common_encoding::cp855:
			result = has_codepage(855);
			break;
		case common_encoding::cp857:
			result = has_codepage(857);
			break;
		case common_encoding::cp860:
			result = has_codepage(860);
			break;
		case common_encoding::cp861:
			result = has_codepage(861);
			break;
		case common_encoding::cp862:
			result = has_codepage(862);
			break;
		case common_encoding::cp863:
			result = has_codepage(863);
			break;
		case common_encoding::cp864:
			result = has_codepage(864);
			break;
		case common_encoding::cp865:
			result = has_codepage(865);
			break;
		case common_encoding::cp866:
			result = has_codepage(866);
			break;
		case common_encoding::cp869:
			result = has_codepage(869);
			break;
		case common_encoding::cp874:
			result = has_codepage(874);
			break;
		case common_encoding::windows1252:
			result = has_codepage(1252);
			break;
		case common_encoding::amiga:
			result = false;
			break;
		case common_encoding::riscos:
			result = false;
			break;
		case common_encoding::atarist:
			result = false;
			break;
		case common_encoding::iso8859_1_no_c1:
			result = false;
			break;
		case common_encoding::iso8859_15_no_c1:
			result = false;
			break;
		case common_encoding::amiga_no_c1:
			result = false;
			break;
	}
	return result;
}

inline bool windows_has_encoding(logical_encoding encoding) {
	bool result = false;
	switch (encoding) {
		case logical_encoding::locale:
			result = true;
			break;
		case logical_encoding::active_locale:
			result = false;
			break;
	}
	return result;
}

inline UINT codepage_from_encoding(logical_encoding encoding) {
	UINT result = 0;
	switch (encoding) {
		case logical_encoding::locale:
			result = CP_ACP;
			break;
		case logical_encoding::active_locale:
			throw std::domain_error("unsupported encoding");
			break;
	}
	return result;
}

inline UINT codepage_from_encoding(common_encoding encoding) {
	UINT result = 0;
	switch (encoding) {
		case common_encoding::utf8:
			result = CP_UTF8;
			break;
		case common_encoding::ascii:
			result = 20127;
			break;
		case common_encoding::iso8859_1:
			result = 28591;
			break;
		case common_encoding::iso8859_15:
			result = 28605;
			break;
		case common_encoding::cp437:
			result = 437;
			break;
		case common_encoding::cp737:
			result = 737;
			break;
		case common_encoding::cp775:
			result = 775;
			break;
		case common_encoding::cp850:
			result = 850;
			break;
		case common_encoding::cp852:
			result = 852;
			break;
		case common_encoding::cp855:
			result = 855;
			break;
		case common_encoding::cp857:
			result = 857;
			break;
		case common_encoding::cp860:
			result = 860;
			break;
		case common_encoding::cp861:
			result = 861;
			break;
		case common_encoding::cp862:
			result = 862;
			break;
		case common_encoding::cp863:
			result = 863;
			break;
		case common_encoding::cp864:
			result = 864;
			break;
		case common_encoding::cp865:
			result = 865;
			break;
		case common_encoding::cp866:
			result = 866;
			break;
		case common_encoding::cp869:
			result = 869;
			break;
		case common_encoding::cp874:
			result = 874;
			break;
		case common_encoding::windows1252:
			result = 1252;
			break;
		case common_encoding::amiga:
			throw std::domain_error("unsupported encoding");
			break;
		case common_encoding::riscos:
			throw std::domain_error("unsupported encoding");
			break;
		case common_encoding::atarist:
			throw std::domain_error("unsupported encoding");
			break;
		case common_encoding::iso8859_1_no_c1:
			throw std::domain_error("unsupported encoding");
			break;
		case common_encoding::iso8859_15_no_c1:
			throw std::domain_error("unsupported encoding");
			break;
		case common_encoding::amiga_no_c1:
			throw std::domain_error("unsupported encoding");
			break;
	}
	return result;
}

inline std::optional<common_encoding> optional_encoding_from_codepage(UINT codepage) {
	std::optional<common_encoding> result = std::nullopt;
	switch (codepage) {
		case CP_UTF8:
			result = common_encoding::utf8;
			break;
		case 20127:
			result = common_encoding::ascii;
			break;
		case 28591:
			result = common_encoding::iso8859_1;
			break;
		case 28605:
			result = common_encoding::iso8859_15;
			break;
		case 437:
			result = common_encoding::cp437;
			break;
		case 737:
			result = common_encoding::cp737;
			break;
		case 775:
			result = common_encoding::cp775;
			break;
		case 850:
			result = common_encoding::cp850;
			break;
		case 852:
			result = common_encoding::cp852;
			break;
		case 855:
			result = common_encoding::cp855;
			break;
		case 857:
			result = common_encoding::cp857;
			break;
		case 860:
			result = common_encoding::cp860;
			break;
		case 861:
			result = common_encoding::cp861;
			break;
		case 862:
			result = common_encoding::cp862;
			break;
		case 863:
			result = common_encoding::cp863;
			break;
		case 864:
			result = common_encoding::cp864;
			break;
		case 865:
			result = common_encoding::cp865;
			break;
		case 866:
			result = common_encoding::cp866;
			break;
		case 869:
			result = common_encoding::cp869;
			break;
		case 874:
			result = common_encoding::cp874;
			break;
		case 1252:
			result = common_encoding::windows1252;
			break;
		default:
			result = std::nullopt;
			break;
	}
	return result;
}

inline common_encoding encoding_from_codepage(UINT codepage) {
	std::optional<common_encoding> optional_result = optional_encoding_from_codepage(codepage);
	if (!optional_result) {
		throw std::domain_error("unsupported encoding");
	}
	return *optional_result;
}

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)

template <typename Tdststring>
inline Tdststring encode_codepage(UINT codepage, const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
	Tdststring encoded_string;
	int required_size = WideCharToMultiByte(codepage, 0, src.data(), mpt::saturate_cast<int>(src.size()), nullptr, 0, nullptr, nullptr);
	if (required_size > 0) {
		encoded_string.resize(required_size);
		WideCharToMultiByte(codepage, 0, src.data(), mpt::saturate_cast<int>(src.size()), reinterpret_cast<CHAR *>(encoded_string.data()), required_size, nullptr, nullptr);
	}
	return encoded_string;
}

template <typename Tsrcstring>
inline mpt::widestring decode_codepage(UINT codepage, const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
	mpt::widestring decoded_string;
	int required_size = MultiByteToWideChar(codepage, 0, reinterpret_cast<const CHAR *>(src.data()), mpt::saturate_cast<int>(src.size()), nullptr, 0);
	if (required_size > 0) {
		decoded_string.resize(required_size);
		MultiByteToWideChar(codepage, 0, reinterpret_cast<const CHAR *>(src.data()), mpt::saturate_cast<int>(src.size()), decoded_string.data(), required_size);
	}
	return decoded_string;
}

#endif // !MPT_COMPILER_QUIRK_NO_WCHAR

#endif // MPT_OS_WINDOWS



#if MPT_OS_DJGPP

inline common_encoding djgpp_get_locale_encoding() {
	uint16 active_codepage = 437;
	uint16 system_codepage = 437;
	__dpmi_regs regs;
	std::memset(&regs, 0, sizeof(__dpmi_regs));
	regs.x.ax = 0x6601;
	if (__dpmi_int(0x21, &regs) == 0) {
		int cf = (regs.x.flags >> 0) & 1;
		if (cf == 0) {
			active_codepage = regs.x.bx;
			system_codepage = regs.x.dx;
		}
	}
	common_encoding result = common_encoding::cp437;
	if (active_codepage == 0) {
		result = common_encoding::cp437;
	} else if (active_codepage == 437) {
		result = common_encoding::cp437;
	} else if (active_codepage == 737) {
		result = common_encoding::cp737;
	} else if (active_codepage == 775) {
		result = common_encoding::cp775;
	} else if (active_codepage == 850) {
		result = common_encoding::cp850;
	} else if (active_codepage == 852) {
		result = common_encoding::cp852;
	} else if (active_codepage == 855) {
		result = common_encoding::cp855;
	} else if (active_codepage == 857) {
		result = common_encoding::cp857;
	} else if (active_codepage == 860) {
		result = common_encoding::cp860;
	} else if (active_codepage == 861) {
		result = common_encoding::cp861;
	} else if (active_codepage == 862) {
		result = common_encoding::cp862;
	} else if (active_codepage == 863) {
		result = common_encoding::cp863;
	} else if (active_codepage == 864) {
		result = common_encoding::cp864;
	} else if (active_codepage == 865) {
		result = common_encoding::cp865;
	} else if (active_codepage == 866) {
		result = common_encoding::cp866;
	} else if (active_codepage == 869) {
		result = common_encoding::cp869;
	} else if (active_codepage == 874) {
		result = common_encoding::cp874;
	} else if (system_codepage == 437) {
		result = common_encoding::cp437;
	} else if (system_codepage == 737) {
		result = common_encoding::cp737;
	} else if (system_codepage == 775) {
		result = common_encoding::cp775;
	} else if (system_codepage == 850) {
		result = common_encoding::cp850;
	} else if (system_codepage == 852) {
		result = common_encoding::cp852;
	} else if (system_codepage == 855) {
		result = common_encoding::cp855;
	} else if (system_codepage == 857) {
		result = common_encoding::cp857;
	} else if (system_codepage == 860) {
		result = common_encoding::cp860;
	} else if (system_codepage == 861) {
		result = common_encoding::cp861;
	} else if (system_codepage == 862) {
		result = common_encoding::cp862;
	} else if (system_codepage == 863) {
		result = common_encoding::cp863;
	} else if (system_codepage == 864) {
		result = common_encoding::cp864;
	} else if (system_codepage == 865) {
		result = common_encoding::cp865;
	} else if (system_codepage == 866) {
		result = common_encoding::cp866;
	} else if (system_codepage == 869) {
		result = common_encoding::cp869;
	} else if (system_codepage == 874) {
		result = common_encoding::cp874;
	} else {
		result = common_encoding::cp437;
	}
	return result;
}

#endif // MPT_OS_DJGPP



inline std::optional<common_encoding> optional_encoding_from_codepage(uint16 codepage) {
	std::optional<common_encoding> result = std::nullopt;
	switch (codepage) {
		case 65001:
			result = common_encoding::utf8;
			break;
		case 20127:
			result = common_encoding::ascii;
			break;
		case 28591:
			result = common_encoding::iso8859_1;
			break;
		case 28605:
			result = common_encoding::iso8859_15;
			break;
		case 437:
			result = common_encoding::cp437;
			break;
		case 737:
			result = common_encoding::cp737;
			break;
		case 775:
			result = common_encoding::cp775;
			break;
		case 850:
			result = common_encoding::cp850;
			break;
		case 852:
			result = common_encoding::cp852;
			break;
		case 855:
			result = common_encoding::cp855;
			break;
		case 857:
			result = common_encoding::cp857;
			break;
		case 860:
			result = common_encoding::cp860;
			break;
		case 861:
			result = common_encoding::cp861;
			break;
		case 862:
			result = common_encoding::cp862;
			break;
		case 863:
			result = common_encoding::cp863;
			break;
		case 864:
			result = common_encoding::cp864;
			break;
		case 865:
			result = common_encoding::cp865;
			break;
		case 866:
			result = common_encoding::cp866;
			break;
		case 869:
			result = common_encoding::cp869;
			break;
		case 874:
			result = common_encoding::cp874;
			break;
		case 1252:
			result = common_encoding::windows1252;
			break;
		default:
			result = std::nullopt;
			break;
	}
	return result;
}

inline common_encoding encoding_from_codepage(uint16 codepage) {
	std::optional<common_encoding> optional_result = optional_encoding_from_codepage(codepage);
	if (!optional_result) {
		throw std::domain_error("unsupported encoding");
	}
	return *optional_result;
}





#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)

// Note:
//
//  std::codecvt::out in LLVM libc++ does not advance in and out pointers when
// running into a non-convertible character. This can happen when no locale is
// set on FreeBSD or MacOSX. This behaviour violates the C++ standard.
//
//  We apply the following (albeit costly, even on other platforms) work-around:
//  If the conversion errors out and does not advance the pointers at all, we
// retry the conversion with a space character prepended to the string. If it
// still does error out, we retry the whole conversion character by character.
//  This is costly even on other platforms in one single case: The first
// character is an invalid Unicode code point or otherwise not convertible. Any
// following non-convertible characters are not a problem.

inline std::wstring decode_locale_impl(const std::string & str, const std::locale & locale, wchar_t replacement = L'\uFFFD', int retry = 0, bool * progress = nullptr) {
	if (str.empty()) {
		return std::wstring();
	}
	std::vector<wchar_t> out;
	using codecvt_type = std::codecvt<wchar_t, char, std::mbstate_t>;
	std::mbstate_t state = std::mbstate_t();
	const codecvt_type & facet = std::use_facet<codecvt_type>(locale);
	codecvt_type::result result = codecvt_type::partial;
	const char * in_begin = str.data();
	const char * in_end = in_begin + str.size();
	out.resize((in_end - in_begin) * (mpt::saturate_cast<std::size_t>(facet.max_length()) + 1));
	wchar_t * out_begin = out.data();
	wchar_t * out_end = out.data() + out.size();
	const char * in_next = nullptr;
	wchar_t * out_next = nullptr;
	do {
		if (retry == 2) {
			for (;;) {
				in_next = nullptr;
				out_next = nullptr;
				result = facet.in(state, in_begin, in_begin + 1, in_next, out_begin, out_end, out_next);
				if (result == codecvt_type::partial && in_next == in_begin + 1) {
					in_begin = in_next;
					out_begin = out_next;
					continue;
				} else {
					break;
				}
			}
		} else {
			in_next = nullptr;
			out_next = nullptr;
			result = facet.in(state, in_begin, in_end, in_next, out_begin, out_end, out_next);
		}
		if (result == codecvt_type::partial || (result == codecvt_type::error && out_next == out_end)) {
			out.resize(out.size() * 2);
			in_begin = in_next;
			out_begin = out.data() + (out_next - out_begin);
			out_end = out.data() + out.size();
			continue;
		}
		if (retry == 0) {
			if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
				bool made_progress = true;
				decode_locale_impl(std::string(" ") + str, locale, replacement, 1, &made_progress);
				if (!made_progress) {
					return decode_locale_impl(str, locale, replacement, 2);
				}
			}
		} else if (retry == 1) {
			if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
				*progress = false;
			} else {
				*progress = true;
			}
			return std::wstring();
		}
		if (result == codecvt_type::error) {
			++in_next;
			*out_next = replacement;
			++out_next;
		}
		in_begin = in_next;
		out_begin = out_next;
	} while ((result == codecvt_type::error && in_next < in_end && out_next < out_end) || (retry == 2 && in_next < in_end));
	return std::wstring(out.data(), out_next);
}

template <typename Tsrcstring>
inline mpt::widestring decode_locale(const std::locale & locale, const Tsrcstring & src) {
	if constexpr (std::is_same<Tsrcstring, std::string>::value) {
		return decode_locale_impl(src, locale);
	} else {
		return decode_locale_impl(std::string(src.begin(), src.end()), locale);
	}
}

inline std::string encode_locale_impl(const std::wstring & str, const std::locale & locale, char replacement = '?', int retry = 0, bool * progress = nullptr) {
	if (str.empty()) {
		return std::string();
	}
	std::vector<char> out;
	using codecvt_type = std::codecvt<wchar_t, char, std::mbstate_t>;
	std::mbstate_t state = std::mbstate_t();
	const codecvt_type & facet = std::use_facet<codecvt_type>(locale);
	codecvt_type::result result = codecvt_type::partial;
	const wchar_t * in_begin = str.data();
	const wchar_t * in_end = in_begin + str.size();
	out.resize((in_end - in_begin) * (mpt::saturate_cast<std::size_t>(facet.max_length()) + 1));
	char * out_begin = out.data();
	char * out_end = out.data() + out.size();
	const wchar_t * in_next = nullptr;
	char * out_next = nullptr;
	do {
		if (retry == 2) {
			for (;;) {
				in_next = nullptr;
				out_next = nullptr;
				result = facet.out(state, in_begin, in_begin + 1, in_next, out_begin, out_end, out_next);
				if (result == codecvt_type::partial && in_next == in_begin + 1) {
					in_begin = in_next;
					out_begin = out_next;
					continue;
				} else {
					break;
				}
			}
		} else {
			in_next = nullptr;
			out_next = nullptr;
			result = facet.out(state, in_begin, in_end, in_next, out_begin, out_end, out_next);
		}
		if (result == codecvt_type::partial || (result == codecvt_type::error && out_next == out_end)) {
			out.resize(out.size() * 2);
			in_begin = in_next;
			out_begin = out.data() + (out_next - out_begin);
			out_end = out.data() + out.size();
			continue;
		}
		if (retry == 0) {
			if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
				bool made_progress = true;
				encode_locale_impl(std::wstring(L" ") + str, locale, replacement, 1, &made_progress);
				if (!made_progress) {
					return encode_locale_impl(str, locale, replacement, 2);
				}
			}
		} else if (retry == 1) {
			if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
				*progress = false;
			} else {
				*progress = true;
			}
			return std::string();
		}
		if (result == codecvt_type::error) {
			++in_next;
			*out_next = replacement;
			++out_next;
		}
		in_begin = in_next;
		out_begin = out_next;
	} while ((result == codecvt_type::error && in_next < in_end && out_next < out_end) || (retry == 2 && in_next < in_end));
	return std::string(out.data(), out_next);
}

template <typename Tdststring>
inline Tdststring encode_locale(const std::locale & locale, const mpt::widestring & src) {
	if constexpr (std::is_same<Tdststring, std::string>::value) {
		return encode_locale_impl(src, locale);
	} else {
		const std::string tmp = encode_locale_impl(src, locale);
		return Tdststring(tmp.begin(), tmp.end());
	}
}

#endif // !MPT_COMPILER_QUIRK_NO_WCHAR



#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
template <typename Tdststring>
inline Tdststring encode(UINT codepage, const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
	return encode_codepage<Tdststring>(codepage, src);
}
#endif // MPT_OS_WINDOWS && !MPT_COMPILER_QUIRK_NO_WCHAR

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
template <typename Tdststring>
inline Tdststring encode(const std::locale & locale, const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
	return encode_locale<Tdststring>(src, locale);
}
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR

template <typename Tdststring>
inline Tdststring encode(const char32_t (&table)[256], const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
	return encode_8bit<Tdststring>(src, table);
}

template <typename Tdststring>
inline Tdststring encode(common_encoding encoding, const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	if (windows_has_encoding(encoding)) {
		return encode_codepage<Tdststring>(codepage_from_encoding(encoding), src);
	}
#endif // MPT_OS_WINDOWS && !MPT_COMPILER_QUIRK_NO_WCHAR
	switch (encoding) {
		case common_encoding::utf8:
			return encode_utf8<Tdststring>(src);
			break;
		case common_encoding::ascii:
			return encode_ascii<Tdststring>(src);
			break;
		case common_encoding::iso8859_1:
			return encode_iso8859_1<Tdststring>(src);
			break;
		case common_encoding::iso8859_15:
			return encode_8bit<Tdststring>(src, CharsetTableISO8859_15);
			break;
		case common_encoding::cp437:
			return encode_8bit<Tdststring>(src, CharsetTableCP437);
			break;
		case common_encoding::cp737:
			return encode_8bit<Tdststring>(src, CharsetTableCP737);
			break;
		case common_encoding::cp775:
			return encode_8bit<Tdststring>(src, CharsetTableCP775);
			break;
		case common_encoding::cp850:
			return encode_8bit<Tdststring>(src, CharsetTableCP850);
			break;
		case common_encoding::cp852:
			return encode_8bit<Tdststring>(src, CharsetTableCP852);
			break;
		case common_encoding::cp855:
			return encode_8bit<Tdststring>(src, CharsetTableCP855);
			break;
		case common_encoding::cp857:
			return encode_8bit<Tdststring>(src, CharsetTableCP857);
			break;
		case common_encoding::cp860:
			return encode_8bit<Tdststring>(src, CharsetTableCP860);
			break;
		case common_encoding::cp861:
			return encode_8bit<Tdststring>(src, CharsetTableCP861);
			break;
		case common_encoding::cp862:
			return encode_8bit<Tdststring>(src, CharsetTableCP862);
			break;
		case common_encoding::cp863:
			return encode_8bit<Tdststring>(src, CharsetTableCP863);
			break;
		case common_encoding::cp864:
			return encode_8bit<Tdststring>(src, CharsetTableCP864);
			break;
		case common_encoding::cp865:
			return encode_8bit<Tdststring>(src, CharsetTableCP865);
			break;
		case common_encoding::cp866:
			return encode_8bit<Tdststring>(src, CharsetTableCP866);
			break;
		case common_encoding::cp869:
			return encode_8bit<Tdststring>(src, CharsetTableCP869);
			break;
		case common_encoding::cp874:
			return encode_8bit<Tdststring>(src, CharsetTableCP874);
			break;
		case common_encoding::windows1252:
			return encode_8bit<Tdststring>(src, CharsetTableWindows1252);
			break;
		case common_encoding::amiga:
			return encode_8bit<Tdststring>(src, CharsetTableAmiga);
			break;
		case common_encoding::riscos:
			return encode_8bit<Tdststring>(src, CharsetTableRISC_OS);
			break;
		case common_encoding::atarist:
			return encode_8bit<Tdststring>(src, CharsetTableAtariST);
			break;
		case common_encoding::iso8859_1_no_c1:
			return encode_8bit_no_c1<Tdststring>(src, CharsetTableISO8859_1);
			break;
		case common_encoding::iso8859_15_no_c1:
			return encode_8bit_no_c1<Tdststring>(src, CharsetTableISO8859_15);
			break;
		case common_encoding::amiga_no_c1:
			return encode_8bit_no_c1<Tdststring>(src, CharsetTableAmiga);
			break;
	}
	throw std::domain_error("unsupported encoding");
}

template <typename Tdststring>
inline Tdststring encode(logical_encoding encoding, const mpt::widestring & src) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	if (windows_has_encoding(encoding)) {
		return encode_codepage<Tdststring>(codepage_from_encoding(encoding), src);
	}
#elif MPT_OS_WINDOWS && defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	switch (encoding) {
		case logical_encoding::locale:
			return encode<Tdststring>(optional_encoding_from_codepage(GetACP()).value_or(common_encoding::windows1252), src);
			break;
		case logical_encoding::active_locale:
			return encode<Tdststring>(optional_encoding_from_codepage(GetACP()).value_or(common_encoding::windows1252), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#endif // MPT_OS_WINDOWS && !MPT_COMPILER_QUIRK_NO_WCHAR
#if MPT_OS_DJGPP
	switch (encoding) {
		case logical_encoding::locale:
			return encode<Tdststring>(djgpp_get_locale_encoding(), src);
			break;
		case logical_encoding::active_locale:
			return encode<Tdststring>(djgpp_get_locale_encoding(), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#elif !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	switch (encoding) {
		case logical_encoding::locale:
			return encode_locale<Tdststring>(std::locale("C"), src);
			break;
		case logical_encoding::active_locale:
			return encode_locale<Tdststring>(std::locale(), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#else
	throw std::domain_error("unsupported encoding");
#endif
}

template <typename Tdststring, typename Ttranscoder>
inline auto encode(Ttranscoder transcoder, const mpt::widestring & src) -> decltype(transcoder.template encode<Tdststring>(src)) {
	static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tdststring::value_type>::value);
	return transcoder.template encode<Tdststring>(src);
}

#if MPT_OS_WINDOWS
template <typename Tsrcstring>
inline mpt::widestring decode(UINT codepage, const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
	return decode_codepage(codepage, src);
}
#endif // MPT_OS_WINDOWS

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
template <typename Tsrcstring>
inline mpt::widestring decode(const std::locale & locale, const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
	return decode_locale(src, locale);
}
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR

template <typename Tsrcstring>
inline mpt::widestring decode(const char32_t (&table)[256], const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
	return decode_8bit(src, table);
}

template <typename Tsrcstring>
inline mpt::widestring decode(common_encoding encoding, const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	if (windows_has_encoding(encoding)) {
		return decode_codepage(codepage_from_encoding(encoding), src);
	}
#endif // MPT_OS_WINDOWS && !MPT_COMPILER_QUIRK_NO_WCHAR
	switch (encoding) {
		case common_encoding::utf8:
			return decode_utf8(src);
			break;
		case common_encoding::ascii:
			return decode_ascii(src);
			break;
		case common_encoding::iso8859_1:
			return decode_iso8859_1(src);
			break;
		case common_encoding::iso8859_15:
			return decode_8bit(src, CharsetTableISO8859_15);
			break;
		case common_encoding::cp437:
			return decode_8bit(src, CharsetTableCP437);
			break;
		case common_encoding::cp737:
			return decode_8bit(src, CharsetTableCP737);
			break;
		case common_encoding::cp775:
			return decode_8bit(src, CharsetTableCP775);
			break;
		case common_encoding::cp850:
			return decode_8bit(src, CharsetTableCP850);
			break;
		case common_encoding::cp852:
			return decode_8bit(src, CharsetTableCP852);
			break;
		case common_encoding::cp855:
			return decode_8bit(src, CharsetTableCP855);
			break;
		case common_encoding::cp857:
			return decode_8bit(src, CharsetTableCP857);
			break;
		case common_encoding::cp860:
			return decode_8bit(src, CharsetTableCP860);
			break;
		case common_encoding::cp861:
			return decode_8bit(src, CharsetTableCP861);
			break;
		case common_encoding::cp862:
			return decode_8bit(src, CharsetTableCP862);
			break;
		case common_encoding::cp863:
			return decode_8bit(src, CharsetTableCP863);
			break;
		case common_encoding::cp864:
			return decode_8bit(src, CharsetTableCP864);
			break;
		case common_encoding::cp865:
			return decode_8bit(src, CharsetTableCP865);
			break;
		case common_encoding::cp866:
			return decode_8bit(src, CharsetTableCP866);
			break;
		case common_encoding::cp869:
			return decode_8bit(src, CharsetTableCP869);
			break;
		case common_encoding::cp874:
			return decode_8bit(src, CharsetTableCP874);
			break;
		case common_encoding::windows1252:
			return decode_8bit(src, CharsetTableWindows1252);
			break;
		case common_encoding::amiga:
			return decode_8bit(src, CharsetTableAmiga);
			break;
		case common_encoding::riscos:
			return decode_8bit(src, CharsetTableRISC_OS);
			break;
		case common_encoding::atarist:
			return decode_8bit(src, CharsetTableAtariST);
			break;
		case common_encoding::iso8859_1_no_c1:
			return decode_8bit_no_c1(src, CharsetTableISO8859_1);
			break;
		case common_encoding::iso8859_15_no_c1:
			return decode_8bit_no_c1(src, CharsetTableISO8859_15);
			break;
		case common_encoding::amiga_no_c1:
			return decode_8bit_no_c1(src, CharsetTableAmiga);
			break;
	}
	throw std::domain_error("unsupported encoding");
}

template <typename Tsrcstring>
inline mpt::widestring decode(logical_encoding encoding, const Tsrcstring & src) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	if (windows_has_encoding(encoding)) {
		return decode_codepage(codepage_from_encoding(encoding), src);
	}
#elif MPT_OS_WINDOWS && defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	switch (encoding) {
		case logical_encoding::locale:
			return decode(optional_encoding_from_codepage(GetACP()).value_or(common_encoding::windows1252), src);
			break;
		case logical_encoding::active_locale:
			return decode(optional_encoding_from_codepage(GetACP()).value_or(common_encoding::windows1252), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#endif // MPT_OS_WINDOWS && !MPT_COMPILER_QUIRK_NO_WCHAR
#if MPT_OS_DJGPP
	switch (encoding) {
		case logical_encoding::locale:
			return decode(djgpp_get_locale_encoding(), src);
			break;
		case logical_encoding::active_locale:
			return decode(djgpp_get_locale_encoding(), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#elif !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
	switch (encoding) {
		case logical_encoding::locale:
			return decode_locale(std::locale("C"), src);
			break;
		case logical_encoding::active_locale:
			return decode_locale(std::locale(), src);
			break;
	}
	throw std::domain_error("unsupported encoding");
#else
	throw std::domain_error("unsupported encoding");
#endif
}

template <typename Tsrcstring, typename Ttranscoder>
inline auto decode(Ttranscoder transcoder, const Tsrcstring & src) -> decltype(transcoder.template decode<Tsrcstring>(src)) {
	static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
	static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
	return transcoder.template decode<Tsrcstring>(src);
}



inline bool is_utf8(const std::string & str) {
	return (str == encode<std::string>(common_encoding::utf8, decode<std::string>(common_encoding::utf8, str)));
}



template <typename Tstring>
struct string_transcoder {
};

#if defined(MPT_COMPILER_QUIRK_NO_AUTO_TEMPLATE_ARGUMENT)
template <typename encoding_type, encoding_type encoding>
struct string_transcoder<std::basic_string<char, encoding_char_traits<encoding_type, encoding>>> {
	using string_type = std::basic_string<char, encoding_char_traits<encoding_type, encoding>>;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::decode<string_type>(encoding, src);
	}
	static inline string_type encode(const mpt::widestring & src) {
		return mpt::encode<string_type>(encoding, src);
	}
};
#else
template <auto encoding>
struct string_transcoder<std::basic_string<char, encoding_char_traits<encoding>>> {
	using string_type = std::basic_string<char, encoding_char_traits<encoding>>;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::decode<string_type>(encoding, src);
	}
	static inline string_type encode(const mpt::widestring & src) {
		return mpt::encode<string_type>(encoding, src);
	}
};
#endif

#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
template <>
struct string_transcoder<std::wstring> {
	using string_type = std::wstring;
	static inline mpt::widestring decode(const string_type & src) {
		return src;
	}
	static inline string_type encode(const mpt::widestring & src) {
		return src;
	}
};
#endif // !MPT_COMPILER_QUIRK_NO_WCHAR

#if MPT_CXX_AT_LEAST(20)
template <>
struct string_transcoder<std::u8string> {
	using string_type = std::u8string;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::decode_utf8<string_type>(src);
	}
	static inline string_type encode(const mpt::widestring & src) {
		return mpt::encode_utf8<string_type>(src);
	}
};
#endif // C++10

template <>
struct string_transcoder<std::u16string> {
	using string_type = std::u16string;
	static inline mpt::widestring decode(const string_type & src) {
		if constexpr (sizeof(mpt::widechar) == sizeof(char16_t)) {
			return mpt::widestring(src.begin(), src.end());
		} else {
			return utf32_from_utf16<mpt::widestring, std::u16string>(src);
		}
	}
	static inline string_type encode(const mpt::widestring & src) {
		if constexpr (sizeof(mpt::widechar) == sizeof(char16_t)) {
			return string_type(src.begin(), src.end());
		} else {
			return utf16_from_utf32<std::u16string, mpt::widestring>(src);
		}
	}
};

#if defined(MPT_COMPILER_QUIRK_NO_WCHAR)
template <>
struct string_transcoder<std::u32string> {
	using string_type = std::u32string;
	static inline mpt::widestring decode(const string_type & src) {
		return src;
	}
	static inline string_type encode(const mpt::widestring & src) {
		return src;
	}
};
#else  // !MPT_COMPILER_QUIRK_NO_WCHAR
template <>
struct string_transcoder<std::u32string> {
	using string_type = std::u32string;
	static inline mpt::widestring decode(const string_type & src) {
		if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
			return mpt::widestring(src.begin(), src.end());
		} else {
			return utf16_from_utf32<mpt::widestring, std::u32string>(src);
		}
	}
	static inline string_type encode(const mpt::widestring & src) {
		if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
			return string_type(src.begin(), src.end());
		} else {
			return utf32_from_utf16<std::u32string, mpt::widestring>(src);
		}
	}
};
#endif // MPT_COMPILER_QUIRK_NO_WCHAR

#if MPT_DETECTED_MFC

template <>
struct string_transcoder<CStringW> {
	using string_type = CStringW;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::widestring(src.GetString());
	}
	static inline string_type encode(const mpt::widestring & src) {
		return src.c_str();
	}
};

template <>
struct string_transcoder<CStringA> {
	using string_type = CStringA;
	static inline mpt::widestring decode(const string_type & src) {
		return mpt::decode<std::string>(mpt::logical_encoding::locale, std::string(src.GetString()));
	}
	static inline string_type encode(const mpt::widestring & src) {
		return mpt::encode<std::string>(mpt::logical_encoding::locale, src).c_str();
	}
};

#endif // MPT_DETECTED_MFC

template <typename Tdststring, typename Tsrcstring, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type>::value, bool> = true>
inline Tdststring transcode(Tsrcstring && src) {
	if constexpr (std::is_same<Tdststring, typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type>::value) {
		return mpt::as_string(std::forward<Tsrcstring>(src));
	} else {
		return string_transcoder<Tdststring>::encode(string_transcoder<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::decode(mpt::as_string(std::forward<Tsrcstring>(src))));
	}
}

template <typename Tdststring, typename Tsrcstring, typename Tencoding, std::enable_if_t<std::is_same<Tdststring, std::string>::value, bool> = true, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type>::value, bool> = true>
inline Tdststring transcode(Tencoding to, Tsrcstring && src) {
	if constexpr (std::is_same<Tencoding, mpt::common_encoding>::value) {
		if constexpr (std::is_same<decltype(mpt::as_string(std::forward<Tsrcstring>(src))), mpt::u8string>::value) {
			if (to == mpt::common_encoding::utf8) {
				auto src_ = mpt::as_string(std::forward<Tsrcstring>(src));
				Tdststring dst;
				mpt::string_traits<Tdststring>::reserve(dst, mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::length(src_));
				for (std::size_t i = 0; i < mpt::saturate_cast<std::size_t>(mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::length(src_)); ++i) {
					mpt::string_traits<Tdststring>::append(dst, static_cast<typename mpt::string_traits<Tdststring>::unsigned_char_type>(static_cast<typename mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::unsigned_char_type>(src_[i])));
				}
				return dst;
			}
		}
	}
	return mpt::encode<Tdststring>(to, string_transcoder<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::decode(mpt::as_string(std::forward<Tsrcstring>(src))));
}

template <typename Tdststring, typename Tsrcstring, typename Tencoding, std::enable_if_t<std::is_same<typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type, std::string>::value, bool> = true, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type>::value, bool> = true>
inline Tdststring transcode(Tencoding from, Tsrcstring && src) {
	if constexpr (std::is_same<Tencoding, mpt::common_encoding>::value) {
		if constexpr (std::is_same<Tdststring, mpt::u8string>::value) {
			if (from == mpt::common_encoding::utf8) {
				auto src_ = mpt::as_string(std::forward<Tsrcstring>(src));
				Tdststring dst;
				mpt::string_traits<Tdststring>::reserve(dst, mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::length(src_));
				for (std::size_t i = 0; i < mpt::saturate_cast<std::size_t>(mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::length(src_)); ++i) {
					mpt::string_traits<Tdststring>::append(dst, static_cast<typename mpt::string_traits<Tdststring>::unsigned_char_type>(static_cast<typename mpt::string_traits<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>::unsigned_char_type>(src_[i])));
				}
				return dst;
			}
		}
	}
	return string_transcoder<Tdststring>::encode(mpt::decode<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>(from, mpt::as_string(std::forward<Tsrcstring>(src))));
}

template <typename Tdststring, typename Tsrcstring, typename Tto, typename Tfrom, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<typename std::decay<Tsrcstring>::type>::type>::value, bool> = true>
inline Tdststring transcode(Tto to, Tfrom from, Tsrcstring && src) {
	if constexpr (std::is_same<Tto, Tfrom>::value) {
		if constexpr (std::is_same<decltype(mpt::as_string(std::forward<Tsrcstring>(src))), Tdststring>::value) {
			if (to == from) {
				return mpt::as_string(std::forward<Tsrcstring>(src));
			}
		}
	}
	return mpt::encode<Tdststring>(to, mpt::decode<decltype(mpt::as_string(std::forward<Tsrcstring>(src)))>(from, mpt::as_string(std::forward<Tsrcstring>(src))));
}



} // namespace MPT_INLINE_NS
} // namespace mpt



#endif // MPT_STRING_TRANSCODE_TRANSCODE_HPP
