1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/dos32krnl/country.c
5  * PURPOSE:         DOS32 Country support
6  * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
7  *
8  * NOTE:            Support for default (english) language only.
9  *                  For other languages, please use COUNTRY.SYS
10  */
11 
12 /* INCLUDES *******************************************************************/
13 
14 #include "ntvdm.h"
15 
16 #define NDEBUG
17 #include <debug.h>
18 
19 #include "emulator.h"
20 
21 #include "country.h"
22 #include "memory.h"
23 
24 /* PRIVATE VARIABLES **********************************************************/
25 
26 /* CaseMap routine: should call INT 65h, AL=20h */
27 // ATM, just do nothing.
28 static const BYTE CaseMapRoutine[] =
29 {
30     0xCB // retf
31 };
32 
33 #pragma pack(push, 1)
34 
35 #define DATATABLE(name, type, len)   \
36     typedef struct _##name      \
37     {                           \
38         WORD Size;              \
39         type Data[(len)];       \
40     } name
41 
42 DATATABLE(UPPERCASE, CHAR, 0xFF-0x80+1);
43 DATATABLE(LOWERCASE, CHAR, 0xFF-0x00+1);
44 DATATABLE(FNAMETERM, BYTE,          22);
45 DATATABLE(COLLATE  , BYTE, 0xFF-0x00+1);
46 DATATABLE(DBCSLEAD , WORD,      0x00+1);
47 
48 typedef struct _COUNTRY_DATA
49 {
50     BYTE      CaseMapRoutine[sizeof(CaseMapRoutine)];
51     UPPERCASE UpCaseTbl;    // Used also for filename uppercase
52     LOWERCASE LoCaseTbl;
53     FNAMETERM FNameTermTbl;
54     COLLATE   CollateTbl;
55     DBCSLEAD  DBCSLeadTbl;
56 } COUNTRY_DATA, *PCOUNTRY_DATA;
57 
58 #pragma pack(pop)
59 
60 /* Global data contained in guest memory */
61 static WORD CountryDataSegment;
62 static PCOUNTRY_DATA CountryData;
63 
64 WORD YesNoTable[2] = { MAKEWORD('Y', 0), MAKEWORD('N', 0) };
65 
66 /*
67  * See: http://www.ctyme.com/intr/rb-3163.htm#Table1754
68  *      http://www.ctyme.com/intr/rb-3164.htm
69  *      http://www.ctyme.com/intr/rb-3166.htm
70  */
71 
72 /* PRIVATE FUNCTIONS **********************************************************/
73 
74 /* PUBLIC FUNCTIONS ***********************************************************/
75 
76 WORD
DosGetCountryInfo(IN OUT PWORD CountryId,OUT PDOS_COUNTRY_INFO CountryInfo)77 DosGetCountryInfo(IN OUT PWORD CountryId,
78                   OUT PDOS_COUNTRY_INFO CountryInfo)
79 {
80     INT Return;
81     DWORD NumVal;
82 
83     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_IDATE | LOCALE_RETURN_NUMBER, // LOCALE_ILDATE | LOCALE_RETURN_NUMBER
84                             (LPSTR)&NumVal,
85                             sizeof(NumVal));
86     if (Return == 0) return LOWORD(GetLastError());
87     CountryInfo->DateTimeFormat = (WORD)NumVal;
88 
89     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY,
90                             (LPSTR)&CountryInfo->CurrencySymbol,
91                             sizeof(CountryInfo->CurrencySymbol));
92     if (Return == 0) return LOWORD(GetLastError());
93 
94     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,
95                             (LPSTR)&CountryInfo->ThousandSep,
96                             sizeof(CountryInfo->ThousandSep));
97     if (Return == 0) return LOWORD(GetLastError());
98 
99     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,
100                             (LPSTR)&CountryInfo->DecimalSep,
101                             sizeof(CountryInfo->DecimalSep));
102     if (Return == 0) return LOWORD(GetLastError());
103 
104     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDATE,
105                             (LPSTR)&CountryInfo->DateSep,
106                             sizeof(CountryInfo->DateSep));
107     if (Return == 0) return LOWORD(GetLastError());
108 
109     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STIME,
110                             (LPSTR)&CountryInfo->TimeSep,
111                             sizeof(CountryInfo->TimeSep));
112     if (Return == 0) return LOWORD(GetLastError());
113 
114     // NOTE: '4: Symbol replace decimal separator' is unsupported.
115     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ICURRENCY | LOCALE_RETURN_NUMBER,
116                             (LPSTR)&NumVal,
117                             sizeof(NumVal));
118     if (Return == 0) return LOWORD(GetLastError());
119     CountryInfo->CurrencyFormat = (BYTE)NumVal;
120 
121     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ICURRDIGITS | LOCALE_RETURN_NUMBER, // LOCALE_IDIGITS | LOCALE_RETURN_NUMBER
122                             (LPSTR)&NumVal,
123                             sizeof(NumVal));
124     if (Return == 0) return LOWORD(GetLastError());
125     CountryInfo->CurrencyDigitsNum = (BYTE)NumVal;
126 
127     Return = GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_ITIME | LOCALE_RETURN_NUMBER,
128                             (LPSTR)&NumVal,
129                             sizeof(NumVal));
130     if (Return == 0) return LOWORD(GetLastError());
131     CountryInfo->TimeFormat = (BYTE)NumVal;
132 
133     CountryInfo->CaseMapPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, CaseMapRoutine), CountryDataSegment);
134 
135     // CountryInfo->DataListSep;
136 
137     return ERROR_SUCCESS;
138 }
139 
140 WORD
DosGetCountryInfoEx(IN BYTE InfoId,IN WORD CodePage,IN WORD CountryId,OUT PDOS_COUNTRY_INFO_2 CountryInfo,IN OUT PWORD BufferSize)141 DosGetCountryInfoEx(IN BYTE InfoId,
142                     IN WORD CodePage,
143                     IN WORD CountryId,
144                     OUT PDOS_COUNTRY_INFO_2 CountryInfo,
145                     IN OUT PWORD BufferSize)
146 {
147     // FIXME: use: CodePage; CountryId
148     // FIXME: Check BufferSize
149     // FIXME: Use NLSFUNC resident?
150 
151     switch (InfoId)
152     {
153         /* Get General Internationalization Info (similar to AX=3800h) */
154         case 0x01:
155         {
156             WORD ErrorCode;
157             ErrorCode = DosGetCountryInfo(&CountryId,
158                                           &CountryInfo->CountryInfoEx.CountryInfo);
159             if (ErrorCode != ERROR_SUCCESS) return ErrorCode;
160             CountryInfo->CountryInfoEx.Size = sizeof(CountryInfo->CountryInfoEx);
161             CountryInfo->CountryInfoEx.CountryId = CountryId;
162             // CountryInfo->CodePage;
163             break;
164         }
165 
166         /* Get Pointer to Uppercase Table */
167         case 0x02:
168             CountryInfo->UpCaseTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, UpCaseTbl), CountryDataSegment);
169             break;
170 
171         /* Get Pointer to Lowercase Table */
172         case 0x03:
173             CountryInfo->LoCaseTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, LoCaseTbl), CountryDataSegment);
174             break;
175 
176         /* Get Pointer to Filename Uppercase Table */
177         case 0x04:
178             CountryInfo->FNameUpCaseTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, UpCaseTbl), CountryDataSegment);
179             break;
180 
181         /* Get Pointer to Filename Terminator Table */
182         case 0x05:
183             CountryInfo->FNameTermTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, FNameTermTbl), CountryDataSegment);
184             break;
185 
186         /* Get Pointer to Collating Sequence Table */
187         case 0x06:
188             CountryInfo->CollateTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, CollateTbl), CountryDataSegment);
189             break;
190 
191         /* Get Pointer to Double-Byte Character Set Table */
192         case 0x07:
193             CountryInfo->DBCSLeadTblPtr = MAKELONG(FIELD_OFFSET(COUNTRY_DATA, DBCSLeadTbl), CountryDataSegment);
194             break;
195 
196         default:
197             return ERROR_CALL_NOT_IMPLEMENTED;
198     }
199     CountryInfo->InfoId = InfoId;
200 
201     return ERROR_SUCCESS;
202 }
203 
DosIfCharYesNo(WORD Char)204 WORD DosIfCharYesNo(WORD Char)
205 {
206     Char = toupper(Char);
207 
208     /* NO-type */
209     if (Char == YesNoTable[1])
210         return 0x0000;
211     /* YES-type */
212     if (Char == YesNoTable[0])
213         return 0x0001;
214     /* Unknown type */
215         return 0x0002;
216 }
217 
DosToUpper(CHAR Char)218 CHAR DosToUpper(CHAR Char)
219 {
220     // FIXME: Use the current locale
221     return toupper(Char);
222 }
223 
DosToUpperStrN(PCHAR DestStr,PCHAR SrcStr,WORD Length)224 VOID DosToUpperStrN(PCHAR DestStr, PCHAR SrcStr, WORD Length)
225 {
226     while (Length-- > 0)
227         *DestStr++ = toupper(*SrcStr++);
228 }
229 
DosToUpperStrZ(PSTR DestStr,PSTR SrcStr)230 VOID DosToUpperStrZ(PSTR DestStr, PSTR SrcStr)
231 {
232     while (*SrcStr)
233         *DestStr++ = toupper(*SrcStr++);
234 }
235 
DosCountryInitialize(VOID)236 BOOLEAN DosCountryInitialize(VOID)
237 {
238     UINT i;
239 
240     /* Initialize some memory to store country information */
241     // FIXME: Can we use instead some static area from the DOS data structure??
242     CountryDataSegment = DosAllocateMemory(sizeof(COUNTRY_DATA), NULL);
243     if (CountryDataSegment == 0) return FALSE;
244     CountryData = (PCOUNTRY_DATA)SEG_OFF_TO_PTR(CountryDataSegment, 0x0000);
245 
246     RtlMoveMemory(CountryData->CaseMapRoutine,
247                   CaseMapRoutine,
248                   sizeof(CaseMapRoutine));
249 
250     CountryData->UpCaseTbl.Size = ARRAYSIZE(CountryData->UpCaseTbl.Data);
251     for (i = 0; i < CountryData->UpCaseTbl.Size; ++i)
252         CountryData->UpCaseTbl.Data[i] = 0x80 + i;
253 
254     CountryData->LoCaseTbl.Size = ARRAYSIZE(CountryData->LoCaseTbl.Data);
255     for (i = 0; i < CountryData->LoCaseTbl.Size; ++i)
256         CountryData->LoCaseTbl.Data[i] = i;
257 
258     CountryData->FNameTermTbl.Size = ARRAYSIZE(CountryData->FNameTermTbl.Data);
259     CountryData->FNameTermTbl.Data[ 0] = 0x01; // Dummy Byte
260     CountryData->FNameTermTbl.Data[ 1] = 0x00; //  Lowest permissible Character Value for Filename
261     CountryData->FNameTermTbl.Data[ 2] = 0xFF; // Highest permissible Character Value for Filename
262     CountryData->FNameTermTbl.Data[ 3] = 0x00; // Dummy Byte
263     CountryData->FNameTermTbl.Data[ 4] = 0x00; // First excluded Character in Range \ all characters in this
264     CountryData->FNameTermTbl.Data[ 5] = 0x20; //  Last excluded Character in Range / range are illegal
265     CountryData->FNameTermTbl.Data[ 6] = 0x02; // Dummy Byte
266     CountryData->FNameTermTbl.Data[ 7] = 14;   // Number of illegal (terminator) Characters
267 //  CountryData->FNameTermTbl.Data[ 8] = ".\"/\\[]:|<>+=;,"; // Characters which terminate a Filename
268     CountryData->FNameTermTbl.Data[ 8] = '.';
269     CountryData->FNameTermTbl.Data[ 9] = '\"';
270     CountryData->FNameTermTbl.Data[10] = '/';
271     CountryData->FNameTermTbl.Data[11] = '\\';
272     CountryData->FNameTermTbl.Data[12] = '[';
273     CountryData->FNameTermTbl.Data[13] = ']';
274     CountryData->FNameTermTbl.Data[14] = ':';
275     CountryData->FNameTermTbl.Data[15] = '|';
276     CountryData->FNameTermTbl.Data[16] = '<';
277     CountryData->FNameTermTbl.Data[17] = '>';
278     CountryData->FNameTermTbl.Data[18] = '+';
279     CountryData->FNameTermTbl.Data[19] = '=';
280     CountryData->FNameTermTbl.Data[20] = ';';
281     CountryData->FNameTermTbl.Data[21] = ',';
282 
283     CountryData->CollateTbl.Size = ARRAYSIZE(CountryData->CollateTbl.Data);
284     for (i = 0; i < CountryData->CollateTbl.Size; ++i)
285         CountryData->CollateTbl.Data[i] = i;
286 
287     CountryData->DBCSLeadTbl.Size = 0; // Empty DBCS table
288     CountryData->DBCSLeadTbl.Data[0] = 0x0000;
289 
290     return TRUE;
291 }
292