Cleanup country support, add historic locales

This commit is contained in:
FeralChild64 2023-11-20 21:49:19 +01:00 committed by FeralChild64
parent 5954fa1c45
commit 6c2badb7ca
16 changed files with 3189 additions and 938 deletions

View file

@ -31,7 +31,7 @@ _dosbox()
)\
_exclusive_opts=(
--version --printconf --editconf -resetconf -eraseconf
-erasemapper -resetmapper --list-glshaders
-erasemapper -resetmapper --list-countries --list-glshaders
)
####

View file

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*-
.TH DOSBOX 1 "Oct 27, 2023"
.TH DOSBOX 1 "Dec 4, 2023"
.\" Please adjust this date whenever revising the manpage.
.SH NAME
@ -39,6 +39,8 @@ dosbox \- DOSBox Staging, x86/DOS emulator, a modern continuation of DOSBox
.LP
.B dosbox \-\-working-dir <path>
.LP
.B dosbox \-\-list\-countries
.LP
.B dosbox \-\-list\-glshaders
.LP
.B dosbox \-\-erasemapper
@ -89,8 +91,11 @@ Legacy single dash abbrevations still work for backwards compatibility.
--working-dir <path> Set working directory to <path>. DOSBox will act as if
started from this directory.
--list-countries List all supported countries with their numeric codes.
Codes are to be used in the 'country' config setting.
--list-glshaders List all available OpenGL shaders and their paths.
Results are useable in the 'glshader = ' config setting.
Shaders are to be used in the 'glshader' config setting.
--fullscreen Start in fullscreen mode.

View file

@ -45,6 +45,7 @@ struct CommandLineArguments {
bool noprimaryconf;
bool nolocalconf;
bool fullscreen;
bool list_countries;
bool list_glshaders;
bool version;
bool help;

View file

@ -159,4 +159,27 @@ bool get_expanded_files(const std::string &path,
std::string get_language_from_os();
// A printf variant outputting UTF-8 strings
template <typename... Arguments>
void printf_utf8(const char* format, Arguments... arguments)
{
#ifdef WIN32
constexpr uint16_t CodePageUtf8 = 65001;
const auto old_code_page = GetConsoleOutputCP();
SetConsoleOutputCP(CodePageUtf8);
try {
printf(format, arguments...);
} catch (...) {
SetConsoleOutputCP(old_code_page);
throw;
}
SetConsoleOutputCP(old_code_page);
#else
// Assume any OS without special support uses UTF-8 as console encoding
printf(format, arguments...);
#endif
}
#endif

View file

@ -878,127 +878,36 @@ static inline uint8_t RealHandle(uint16_t handle) {
return psp.GetFileHandle(handle);
}
#define DOS_DATE_FORMAT_OFS 0
#define DOS_DATE_SEPARATOR_OFS 11
#define DOS_TIME_FORMAT_OFS 17
#define DOS_TIME_SEPARATOR_OFS 13
#define DOS_THOUSANDS_SEPARATOR_OFS 7
#define DOS_DECIMAL_SEPARATOR_OFS 9
/* Locale information */
// Sources of the country numbers:
// - MS-DOS 6.22, COUNTRY.TXT file
// - PC-DOS 2000, HELP COUNTRY command, information table
// - DR-DOS 7.03, HELP, Table 9-2: Country Codes and Code Pages
// - FreeDOS 1.3, country.asm (source code)
// - Paragon PTS DOS 2000 Pro manual
// - https://en.wikipedia.org/wiki/List_of_country_calling_codes
// (used for remaining countries, especially where we have keyboard layout)
enum class Country : uint16_t {
United_States = 1, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Canada_French = 2, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Latin_America = 3, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Canada_English = 4, // MS-DOS
Russia = 7, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
South_Africa = 27, // MS-DOS
Greece = 30, // MS-DOS, PC-DOS, FreeDOS
Netherlands = 31, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Belgium = 32, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
France = 33, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Spain = 34, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Hungary = 36, // MS-DOS, PC-DOS, DR-DOS, FreeDOS
Yugoslavia = 38, // MS-DOS, PC-DOS, FreeDOS
Italy = 39, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Romania = 40, // MS-DOS, PC-DOS, FreeDOS
Switzerland = 41, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Czechia = 42, // MS-DOS (*)
Austria = 43, // MS-DOS FreeDOS
United_Kingdom = 44, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Denmark = 45, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Sweden = 46, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Norway = 47, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Poland = 48, // MS-DOS, PC-DOS, FreeDOS
Germany = 49, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Mexico = 52, // MS-DOS
Argentina = 54, // MS-DOS, FreeDOS
Brazil = 55, // MS-DOS, PC-DOS, FreeDOS
Chile = 56, // MS-DOS
Colombia = 57, // MS-DOS
Venezuela = 58, // MS-DOS
Malaysia = 60, // MS-DOS, FreeDOS
Australia = 61, // MS-DOS, PC-DOS, DR-DOS, FreeDOS (*)
Philippines = 63,
New_Zealand = 64, // MS-DOS
Singapore = 65, // MS-DOS, FreeDOS
Kazakhstan = 77,
Japan = 81, // MS-DOS, PC-DOS, FreeDOS, Paragon
Korea = 82, // MS-DOS, FreeDOS, Paragon (*)
Vietnam = 84,
China = 86, // MS-DOS, FreeDOS, Paragon
Turkey = 90, // MS-DOS, PC-DOS, DR-DOS, FreeDOS
India = 91, // MS-DOS, FreeDOS
Niger = 227,
Benin = 229,
Nigeria = 234,
Faroe_Islands = 298,
Portugal = 351, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Ireland = 353, // MS-DOS
Iceland = 354, // MS-DOS, PC-DOS
Albania = 355, // MS-DOS, PC-DOS
Malta = 356,
Finland = 358, // MS-DOS, PC-DOS, DR-DOS, FreeDOS, Paragon
Bulgaria = 359, // MS-DOS, PC-DOS, FreeDOS
Lithuania = 370,
Latvia = 371,
Estonia = 372,
Armenia = 374,
Belarus = 375, // FreeDOS
Ukraine = 380, // FreeDOS
Serbia = 381, // MS-DOS, PC-DOS, FreeDOS (*)
Montenegro = 382,
Croatia = 384, // MS-DOS, FreeDOS (*)
Slovenia = 386, // MS-DOS, PC-DOS, FreeDOS
Bosnia_Herzegovina = 387, // PC-DOS, FreeDOS
Macedonia = 389, // MS-DOS, PC-DOS, FreeDOS
Slovakia = 421, // MS-DOS (*)
Ecuador = 593, // MS-DOS
Arabic = 785, // MS-DOS, FreeDOS, Paragon (*)
Hong_Kong = 852, // MS-DOS
Taiwan = 886, // MS-DOS
Israel = 972, // MS-DOS, FreeDOS, Paragon
Mongolia = 976,
Tadjikistan = 992,
Turkmenistan = 993,
Azerbaijan = 994,
Georgia = 995,
Kyrgyzstan = 996,
Uzbekistan = 998,
// (*) Remarks:
// - MS-DOS and PC-DOS use country code 381 for both Serbia and Montenegro
// - MS-DOS and PC-DOS use country code 61 also for International English
// - PC-DOS uses country code 381 also for Yugoslavia Cyrillic
// - PC-DOS uses country code 385 (not 386) for Croatia
// - PC-DOS uses country code 388 for Bosna/Herzegovina Cyrillic
// - PC-DOS uses country code 421 for Czechia and 422 for Slovakia
// - FreeDOS uses country code 042 for Czechoslovakia
// - FreeDOS calls country code 785 Middle-East,
// MS-DOS calls it Arabic South
// - Paragon PTS DOS uses country code 61 only for Australia
// - Paragon PTS DOS uses country code 88 for Taiwan
// - DOSes use country code 82 for Korea, despite country calling code
// 82 is assigned to South Korea
// FreeDOS also supports the following, not yet handled here:
// - Belgium/Dutch 40032
// - Belgium/French 41032
// - Belgium/German 42032
// - Spain/Spanish 40034
// - Spain/Catalan 41034
// - Spain/Gallegan 42034
// - Spain/Basque 43034
// - Switzerland/German 40041
// - Switzerland/French 41041
// - Switzerland/Italian 42041
enum class DosDateFormat : uint8_t {
MonthDayYear = 0,
DayMonthYear = 1,
YearMonthDay = 2,
};
enum class DosTimeFormat : uint8_t {
Time12H = 0, // AM/PM
Time24H = 1,
};
enum class DosCurrencyFormat : uint8_t {
SymbolAmount = 0,
AmountSymbol = 1,
SymbolSpaceAmount = 2,
AmountSpaceSymbol = 3,
// Some sources claim that bit 2 set means currency symbol should
// replace decimal point; so far it is unknown which (if any)
// COUNTRY.SYS uses this bit, most likely no DOS software uses it.
};
DosDateFormat DOS_GetLocaleDateFormat();
DosTimeFormat DOS_GetLocaleTimeFormat();
char DOS_GetLocaleDateSeparator();
char DOS_GetLocaleTimeSeparator();
char DOS_GetLocaleThousandsSeparator();
char DOS_GetLocaleDecimalSeparator();
char DOS_GetLocaleListSeparator();
#endif

View file

@ -27,6 +27,7 @@
#include "bios.h"
#include "callback.h"
#include "dos_locale.h"
#include "drives.h"
#include "mem.h"
#include "program_mount_common.h"
@ -52,146 +53,6 @@ void DOS_SetError(uint16_t code) {
dos.errorcode=code;
}
typedef struct CountryInfo {
Country country_number;
uint8_t date_format;
uint8_t date_separator;
uint8_t time_format;
uint8_t time_separator;
uint8_t thousands_separator;
uint8_t decimal_separator;
} CountryInfo;
static const CountryInfo& LookupCountryInfo(const uint16_t country_number) {
static constexpr uint8_t DATE_MDY = 0;
static constexpr uint8_t DATE_DMY = 1;
static constexpr uint8_t DATE_YMD = 2;
static constexpr uint8_t TIME_12H = 0;
static constexpr uint8_t TIME_24H = 1;
static constexpr uint8_t SEP_SPACE = 0x20; // ( )
static constexpr uint8_t SEP_APOST = 0x27; // (')
static constexpr uint8_t SEP_COMMA = 0x2c; // (,)
static constexpr uint8_t SEP_DASH = 0x2d; // (-)
static constexpr uint8_t SEP_PERIOD = 0x2e; // (.)
static constexpr uint8_t SEP_SLASH = 0x2f; // (/)
static constexpr uint8_t SEP_COLON = 0x3a; // (:)
// Values here reflect the current KDE/Linux system settings - they will probably not produce 100% same
// result as old MS-DOS systems, but should at least provide reasonably consistent user experience with
// certain host operating systems.
static constexpr CountryInfo COUNTRY_INFO[]= {
// | Date fmt | Date separ | Time fmt | Time separ | 1000 separ | Dec separ |
// { Country::International , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_PERIOD }, // C
{ Country::United_States , DATE_MDY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_US
{ Country::Canada_French , DATE_YMD , SEP_DASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fr_CA
{ Country::Latin_America , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // es_419
{ Country::Canada_English , DATE_YMD , SEP_DASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_CA
{ Country::Russia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // ru_RU
{ Country::South_Africa , DATE_YMD , SEP_DASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // af_ZA
{ Country::Greece , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // el_GR
{ Country::Netherlands , DATE_DMY , SEP_DASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // nl_NL
{ Country::Belgium , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fr_BE
{ Country::France , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fr_FR
{ Country::Spain , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_ES
{ Country::Hungary , DATE_YMD , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // hu_HU
{ Country::Yugoslavia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // sr_RS/sr_ME/hr_HR/sk_SK/bs_BA/mk_MK
{ Country::Italy , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // it_IT
{ Country::Romania , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // ro_RO
{ Country::Switzerland , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_APOST , SEP_PERIOD }, // ??_CH
{ Country::Czechia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // cs_CZ
{ Country::Austria , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // de_AT
{ Country::United_Kingdom , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_GB
// | Date fmt | Date separ | Time fmt | Time separ | 1000 separ | Dec separ |
{ Country::Denmark , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // da_DK
{ Country::Sweden , DATE_YMD , SEP_DASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // sv_SE
{ Country::Norway , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // nn_NO
{ Country::Poland , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // pl_PL
{ Country::Germany , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // de_DE
{ Country::Argentina , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_AR
{ Country::Brazil , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // pt_BR
{ Country::Chile , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_CL
{ Country::Colombia , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_CO
{ Country::Venezuela , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_VE
{ Country::Malaysia , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // ms_MY
{ Country::Australia , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_AU
{ Country::Philippines , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // fil_PH
{ Country::New_Zealand , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_NZ
{ Country::Singapore , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // ms_SG
{ Country::Kazakhstan , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // kk_KZ
{ Country::Japan , DATE_YMD , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // ja_JP
{ Country::Korea , DATE_YMD , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // ko_KR
{ Country::Vietnam , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // vi_VN
{ Country::China , DATE_YMD , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // zh_CN
// | Date fmt | Date separ | Time fmt | Time separ | 1000 separ | Dec separ
{ Country::Turkey , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // tr_TR
{ Country::India , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // hi_IN
{ Country::Niger , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fr_NE
{ Country::Benin , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fr_BJ
{ Country::Nigeria , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_NG
{ Country::Faroe_Islands , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // fo_FO
{ Country::Portugal , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // pt_PT
{ Country::Ireland , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_IE
{ Country::Iceland , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // is_IS
{ Country::Albania , DATE_DMY , SEP_PERIOD , TIME_12H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // sq_AL
{ Country::Malta , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // mt_MT
{ Country::Finland , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // fi_FI
{ Country::Bulgaria , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // bg_BG
{ Country::Lithuania , DATE_YMD , SEP_DASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // lt_LT
{ Country::Latvia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // lv_LV
{ Country::Estonia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // et_EE
{ Country::Armenia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // hy_AM
{ Country::Belarus , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // be_BY
{ Country::Ukraine , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // uk_UA
{ Country::Serbia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // sr_RS
// | Date fmt | Date separ | Time fmt | Time separ | 1000 separ | Dec separ |
{ Country::Montenegro , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // sr_ME
{ Country::Croatia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // hr_HR
{ Country::Slovenia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // sl_SI
{ Country::Bosnia_Herzegovina , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // bs_BA
{ Country::Macedonia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // mk_MK
{ Country::Slovakia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // sk_SK
{ Country::Ecuador , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // es_EC
{ Country::Arabic , DATE_DMY , SEP_PERIOD , TIME_12H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // ar_??
{ Country::Hong_Kong , DATE_DMY , SEP_SLASH , TIME_12H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // en_HK, zh_HK
{ Country::Taiwan , DATE_YMD , SEP_SLASH , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // zh_TW
{ Country::Israel , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // he_IL
{ Country::Mongolia , DATE_YMD , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_COMMA , SEP_PERIOD }, // mn_MN
{ Country::Tadjikistan , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // tg_TJ
{ Country::Turkmenistan , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // tk_TM
{ Country::Azerbaijan , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_PERIOD , SEP_COMMA }, // az_AZ
{ Country::Georgia , DATE_DMY , SEP_PERIOD , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // ka_GE
{ Country::Kyrgyzstan , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // ky_KG
{ Country::Uzbekistan , DATE_DMY , SEP_SLASH , TIME_24H , SEP_COLON , SEP_SPACE , SEP_COMMA }, // uz_UZ
// | Date fmt | Date separ | Time fmt | Time separ | 1000 separ | Dec separ |
};
for (const auto& country : COUNTRY_INFO) {
if (static_cast<uint16_t>(country.country_number) == country_number)
return country;
}
LOG_WARNING("DOS: No locale info for country %d", country_number);
return COUNTRY_INFO[0];
}
void DOS_SetCountry(uint16_t country_number)
{
if (dos.tables.country == nullptr)
return;
const auto country_info = LookupCountryInfo(country_number);
dos.tables.country[DOS_DATE_FORMAT_OFS] = country_info.date_format;
dos.tables.country[DOS_DATE_SEPARATOR_OFS] = country_info.date_separator;
dos.tables.country[DOS_TIME_FORMAT_OFS] = country_info.time_format;
dos.tables.country[DOS_TIME_SEPARATOR_OFS] = country_info.time_separator;
dos.tables.country[DOS_THOUSANDS_SEPARATOR_OFS] = country_info.thousands_separator;
dos.tables.country[DOS_DECIMAL_SEPARATOR_OFS] = country_info.decimal_separator;
}
uint16_t DOS_GetBiosTimePacked()
{
const auto ticks = mem_readd(BIOS_TIMER);
@ -793,16 +654,26 @@ static Bitu DOS_21Handler(void) {
};
LOG(LOG_MISC,LOG_ERROR)("DOS:0x37:Call for not supported switchchar");
break;
case 0x38: /* Set Country Code */
if (reg_al == 0) { /* Get country specific information */
case 0x38:
if (reg_dx == 0xffff) { /* Set Country Code */
// TODO: For unknown reason on modern DOSes (checked
// MS-DOS 6.22, PC DOS 2000 and DR DOS 7.03) this only
// works when setting the country to the one currently
// set - unknown, why
countryNo = (reg_al == 0xff) ? reg_bx : reg_al;
if (DOS_SetCountry(countryNo)) {
reg_ax = 0;
CALLBACK_SCF(false);
} else {
reg_ax = 0x02; // invalid country
CALLBACK_SCF(true);
}
} else { /* Get country specific information */
PhysPt dest = SegPhys(ds) + reg_dx;
MEM_BlockWrite(dest, dos.tables.country, 0x18);
reg_ax = reg_bx = 0x01;
CALLBACK_SCF(false);
} else { /* Set country code */
countryNo = reg_al == 0xff ? reg_bx : reg_al;
DOS_SetCountry(countryNo);
reg_ax = 0;
reg_bx = DOS_GetCountry();
reg_ah = 0;
reg_al = reg_bl;
CALLBACK_SCF(false);
}
break;

View file

@ -30,6 +30,7 @@ using sv = std::string_view;
#include "bios_disk.h"
#include "callback.h"
#include "dos_inc.h"
#include "dos_locale.h"
#include "drives.h"
#include "mapper.h"
#include "math_utils.h"
@ -37,10 +38,6 @@ using sv = std::string_view;
#include "setup.h"
#include "string_utils.h"
// The default codepage for DOS
constexpr uint16_t default_cp_437 = 437;
constexpr auto default_country = Country::United_States;
// A common pattern in the keyboard layout file is to try opening the requested
// file first within DOS, then from the local path, and finally from builtin
// resources. This function performs those in order and returns the first hit.
@ -181,6 +178,12 @@ void KeyboardLayout::ReadKeyboardFile(const int32_t specific_layout)
dos.loaded_codepage);
}
static void log_layout_read_error()
{
LOG_WARNING("DOS: Error reading keyboard layout file: '%s'",
strerror(errno));
}
static uint32_t read_kcl_file(const FILE_unique_ptr &kcl_file, const char *layout_id, bool first_id_only)
{
assert(kcl_file);
@ -195,7 +198,7 @@ static uint32_t read_kcl_file(const FILE_unique_ptr &kcl_file, const char *layou
const auto seek_pos = 7 + rbuf[6];
if (fseek(kcl_file.get(), seek_pos, SEEK_SET) != 0) {
LOG_WARNING("LAYOUT: could not seek to byte %d in keyboard layout file: %s", seek_pos, strerror(errno));
log_layout_read_error();
return 0;
}
@ -214,10 +217,7 @@ static uint32_t read_kcl_file(const FILE_unique_ptr &kcl_file, const char *layou
constexpr int8_t lang_codes_offset = -2;
if (fseek(kcl_file.get(), lang_codes_offset, SEEK_CUR) != 0) {
LOG_ERR("LAYOUT: could not seek to the language codes "
"at byte %d in keyboard layout: %s",
check_cast<int>(cur_pos) + lang_codes_offset,
strerror(errno));
log_layout_read_error();
return 0;
}
@ -252,9 +252,7 @@ static uint32_t read_kcl_file(const FILE_unique_ptr &kcl_file, const char *layou
}
}
if (fseek(kcl_file.get(), cur_pos + 3 + len, SEEK_SET) != 0) {
LOG_ERR("LAYOUT: could not seek to byte %d in keyboard layout file: %s",
check_cast<int>(cur_pos) + 3 + len,
strerror(errno));
log_layout_read_error();
return 0;
}
}
@ -319,8 +317,7 @@ KeyboardErrorCode KeyboardLayout::ReadKeyboardFile(const char *keyboard_file_nam
if (tempfile) {
const auto seek_pos = start_pos + 2;
if (fseek(tempfile.get(), seek_pos, SEEK_SET) != 0) {
LOG_WARNING("LAYOUT: could not seek to byte %d in keyboard layout file '%s': %s",
seek_pos, keyboard_file_name, strerror(errno));
log_layout_read_error();
return KEYB_INVALIDFILE;
}
read_buf_size = (uint32_t)fread(read_buf, sizeof(uint8_t),
@ -655,8 +652,9 @@ bool KeyboardLayout::SetMapKey(const uint8_t key, const uint16_t layouted_key,
uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
{
if (!strcmp(keyboard_file_name, "none"))
return default_cp_437;
if (!strcmp(keyboard_file_name, "none")) {
return DefaultCodePage437;
}
size_t read_buf_size = 0;
static uint8_t read_buf[65535];
@ -670,7 +668,7 @@ uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
if (!load_builtin_keyboard_layouts(keyboard_file_name, tempfile, start_pos)) {
LOG(LOG_BIOS, LOG_ERROR)
("Keyboard layout file %s not found", keyboard_file_name);
return default_cp_437;
return DefaultCodePage437;
}
if (tempfile) {
fseek(tempfile.get(), start_pos + 2, SEEK_SET);
@ -684,7 +682,7 @@ uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
if ((dr<4) || (read_buf[0]!=0x4b) || (read_buf[1]!=0x4c) || (read_buf[2]!=0x46)) {
LOG(LOG_BIOS, LOG_ERROR)
("Invalid keyboard layout file %s", keyboard_file_name);
return default_cp_437;
return DefaultCodePage437;
}
fseek(tempfile.get(), 0, SEEK_SET);
@ -692,7 +690,7 @@ uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
}
if (read_buf_size == 0) {
LOG_WARNING("CODEPAGE: Could not read data from layout file %s", keyboard_file_name);
return default_cp_437;
return DefaultCodePage437;
}
auto data_len = read_buf[start_pos++];
@ -707,7 +705,7 @@ uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
if (submappings >= ceil_udivide(sizeof(read_buf) - start_pos - 0x14, 8u)) {
LOG(LOG_BIOS, LOG_ERROR)
("Keyboard layout file %s is corrupt", keyboard_file_name);
return default_cp_437;
return DefaultCodePage437;
}
// check all submappings and use them if general submapping or same
@ -720,111 +718,7 @@ uint16_t KeyboardLayout::ExtractCodePage(const char *keyboard_file_name)
if (submap_cp!=0) return submap_cp;
}
return default_cp_437;
}
const char *get_builtin_cp_filename(const int codepage_id)
{
// reference:
// https://gitlab.com/FreeDOS/base/cpidos/-/blob/master/DOC/CPIDOS/CODEPAGE.TXT
switch (codepage_id) {
case 437:
case 850:
case 852:
case 853:
case 857:
case 858: return "EGA.CPX";
case 775:
case 859:
case 1116:
case 1117:
case 1118:
case 1119: return "EGA2.CPX";
case 771:
case 772:
case 808:
case 855:
case 866:
case 872: return "EGA3.CPX";
case 848:
case 849:
case 1125:
case 1131:
case 3012:
case 30010: return "EGA4.CPX";
case 113:
case 737:
case 851:
case 869: return "EGA5.CPX";
case 899:
case 30008:
case 58210:
case 59829:
case 60258:
case 60853: return "EGA6.CPX";
case 30011:
case 30013:
case 30014:
case 30017:
case 30018:
case 30019: return "EGA7.CPX";
case 770:
case 773:
case 774:
case 777:
case 778: return "EGA8.CPX";
case 860:
case 861:
case 863:
case 865:
case 867: return "EGA9.CPX";
case 667:
case 668:
case 790:
case 991:
case 3845: return "EGA10.CPX";
case 30000:
case 30001:
case 30004:
case 30007:
case 30009: return "EGA11.CPX";
case 30003:
case 30029:
case 30030:
case 58335: return "EGA12.CPX";
case 895:
case 30002:
case 58152:
case 59234:
case 62306: return "EGA13.CPX";
case 30006:
case 30012:
case 30015:
case 30016:
case 30020:
case 30021: return "EGA14.CPX";
case 30023:
case 30024:
case 30025:
case 30026:
case 30027:
case 30028: return "EGA15.CPX";
case 3021:
case 30005:
case 30022:
case 30031:
case 30032: return "EGA16.CPX";
case 862:
case 864:
case 30034:
case 30033:
case 30039:
case 30040: return "EGA17.CPX";
case 856:
case 3846:
case 3848: return "EGA18.CPI";
default: return ""; // none
}
return DefaultCodePage437;
}
KeyboardErrorCode KeyboardLayout::ReadCodePageFile(const char *requested_cp_filename, const int32_t codepage_id)
@ -836,7 +730,7 @@ KeyboardErrorCode KeyboardLayout::ReadCodePageFile(const char *requested_cp_file
return KEYB_NOERROR;
if (cp_filename == "auto") {
cp_filename = get_builtin_cp_filename(codepage_id);
cp_filename = DOS_GetBundledCodePageFileName(codepage_id);
if (cp_filename.empty()) {
LOG_WARNING("CODEPAGE: Could not find a file for codepage ID %d", codepage_id);
return KEYB_INVALIDCPFILE;
@ -1066,7 +960,9 @@ KeyboardErrorCode KeyboardLayout::ReadCodePageFile(const char *requested_cp_file
}
INT10_SetupRomMemoryChecksum();
// convert UTF-8 [autoexec] section to new code page
// re-create country information and [autoexec] section
// to match new code page
DOS_RefreshCountryInfo();
AUTOEXEC_NotifyNewCodePage();
return KEYB_NOERROR;
@ -1217,459 +1113,14 @@ KeyboardErrorCode DOS_SwitchKeyboardLayout(const char *new_layout, int32_t &trie
}
// get currently loaded layout name (nullptr if no layout is loaded)
const char* DOS_GetLoadedLayout(void) {
const char* DOS_GetLoadedLayout()
{
if (loaded_layout) {
return loaded_layout->GetLayoutName();
}
return nullptr;
}
static const std::map<std::string, Country> country_code_map{
// clang-format off
// reference: https://gitlab.com/FreeDOS/base/keyb_lay/-/blob/master/DOC/KEYB/LAYOUTS/LAYOUTS.TXT
{"ar462", Country::Arabic },
{"ar470", Country::Arabic },
{"az", Country::Azerbaijan },
{"ba", Country::Bosnia_Herzegovina },
{"be", Country::Belgium },
{"bg", Country::Bulgaria }, // 101-key
{"bg103", Country::Bulgaria }, // 101-key, Phonetic
{"bg241", Country::Bulgaria }, // 102-key
{"bl", Country::Belarus },
{"bn", Country::Benin },
{"br", Country::Brazil }, // ABNT layout
{"br274", Country::Brazil }, // US layout
{"bx", Country::Belgium }, // International
{"by", Country::Belarus },
{"ca", Country::Canada_English }, // Standard
{"ce", Country::Russia }, // Chechnya Standard
{"ce443", Country::Russia }, // Chechnya Typewriter
{"cg", Country::Montenegro },
{"cf", Country::Canada_French }, // Standard
{"cf445", Country::Canada_French }, // Dual-layer
{"co", Country::United_States }, // Colemak
{"cz", Country::Czechia }, // QWERTY
{"cz243", Country::Czechia }, // Standard
{"cz489", Country::Czechia }, // Programmers
{"de", Country::Germany }, // Standard
{"dk", Country::Denmark },
{"dv", Country::United_States }, // Dvorak
{"ee", Country::Estonia },
{"el", Country::Greece }, // 319
{"es", Country::Spain },
{"et", Country::Estonia },
{"fi", Country::Finland },
{"fo", Country::Faroe_Islands },
{"fr", Country::France }, // Standard
{"fx", Country::France }, // International
{"gk", Country::Greece }, // 319
{"gk220", Country::Greece }, // 220
{"gk459", Country::Greece }, // 101-key
{"gr", Country::Germany }, // Standard
{"gr453", Country::Germany }, // Dual-layer
{"hr", Country::Croatia },
{"hu", Country::Hungary }, // 101-key
{"hu208", Country::Hungary }, // 102-key
{"hy", Country::Armenia },
{"il", Country::Israel },
{"is", Country::Iceland }, // 101-key
{"is161", Country::Iceland }, // 102-key
{"it", Country::Italy }, // Standard
{"it142", Country::Italy }, // Comma on Numeric Pad
{"ix", Country::Italy }, // International
{"jp", Country::Japan },
{"ka", Country::Georgia },
{"kk", Country::Kazakhstan },
{"kk476", Country::Kazakhstan },
{"kx", Country::United_Kingdom }, // International
{"ky", Country::Kyrgyzstan },
{"la", Country::Latin_America },
{"lh", Country::United_States }, // Left-Hand Dvorak
{"lt", Country::Lithuania }, // Baltic
{"lt210", Country::Lithuania }, // 101-key, Programmers
{"lt211", Country::Lithuania }, // AZERTY
{"lt221", Country::Lithuania }, // Standard
{"lt456", Country::Lithuania }, // Dual-layout
{"lv", Country::Latvia }, // Standard
{"lv455", Country::Latvia }, // Dual-layout
{"ml", Country::Malta }, // UK-based
{"mk", Country::Macedonia },
{"mn", Country::Mongolia },
{"mo", Country::Mongolia },
{"mt", Country::Malta }, // UK-based
{"mt103", Country::Malta }, // US-based
{"ne", Country::Niger },
{"ng", Country::Nigeria },
{"nl", Country::Netherlands }, // 102-key
{"no", Country::Norway },
{"ph", Country::Philippines },
{"pl", Country::Poland }, // 101-key, Programmers
{"pl214", Country::Poland }, // 102-key
{"po", Country::Portugal },
{"px", Country::Portugal }, // International
{"ro", Country::Romania }, // Standard
{"ro446", Country::Romania }, // QWERTY
{"rh", Country::United_States }, // Right-Hand Dvorak
{"ru", Country::Russia }, // Standard
{"ru443", Country::Russia }, // Typewriter
{"rx", Country::Russia }, // Extended Standard
{"rx443", Country::Russia }, // Extended Typewriter
{"sd", Country::Switzerland }, // German
{"sf", Country::Switzerland }, // French
{"sg", Country::Switzerland }, // German
{"si", Country::Slovenia },
{"sk", Country::Slovakia },
{"sp", Country::Spain },
{"sq", Country::Albania }, // No-deadkeys
{"sq448", Country::Albania }, // Deadkeys
{"sr", Country::Serbia }, // Deadkey
{"su", Country::Finland },
{"sv", Country::Sweden },
{"sx", Country::Spain }, // International
{"tj", Country::Tadjikistan },
{"tm", Country::Turkmenistan },
{"tr", Country::Turkey }, // QWERTY
{"tr440", Country::Turkey }, // Non-standard
{"tt", Country::Russia }, // Tatarstan Standard
{"tt443", Country::Russia }, // Tatarstan Typewriter
{"ua", Country::Ukraine }, // 101-key
{"uk", Country::United_Kingdom }, // Standard
{"uk168", Country::United_Kingdom }, // Allternate
{"ur", Country::Ukraine }, // 101-key
{"ur465", Country::Ukraine }, // 101-key
{"ur1996", Country::Ukraine }, // 101-key
{"ur2001", Country::Ukraine }, // 102-key
{"ur2007", Country::Ukraine }, // 102-key
{"us", Country::United_States }, // Standard
{"ux", Country::United_States }, // International
{"uz", Country::Uzbekistan },
{"vi", Country::Vietnam },
{"yc", Country::Serbia }, // Deadkey
{"yc450", Country::Serbia }, // No-deadkey
{"yu", Country::Yugoslavia },
// clang-format on
};
static const std::map<std::string, const char *> language_to_layout_exception_map{
{"nl", "us"},
};
static bool lookup_country_from_code(const char *country_code, Country &country)
{
if (country_code) {
const auto it = country_code_map.find(country_code);
if (it != country_code_map.end()) {
country = it->second;
return true;
}
}
return false;
}
static const char *lookup_language_to_layout_exception(const char *language_code)
{
if (language_code) {
const auto it = language_to_layout_exception_map.find(language_code);
if (it != language_to_layout_exception_map.end())
return it->second;
}
return language_code;
}
uint16_t assert_codepage(const uint16_t codepage)
{
assert(!sv{get_builtin_cp_filename(codepage)}.empty());
return codepage;
}
[[maybe_unused]] constexpr uint16_t lookup_codepage_from_country(const Country country)
{
// grouped in ascending ordered by codepage value
switch (country) {
case Country::Australia:
case Country::China:
case Country::Hong_Kong:
case Country::India:
case Country::Ireland:
case Country::Japan:
case Country::Korea:
case Country::Malaysia:
case Country::New_Zealand:
case Country::Singapore:
case Country::South_Africa:
case Country::Taiwan:
case Country::United_Kingdom:
case Country::United_States: return assert_codepage(437);
case Country::Poland: return assert_codepage(668);
case Country::Lithuania: return assert_codepage(774);
case Country::Argentina:
case Country::Austria:
case Country::Belgium:
case Country::Canada_English:
case Country::Chile:
case Country::Colombia:
case Country::Ecuador:
case Country::Finland:
case Country::France:
case Country::Germany:
case Country::Italy:
case Country::Latin_America:
case Country::Mexico:
case Country::Netherlands:
case Country::Philippines:
case Country::Spain:
case Country::Sweden:
case Country::Switzerland:
case Country::Venezuela: return assert_codepage(850);
case Country::Albania:
case Country::Croatia:
case Country::Montenegro:
case Country::Romania:
case Country::Slovenia:
case Country::Turkmenistan: return assert_codepage(852);
case Country::Malta: return assert_codepage(853);
case Country::Bosnia_Herzegovina:
case Country::Bulgaria:
case Country::Macedonia:
case Country::Serbia:
case Country::Yugoslavia: return assert_codepage(855);
case Country::Turkey: return assert_codepage(857);
case Country::Brazil:
case Country::Portugal: return assert_codepage(860);
case Country::Faroe_Islands:
case Country::Iceland: return assert_codepage(861);
case Country::Israel: return assert_codepage(862);
case Country::Canada_French: return assert_codepage(863);
case Country::Arabic: return assert_codepage(864);
case Country::Denmark:
case Country::Norway: return assert_codepage(865);
case Country::Russia: return assert_codepage(866);
case Country::Czechia:
case Country::Slovakia: return assert_codepage(867);
case Country::Greece: return assert_codepage(869);
case Country::Armenia: return assert_codepage(899);
case Country::Estonia: return assert_codepage(1116);
case Country::Latvia: return assert_codepage(1117);
case Country::Ukraine: return assert_codepage(1125);
case Country::Belarus: return assert_codepage(1131);
case Country::Hungary: return assert_codepage(3845);
case Country::Tadjikistan: return assert_codepage(30002);
case Country::Nigeria: return assert_codepage(30005);
case Country::Vietnam: return assert_codepage(30006);
case Country::Benin: return assert_codepage(30027);
case Country::Niger: return assert_codepage(30028);
case Country::Kazakhstan:
case Country::Kyrgyzstan:
case Country::Mongolia: return assert_codepage(58152);
case Country::Azerbaijan: return assert_codepage(58210);
case Country::Georgia: return assert_codepage(59829);
case Country::Uzbekistan: return assert_codepage(62306);
default:
LOG_WARNING("LAYOUT: No default code page for country %d", static_cast<int>(country));
return assert_codepage(default_cp_437);
}
}
// Use OS-specific calls to extra the layout and from there convert it into a language
std::string get_lang_from_host_layout()
{
#if defined(WIN32)
# include <windows.h>
WORD cur_kb_layout = LOWORD(GetKeyboardLayout(0));
WORD cur_kb_sub_id = 0;
char layout_id_string[KL_NAMELENGTH];
auto parse_hex_string = [](const char *s) {
uint32_t value = 0;
sscanf(s, "%x", &value);
return value;
};
if (GetKeyboardLayoutName(layout_id_string)) {
if (safe_strlen(layout_id_string) == 8) {
const int cur_kb_layout_by_name = parse_hex_string(
(char *)&layout_id_string[4]);
layout_id_string[4] = 0;
const int sub_id = parse_hex_string(
(char *)&layout_id_string[0]);
if ((cur_kb_layout_by_name > 0) &&
(cur_kb_layout_by_name < 65536)) {
// use layout _id extracted from the layout string
cur_kb_layout = (WORD)cur_kb_layout_by_name;
}
if ((sub_id >= 0) && (sub_id < 100)) {
// use sublanguage ID extracted from the layout
// string
cur_kb_sub_id = (WORD)sub_id;
}
}
}
// try to match emulated keyboard layout with host-keyboardlayout
switch (cur_kb_layout) {
case 1025: // Saudi Arabia
case 1119: // Tamazight
case 1120: // Kashmiri
case 2049: // Iraq
case 3073: // Egypt
case 4097: // Libya
case 5121: // Algeria
case 6145: // Morocco
case 7169: // Tunisia
case 8193: // Oman
case 9217: // Yemen
case 10241: // Syria
case 11265: // Jordan
case 12289: // Lebanon
case 13313: // Kuwait
case 14337: // U.A.E
case 15361: // Bahrain
case 16385: // Qatar
return "ar462";
case 1026: return "bg"; // Bulgarian
case 1029: return "cz243"; // Czech
case 1030: return "dk"; // Danish
case 2055: // German - Switzerland
case 3079: // German - Austria
case 4103: // German - Luxembourg
case 5127: // German - Liechtenstein
case 1031: // German - Germany
return "gr";
case 1032: return "gk"; // Greek
case 1034: return "sp"; // Spanish - Spain (Traditional Sort)
case 1035: return "su"; // Finnish
case 1036: // French - France
case 2060: // French - Belgium
case 4108: // French - Switzerland
case 5132: // French - Luxembourg
case 6156: // French - Monaco
case 7180: // French - West Indies
case 8204: // French - Reunion
case 9228: // French - Democratic Rep. of Congo
case 10252: // French - Senegal
case 11276: // French - Cameroon
case 12300: // French - Cote d'Ivoire
case 13324: // French - Mali
case 14348: // French - Morocco
case 15372: // French - Haiti
case 58380: // French - North Africa
return "fr";
case 1037: return "il"; // Hebrew
case 1038: return cur_kb_sub_id ? "hu" : "hu208";
case 1039: return "is161"; // Icelandic
case 2064: // Italian - Switzerland
case 1040: // Italian - Italy
return "it";
case 3084: return "ca"; // French - Canada
case 1041: return "jp"; // Japanese
case 2067: // Dutch - Belgium
case 1043: // Dutch - Netherlands
return "nl";
case 1044: return "no"; // Norwegian (Bokmål)
case 1045: return "pl"; // Polish
case 1046: return "br"; // Portuguese - Brazil
case 2073: // Russian - Moldava
case 1049: // Russian
return "ru";
case 4122: // Croatian (Bosnia/Herzegovina)
case 1050: // Croatian
return "hr";
case 1051: return "sk"; // Slovak
case 1052: return "sq"; // Albanian - Albania
case 2077: // Swedish - Finland
case 1053: // Swedish
return "sv";
case 1055: return "tr"; // Turkish
case 1058: return "ur"; // Ukrainian
case 1059: return "bl"; // Belarusian
case 1060: return "si"; // Slovenian
case 1061: return "et"; // Estonian
case 1062: return "lv"; // Latvian
case 1063: return "lt"; // Lithuanian
case 1064: return "tj"; // Tajik
case 1066: return "vi"; // Vietnamese
case 1067: return "hy"; // Armenian - Armenia
case 1071: return "mk"; // F.Y.R.O. Macedonian
case 1079: return "ka"; // Georgian
case 2070: return "po"; // Portuguese - Portugal
case 2072: return "ro"; // Romanian - Moldava
case 5146: return "ba"; // Bosnian (Bosnia/Herzegovina)
case 2058: // Spanish - Mexico
case 3082: // Spanish - Spain (Modern Sort)
case 4106: // Spanish - Guatemala
case 5130: // Spanish - Costa Rica
case 6154: // Spanish - Panama
case 7178: // Spanish - Dominican Republic
case 8202: // Spanish - Venezuela
case 9226: // Spanish - Colombia
case 10250: // Spanish - Peru
case 11274: // Spanish - Argentina
case 12298: // Spanish - Ecuador
case 13322: // Spanish - Chile
case 14346: // Spanish - Uruguay
case 15370: // Spanish - Paraguay
case 16394: // Spanish - Bolivia
case 17418: // Spanish - El Salvador
case 18442: // Spanish - Honduras
case 19466: // Spanish - Nicaragua
case 20490: // Spanish - Puerto Rico
case 21514: // Spanish - United States
case 58378: // Spanish - Latin America
return "la";
}
#endif
return ""; // default to empty/US
}
// A helper that loads a layout given only a language
KeyboardErrorCode DOS_LoadKeyboardLayoutFromLanguage(const char * language_pref)
{
@ -1677,52 +1128,69 @@ KeyboardErrorCode DOS_LoadKeyboardLayoutFromLanguage(const char * language_pref)
// If a specific language wasn't provided, get it from setup
std::string language = language_pref;
if (language == "auto")
if (language == "auto") {
language = control->GetLanguage();
}
// TODO: This code mixes language code with keyboard layout; this should
// be cleaned up eventually, possibly we should use
// 'use get_language_from_os()' from 'cross.h'
// Does the language have a country associate with it?
auto country = default_country;
bool found_country = lookup_country_from_code(language.c_str(), country);
auto country = DOS_GetDefaultCountry();
bool found_country = DOS_GetCountryFromLayout(language, country);
// If we can't find a country for the language, try from the host
if (!found_country) {
language = get_lang_from_host_layout();
found_country = lookup_country_from_code(language.c_str(), country);
language = DOS_GetLayoutFromHost();
found_country = DOS_GetCountryFromLayout(language, country);
}
// Inform the user if we couldn't find a valid country
if (!language.empty() && !found_country) {
LOG_WARNING("LAYOUT: A country could not be found for the language: %s",
LOG_WARNING("DOS: A country could not be found for the language: %s",
language.c_str());
}
// Regardless of the above, carry on with setting up the layout
const auto codepage = lookup_codepage_from_country(country);
const auto layout = lookup_language_to_layout_exception(language.c_str());
const auto result = DOS_LoadKeyboardLayout(layout, codepage, "auto");
const auto codepage = DOS_GetCodePageFromCountry(country);
const auto layout = DOS_CheckLanguageToLayoutException(language);
const auto result = DOS_LoadKeyboardLayout(layout.c_str(), codepage, "auto");
if (result == KEYB_NOERROR) {
LOG_MSG("LAYOUT: Loaded codepage %d for detected language %s", codepage, language.c_str());
} else if (country != default_country) {
LOG_WARNING("LAYOUT: Failed loading codepage %d for detected language %s", codepage, language.c_str());
LOG_MSG("DOS: Loaded codepage %d for detected language '%s'",
codepage,
language.c_str());
} else if (country != DOS_GetDefaultCountry()) {
LOG_WARNING("DOS: Failed loading codepage %d for detected language '%s'",
codepage,
language.c_str());
}
return result;
}
class DOS_KeyboardLayout final : public Module_base {
public:
DOS_KeyboardLayout(Section* configuration):Module_base(configuration){
Section_prop * section=static_cast<Section_prop *>(configuration);
DOS_KeyboardLayout(Section* configuration) : Module_base(configuration)
{
Section_prop* section = static_cast<Section_prop*>(configuration);
assert(section);
dos.loaded_codepage = default_cp_437; // US codepage already initialized
// US codepage already initialized
dos.loaded_codepage = DefaultCodePage437;
loaded_layout = std::make_unique<KeyboardLayout>();
std::string layoutname = section->Get_string("keyboardlayout");
// If the use only provided a single value (language), then try using it
constexpr bool reason_keyboard_layout = true;
const auto layout_is_one_value = sv(layoutname).find(' ') == std::string::npos;
if (layout_is_one_value) {
if (!DOS_LoadKeyboardLayoutFromLanguage(layoutname.c_str())) {
return; // success
// Success - re-create country information to
// match new keyboard layout
DOS_RefreshCountryInfo(reason_keyboard_layout);
return;
}
}
// Otherwise use the layout to get the codepage
@ -1731,24 +1199,30 @@ public:
if (loaded_layout->ReadKeyboardFile(layoutname.c_str(), dos.loaded_codepage)) {
if (strncmp(layoutname.c_str(), "auto", 4)) {
LOG_ERR("LAYOUT: Failed to load keyboard layout %s",
LOG_ERR("DOS: Failed to load keyboard layout '%s'",
layoutname.c_str());
}
} else {
const char *lcode = loaded_layout->GetMainLanguageCode();
if (lcode) {
LOG_MSG("LAYOUT: DOS keyboard layout loaded with main language code %s for layout %s",lcode,layoutname.c_str());
LOG_MSG("DOS: Loaded keyboard layout '%s' with main language code '%s'",
layoutname.c_str(),
lcode);
}
}
// Convert UTF-8 [autoexec] section to new code page
// Re-create country information and [autoexec] section
// to match new code page and keyboard layout
DOS_RefreshCountryInfo(reason_keyboard_layout);
AUTOEXEC_NotifyNewCodePage();
}
~DOS_KeyboardLayout(){
if ((dos.loaded_codepage != default_cp_437) && (CurMode->type == M_TEXT)) {
~DOS_KeyboardLayout()
{
if ((dos.loaded_codepage != DefaultCodePage437) &&
(CurMode->type == M_TEXT)) {
INT10_ReloadRomFonts();
dos.loaded_codepage = default_cp_437; // US codepage
dos.loaded_codepage = DefaultCodePage437; // US codepage
}
if (loaded_layout) {
loaded_layout.reset();
@ -1762,28 +1236,6 @@ void DOS_KeyboardLayout_ShutDown(Section* /*sec*/) {
KeyboardLayout.reset();
}
void DOS_SetCountry(uint16_t countryNo);
static void set_country_from_pref(const int country_pref)
{
// default to the US
auto country_number = static_cast<uint16_t>(default_country);
// If the country preference is valid, use it
if (country_pref > 0) {
country_number = static_cast<uint16_t>(country_pref);
} else if (const auto country_code = DOS_GetLoadedLayout(); country_code) {
if (Country c; lookup_country_from_code(country_code, c)) {
country_number = static_cast<uint16_t>(c);
} else {
LOG_ERR("LANGUAGE: The layout's country code: '%s' does not have a corresponding country",
country_code);
}
}
DOS_SetCountry(country_number);
}
void DOS_KeyboardLayout_Init(Section *sec)
{
assert(sec);
@ -1791,7 +1243,4 @@ void DOS_KeyboardLayout_Init(Section *sec)
constexpr auto changeable_at_runtime = true;
sec->AddDestroyFunction(&DOS_KeyboardLayout_ShutDown, changeable_at_runtime);
const auto settings = static_cast<const Section_prop *>(sec);
set_country_from_pref(settings->Get_int("country"));
}

2908
src/dos/dos_locale.cpp Normal file

File diff suppressed because it is too large Load diff

59
src/dos/dos_locale.h Normal file
View file

@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2020-2023 The DOSBox Staging Team
* Copyright (C) 2002-2021 The DOSBox Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef DOSBOX_DOS_LOCALE_H
#define DOSBOX_DOS_LOCALE_H
#include "setup.h"
#include <cstdint>
#include <string>
constexpr uint16_t DefaultCodePage437 = 437;
std::string DOS_GenerateListCountriesMessage();
bool DOS_SetCountry(const uint16_t country_id);
uint16_t DOS_GetCountry();
void DOS_RefreshCountryInfo(const bool reason_keyboard_layout = false);
std::string DOS_GetBundledCodePageFileName(const uint16_t code_page);
uint16_t DOS_GetCodePageFromCountry(const uint16_t country);
std::string DOS_CheckLanguageToLayoutException(const std::string& language_code);
uint16_t DOS_GetDefaultCountry();
bool DOS_GetCountryFromLayout(const std::string& layout, uint16_t& country);
std::string DOS_GetLayoutFromHost();
// Lifecycle
void DOS_Locale_Init(Section* sec);
// We need a separate function to support '--list-countries' command line switch
// (and possibly others in the future) - it needs translated strings, but does
// not initialize DOSBox fully.
void DOS_Locale_AddMessages();
#endif

View file

@ -9,6 +9,7 @@ libdos_sources = files(
'dos_files.cpp',
'dos_ioctl.cpp',
'dos_keyboard_layout.cpp',
'dos_locale.cpp',
'dos_memory.cpp',
'dos_misc.cpp',
'dos_mscdex.cpp',

View file

@ -36,6 +36,7 @@
#include "cpu.h"
#include "cross.h"
#include "debug.h"
#include "dos/dos_locale.h"
#include "dos_inc.h"
#include "hardware.h"
#include "inout.h"
@ -1154,11 +1155,22 @@ void DOSBOX_Init()
// DOS locale settings
pint = secprop->Add_int("country", when_idle, 0);
pint->Set_help("Set DOS country code (0 by default).\n"
"This affects country-specific information such as date, time, and decimal\n"
"formats. If set to 0, the country code corresponding to the selected keyboard\n"
"layout will be used.");
secprop->AddInitFunction(&DOS_Locale_Init, changeable_at_runtime);
pstring = secprop->Add_string("locale_period", when_idle, "modern");
pstring->Set_help(
"Set locale epoch ('modern' by default). Historic settings (if available\n"
"for the given country) try to mimic old DOS behaviour when displaying\n"
"information such as dates, time, or numbers, modern ones follow current day\n"
"practices for user experience more consistent with typical host systems.");
pstring->Set_values({"historic", "modern"});
pstring = secprop->Add_string("country", when_idle, "auto");
pstring->Set_help(
"Set DOS country code ('auto' by default).\n"
"This affects country-specific information such as date, time, and decimal\n"
"formats. The list of supported country codes can be displayed using\n"
"'--list-countries' command-line argument. If set to 'auto', the country code\n"
"corresponding to the selected keyboard layout will be used.");
secprop->AddInitFunction(&DOS_KeyboardLayout_Init, changeable_at_runtime);
pstring = secprop->Add_string("keyboardlayout", when_idle, "auto");

View file

@ -49,6 +49,7 @@
#endif
#include "../capture/capture.h"
#include "../dos/dos_locale.h"
#include "../ints/int10.h"
#include "control.h"
#include "cpu.h"
@ -4115,7 +4116,7 @@ static std::vector<std::string> get_sdl_texture_renderers()
return drivers;
}
static void messages_add_sdl()
static void messages_add_command_line()
{
MSG_Add("DOSBOX_HELP",
"Usage: %s [OPTION]... [PATH]\n"
@ -4151,8 +4152,11 @@ static void messages_add_sdl()
" --working-dir <path> Set working directory to <path>. DOSBox will act as if\n"
" started from this directory.\n"
"\n"
" --list-countries List all supported countries with their numeric codes.\n"
" Codes are to be used in the 'country' config setting.\n"
"\n"
" --list-glshaders List all available OpenGL shaders and their paths.\n"
" Results are useable in the 'glshader = ' config setting.\n"
" Shaders are to be used in the 'glshader' config setting.\n"
"\n"
" --fullscreen Start in fullscreen mode.\n"
"\n"
@ -4183,7 +4187,10 @@ static void messages_add_sdl()
" -h, -?, --help Print help message and exit.\n"
"\n"
" -V, --version Print version information and exit.\n");
}
static void messages_add_sdl()
{
MSG_Add("PROGRAM_CONFIG_PROPERTY_ERROR", "No such section or property: %s\n");
MSG_Add("PROGRAM_CONFIG_NO_PROPERTY",
@ -4484,22 +4491,25 @@ void Restart(bool pressed) { // mapper handler
restart_program(control->startup_params);
}
static int list_glshaders()
static void list_glshaders()
{
#if C_OPENGL
for (const auto& line : RENDER_GenerateShaderInventoryMessage()) {
printf("%s\n", line.c_str());
printf_utf8("%s\n", line.c_str());
}
return 0;
#else
fprintf(stderr,
"OpenGL is not supported by this executable "
"and is missing the functionality to list shaders.");
return 1;
#endif
}
static void list_countries()
{
const auto message_utf8 = DOS_GenerateListCountriesMessage();
printf_utf8("%s\n", message_utf8.c_str());
}
static int print_primary_config_location()
{
const auto path = GetPrimaryConfigPath();
@ -4591,7 +4601,7 @@ int sdl_main(int argc, char* argv[])
loguru::g_preamble_pipe = true;
if (arguments->version || arguments->help || arguments->printconf ||
arguments->editconf || arguments->eraseconf ||
arguments->editconf || arguments->eraseconf || arguments->list_countries ||
arguments->list_glshaders || arguments->erasemapper) {
loguru::g_stderr_verbosity = loguru::Verbosity_WARNING;
}
@ -4630,7 +4640,10 @@ int sdl_main(int argc, char* argv[])
//
InitConfigDir();
// Register sdlmain's messages and conf sections
// Register sdlmain's messages, conf sections, and essential
// DOS messages, needed by some command line switches
messages_add_command_line();
DOS_Locale_AddMessages();
messages_add_sdl();
config_add_sdl();
@ -4679,18 +4692,7 @@ int sdl_main(int argc, char* argv[])
const auto program_name = argv[0];
const auto help_utf8 = format_string(MSG_GetRaw("DOSBOX_HELP"),
program_name);
#ifdef WIN32
const auto old_code_page = GetConsoleOutputCP();
constexpr uint16_t CodePageUtf8 = 65001;
SetConsoleOutputCP(CodePageUtf8);
printf("%s", help_utf8.c_str());
SetConsoleOutputCP(old_code_page);
#else
// Assume any OS without special support uses UTF-8 as
// console encoding
printf("%s", help_utf8.c_str());
#endif
printf_utf8("%s", help_utf8.c_str());
return 0;
}
if (arguments->editconf) {
@ -4705,8 +4707,13 @@ int sdl_main(int argc, char* argv[])
if (arguments->printconf) {
return print_primary_config_location();
}
if (arguments->list_countries) {
list_countries();
return 0;
}
if (arguments->list_glshaders) {
return list_glshaders();
list_glshaders();
return 0;
}
// Can't disable the console with debugger enabled

View file

@ -1773,6 +1773,7 @@ void Config::ParseArguments()
arguments.noprimaryconf = cmdline->FindRemoveBoolArgument("noprimaryconf");
arguments.nolocalconf = cmdline->FindRemoveBoolArgument("nolocalconf");
arguments.fullscreen = cmdline->FindRemoveBoolArgument("fullscreen");
arguments.list_countries = cmdline->FindRemoveBoolArgument("list-countries");
arguments.list_glshaders = cmdline->FindRemoveBoolArgument("list-glshaders");
arguments.noconsole = cmdline->FindRemoveBoolArgument("noconsole");
arguments.startmapper = cmdline->FindRemoveBoolArgument("startmapper");

View file

@ -42,6 +42,7 @@
#include "callback.h"
#include "control.h"
#include "cross.h"
#include "dos_inc.h"
#include "drives.h"
#include "paging.h"
#include "regs.h"
@ -557,7 +558,7 @@ std::string format_number(const size_t num)
std::string buffer = {};
std::string separator = " ";
separator[0] = dos.tables.country[DOS_THOUSANDS_SEPARATOR_OFS];
separator[0] = DOS_GetLocaleThousandsSeparator();
auto tmp = num;
while (tmp) {
@ -694,19 +695,19 @@ char *format_date(const uint16_t year, const uint8_t month, const uint8_t day)
{
char format_string[6];
static char return_date_buffer[15] = {0};
const char date_format = dos.tables.country[DOS_DATE_FORMAT_OFS];
const char date_separator = dos.tables.country[DOS_DATE_SEPARATOR_OFS];
const auto date_format = DOS_GetLocaleDateFormat();
const char date_separator = DOS_GetLocaleDateSeparator();
int result;
switch (date_format) {
case 1:
case DosDateFormat::DayMonthYear:
result = sprintf(format_string, "D%cM%cY", date_separator,
date_separator);
break;
case 2:
case DosDateFormat::YearMonthDay:
result = sprintf(format_string, "Y%cM%cD", date_separator,
date_separator);
break;
default:
default: // DosDateFormat::MonthDayYear
result = sprintf(format_string, "M%cD%cY", date_separator,
date_separator);
}
@ -747,16 +748,15 @@ char* format_time(const uint8_t hour, const uint8_t min, const uint8_t sec,
uint8_t fhour = hour;
static char return_time_buffer[19] = {0};
char ampm[3] = "";
char time_format = dos.tables.country[DOS_TIME_FORMAT_OFS];
if (!time_format) { // 12 hour notation?
if (DOS_GetLocaleTimeFormat() == DosTimeFormat::Time12H) {
if (fhour != 12)
fhour %= 12;
strcpy(ampm, hour != 12 && hour == fhour ? "am" : "pm");
if (!full)
*(ampm + 1) = 0; // "a" or "p" in short time format
}
const char time_separator = dos.tables.country[DOS_TIME_SEPARATOR_OFS];
const char decimal_separator = dos.tables.country[DOS_DECIMAL_SEPARATOR_OFS];
const char time_separator = DOS_GetLocaleTimeSeparator();
const char decimal_separator = DOS_GetLocaleDecimalSeparator();
if (full) // Example full time format: 1:02:03.04am
safe_sprintf(return_time_buffer, "%u%c%02u%c%02u%c%02u%s",
(unsigned int)fhour, time_separator,
@ -1675,20 +1675,20 @@ void DOS_Shell::CMD_CALL(char * args){
void DOS_Shell::CMD_DATE(char *args)
{
const char date_format = dos.tables.country[DOS_DATE_FORMAT_OFS];
const char date_separator = dos.tables.country[DOS_DATE_SEPARATOR_OFS];
const auto date_format = DOS_GetLocaleDateFormat();
const char date_separator = DOS_GetLocaleDateSeparator();
char format[11];
int result;
switch (date_format) {
case 1:
case DosDateFormat::DayMonthYear:
result = safe_sprintf(format, "DD%cMM%cYYYY", date_separator,
date_separator);
break;
case 2:
case DosDateFormat::YearMonthDay:
result = safe_sprintf(format, "YYYY%cMM%cDD", date_separator,
date_separator);
break;
default:
default: // DosDateFormat::MonthDayYear
result = safe_sprintf(format, "MM%cDD%cYYYY", date_separator,
date_separator);
}
@ -1723,15 +1723,15 @@ void DOS_Shell::CMD_DATE(char *args)
char date_separator_placeholder_1, date_separator_placeholder_2;
int n;
switch (date_format) {
case 1:
case DosDateFormat::DayMonthYear:
n = sscanf(args, "%u%c%u%c%u", &newday, &date_separator_placeholder_1,
&newmonth, &date_separator_placeholder_2, &newyear);
break;
case 2:
case DosDateFormat::YearMonthDay:
n = sscanf(args, "%u%c%u%c%u", &newyear, &date_separator_placeholder_1,
&newmonth, &date_separator_placeholder_2, &newday);
break;
default:
default: // DosDateFormat::MonthDayYear
n = sscanf(args, "%u%c%u%c%u", &newmonth,
&date_separator_placeholder_1, &newday,
&date_separator_placeholder_2, &newyear);
@ -1780,7 +1780,7 @@ void DOS_Shell::CMD_DATE(char *args)
void DOS_Shell::CMD_TIME(char * args) {
char format[9], example[9];
const char time_separator = dos.tables.country[DOS_TIME_SEPARATOR_OFS];
const char time_separator = DOS_GetLocaleTimeSeparator();
sprintf(format, "hh%cmm%css", time_separator, time_separator);
sprintf(example, "13%c14%c15", time_separator, time_separator);
if (ScanCMDBool(args, "?")) {

View file

@ -547,6 +547,7 @@ IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0</Command>
<ClCompile Include="..\src\dos\dos_files.cpp" />
<ClCompile Include="..\src\dos\dos_ioctl.cpp" />
<ClCompile Include="..\src\dos\dos_keyboard_layout.cpp" />
<ClCompile Include="..\src\dos\dos_locale.cpp" />
<ClCompile Include="..\src\dos\dos_memory.cpp" />
<ClCompile Include="..\src\dos\dos_misc.cpp" />
<ClCompile Include="..\src\dos\dos_mscdex.cpp" />
@ -855,6 +856,7 @@ IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0</Command>
<ClInclude Include="..\src\debug\debug_inc.h" />
<ClInclude Include="..\src\dos\cdrom.h" />
<ClInclude Include="..\src\dos\dev_con.h" />
<ClInclude Include="..\src\dos\dos_locale.h" />
<ClInclude Include="..\src\dos\dos_mscdex.h" />
<ClInclude Include="..\src\dos\program_autotype.h" />
<ClInclude Include="..\src\dos\program_ls.h" />

View file

@ -97,6 +97,9 @@
<ClCompile Include="..\src\dos\dos_keyboard_layout.cpp">
<Filter>src\dos</Filter>
</ClCompile>
<ClCompile Include="..\src\dos\dos_locale.cpp">
<Filter>src\dos</Filter>
</ClCompile>
<ClCompile Include="..\src\dos\dos_memory.cpp">
<Filter>src\dos</Filter>
</ClCompile>