Use wine_get_dos_file_name rather than relying on GetFullPathNameW
[wine] / dlls / winedos / dosmem.c
1 /*
2  * DOS memory emulation
3  *
4  * Copyright 1995 Alexandre Julliard
5  * Copyright 1996 Marcus Meissner
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_MMAN_H
31 # include <sys/mman.h>
32 #endif
33
34 #include "windef.h"
35 #include "winbase.h"
36 #include "winreg.h"
37 #include "excpt.h"
38 #include "winternl.h"
39 #include "wine/winbase16.h"
40
41 #include "dosexe.h"
42 #include "wine/debug.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(dosmem);
45
46 /* DOS memory highest address (including HMA) */
47 #define DOSMEM_SIZE             0x110000
48 #define DOSMEM_64KB             0x10000
49
50 /* see dlls/kernel/dosmem.c for the details */
51 static char *DOSMEM_dosmem;
52 static char *DOSMEM_sysmem;
53
54 /* use 2 low bits of 'size' for the housekeeping */
55 #define DM_BLOCK_DEBUG          0xABE00000
56 #define DM_BLOCK_TERMINAL       0x00000001
57 #define DM_BLOCK_FREE           0x00000002
58 #define DM_BLOCK_MASK           0x001FFFFC
59
60 /*
61 #define __DOSMEM_DEBUG__
62  */
63
64 typedef struct {
65    unsigned     size;
66 } dosmem_entry;
67
68 typedef struct {
69   unsigned      blocks;
70   unsigned      free;
71 } dosmem_info;
72
73 static inline dosmem_entry* next_block(dosmem_entry* block)
74 {
75     return (dosmem_entry*)((char*)block +
76                            sizeof(dosmem_entry) + (block->size & DM_BLOCK_MASK));
77 }
78
79 #define VM_STUB(x) (0x90CF00CD|(x<<8)) /* INT x; IRET; NOP */
80 #define VM_STUB_SEGMENT 0xf000         /* BIOS segment */
81
82 /* FIXME: this should be moved to the LOL, and the whole allocation strategy
83  * should use real MCB
84  */
85 static dosmem_info* DOSMEM_info_block;
86
87 /***********************************************************************
88  *           DOSMEM_MemoryTop
89  *
90  * Gets the DOS memory top.
91  */
92 static char *DOSMEM_MemoryTop(void)
93 {
94     return DOSMEM_dosmem+0x9FFFC; /* 640K */
95 }
96
97 /***********************************************************************
98  *           DOSMEM_RootBlock
99  *
100  * Gets the DOS memory root block.
101  */
102 static dosmem_entry *DOSMEM_RootBlock(void)
103 {
104     /* first block has to be paragraph-aligned */
105     return (dosmem_entry*)(((char*)DOSMEM_info_block) +
106                            ((((sizeof(dosmem_info) + 0xf) & ~0xf) - sizeof(dosmem_entry))));
107 }
108
109 /***********************************************************************
110  *           DOSMEM_FillIsrTable
111  *
112  * Fill the interrupt table with fake BIOS calls to BIOSSEG (0xf000).
113  *
114  * NOTES:
115  * Linux normally only traps INTs performed from or destined to BIOSSEG
116  * for us to handle, if the int_revectored table is empty. Filling the
117  * interrupt table with calls to INT stubs in BIOSSEG allows DOS programs
118  * to hook interrupts, as well as use their familiar retf tricks to call
119  * them, AND let Wine handle any unhooked interrupts transparently.
120  */
121 static void DOSMEM_FillIsrTable(void)
122 {
123     SEGPTR *isr = (SEGPTR*)DOSMEM_sysmem;
124     int x;
125
126     for (x=0; x<256; x++) isr[x]=MAKESEGPTR(VM_STUB_SEGMENT,x*4);
127 }
128
129 static void DOSMEM_MakeIsrStubs(void)
130 {
131     DWORD *stub = (DWORD*)(DOSMEM_dosmem + (VM_STUB_SEGMENT << 4));
132     int x;
133
134     for (x=0; x<256; x++) stub[x]=VM_STUB(x);
135 }
136
137 BIOSDATA* DOSVM_BiosData(void)
138 {
139     return (BIOSDATA *)(DOSMEM_sysmem + 0x400);
140 }
141
142 /**********************************************************************
143  *          DOSMEM_GetTicksSinceMidnight
144  *
145  * Return number of clock ticks since midnight.
146  */
147 static DWORD DOSMEM_GetTicksSinceMidnight(void)
148 {
149     SYSTEMTIME time;
150
151     /* This should give us the (approximately) correct
152      * 18.206 clock ticks per second since midnight.
153      */
154
155     GetLocalTime( &time );
156
157     return (((time.wHour * 3600 + time.wMinute * 60 +
158               time.wSecond) * 18206) / 1000) +
159              (time.wMilliseconds * 1000 / 54927);
160 }
161
162 /***********************************************************************
163  *           DOSMEM_FillBiosSegments
164  *
165  * Fill the BIOS data segment with dummy values.
166  */
167 static void DOSMEM_FillBiosSegments(void)
168 {
169     BYTE *pBiosSys = (BYTE*)DOSMEM_dosmem + 0xf0000;
170     BYTE *pBiosROMTable = pBiosSys+0xe6f5;
171     BIOSDATA *pBiosData = DOSVM_BiosData();
172     static const char bios_date[] = "13/01/99";
173
174       /* Clear all unused values */
175     memset( pBiosData, 0, sizeof(*pBiosData) );
176
177     /* FIXME: should check the number of configured drives and ports */
178     pBiosData->Com1Addr             = 0x3f8;
179     pBiosData->Com2Addr             = 0x2f8;
180     pBiosData->Lpt1Addr             = 0x378;
181     pBiosData->Lpt2Addr             = 0x278;
182     pBiosData->InstalledHardware    = 0x5463;
183     pBiosData->MemSize              = 640;
184     pBiosData->NextKbdCharPtr       = 0x1e;
185     pBiosData->FirstKbdCharPtr      = 0x1e;
186     pBiosData->VideoMode            = 3;
187     pBiosData->VideoColumns         = 80;
188     pBiosData->VideoPageSize        = 80 * 25 * 2;
189     pBiosData->VideoPageStartAddr   = 0xb800;
190     pBiosData->VideoCtrlAddr        = 0x3d4;
191     pBiosData->Ticks                = DOSMEM_GetTicksSinceMidnight();
192     pBiosData->NbHardDisks          = 2;
193     pBiosData->KbdBufferStart       = 0x1e;
194     pBiosData->KbdBufferEnd         = 0x3e;
195     pBiosData->RowsOnScreenMinus1   = 24;
196     pBiosData->BytesPerChar         = 0x10;
197     pBiosData->ModeOptions          = 0x64;
198     pBiosData->FeatureBitsSwitches  = 0xf9;
199     pBiosData->VGASettings          = 0x51;
200     pBiosData->DisplayCombination   = 0x08;
201     pBiosData->DiskDataRate         = 0;
202
203     /* fill ROM configuration table (values from Award) */
204     *(pBiosROMTable+0x0)        = 0x08; /* number of bytes following LO */
205     *(pBiosROMTable+0x1)        = 0x00; /* number of bytes following HI */
206     *(pBiosROMTable+0x2)        = 0xfc; /* model */
207     *(pBiosROMTable+0x3)        = 0x01; /* submodel */
208     *(pBiosROMTable+0x4)        = 0x00; /* BIOS revision */
209     *(pBiosROMTable+0x5)        = 0x74; /* feature byte 1 */
210     *(pBiosROMTable+0x6)        = 0x00; /* feature byte 2 */
211     *(pBiosROMTable+0x7)        = 0x00; /* feature byte 3 */
212     *(pBiosROMTable+0x8)        = 0x00; /* feature byte 4 */
213     *(pBiosROMTable+0x9)        = 0x00; /* feature byte 5 */
214
215     /* BIOS date string */
216     memcpy(pBiosSys+0xfff5, bios_date, sizeof bios_date);
217
218     /* BIOS ID */
219     *(pBiosSys+0xfffe) = 0xfc;
220
221     /* Reboot vector (f000:fff0 or ffff:0000) */
222     *(DWORD*)(pBiosSys + 0xfff0) = VM_STUB(0x19);
223 }
224
225 /***********************************************************************
226  *           BiosTick
227  *
228  * Increment the BIOS tick counter. Called by timer signal handler.
229  */
230 void BiosTick( WORD timer )
231 {
232     BIOSDATA *pBiosData = DOSVM_BiosData();
233     if (pBiosData) pBiosData->Ticks++;
234 }
235
236 /***********************************************************************
237  *           DOSMEM_AllocBlock
238  *
239  * Carve a chunk of the DOS memory block (without selector).
240  */
241 LPVOID DOSMEM_AllocBlock(UINT size, UINT16* pseg)
242 {
243    UINT          blocksize;
244    char         *block = NULL;
245    dosmem_info  *info_block = DOSMEM_info_block;
246    dosmem_entry *dm;
247 #ifdef __DOSMEM_DEBUG_
248    dosmem_entry *prev = NULL;
249 #endif
250
251    if( size > info_block->free ) return NULL;
252    dm = DOSMEM_RootBlock();
253
254    while (dm && dm->size != DM_BLOCK_TERMINAL)
255    {
256 #ifdef __DOSMEM_DEBUG__
257        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
258        {
259             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
260             return NULL;
261        }
262        prev = dm;
263 #endif
264        if( dm->size & DM_BLOCK_FREE )
265        {
266            dosmem_entry  *next = next_block(dm);
267
268            while ( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
269            {
270                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
271                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
272                next = next_block(dm);
273            }
274
275            blocksize = dm->size & DM_BLOCK_MASK;
276            if( blocksize >= size )
277            {
278                block = ((char*)dm) + sizeof(dosmem_entry);
279                if( blocksize - size > 0x20 )
280                {
281                    /* split dm so that the next one stays
282                     * paragraph-aligned (and dm loses free bit) */
283
284                    dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
285                                               sizeof(dosmem_entry));
286                    next = (dosmem_entry*)(((char*)dm) +
287                            sizeof(dosmem_entry) + dm->size);
288                    next->size = (blocksize - (dm->size +
289                            sizeof(dosmem_entry))) | DM_BLOCK_FREE
290 #ifdef __DOSMEM_DEBUG__
291                                                   | DM_BLOCK_DEBUG
292 #endif
293                                                   ;
294                } else dm->size &= DM_BLOCK_MASK;
295
296                info_block->blocks++;
297                info_block->free -= dm->size;
298                if( pseg ) *pseg = (block - DOSMEM_dosmem) >> 4;
299 #ifdef __DOSMEM_DEBUG__
300                dm->size |= DM_BLOCK_DEBUG;
301 #endif
302                break;
303            }
304            dm = next;
305        }
306        else dm = next_block(dm);
307    }
308    return (LPVOID)block;
309 }
310
311 /***********************************************************************
312  *           DOSMEM_FreeBlock
313  */
314 BOOL DOSMEM_FreeBlock(void* ptr)
315 {
316    dosmem_info  *info_block = DOSMEM_info_block;
317
318    if( ptr >= (void*)(((char*)DOSMEM_RootBlock()) + sizeof(dosmem_entry)) &&
319        ptr < (void*)DOSMEM_MemoryTop() && !((((char*)ptr)
320                   - DOSMEM_dosmem) & 0xf) )
321    {
322        dosmem_entry  *dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
323
324        if( !(dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL))
325 #ifdef __DOSMEM_DEBUG__
326          && ((dm->size & DM_BLOCK_DEBUG) == DM_BLOCK_DEBUG )
327 #endif
328          )
329        {
330              info_block->blocks--;
331              info_block->free += dm->size;
332
333              dm->size |= DM_BLOCK_FREE;
334              return TRUE;
335        }
336    }
337    return FALSE;
338 }
339
340 /***********************************************************************
341  *           DOSMEM_ResizeBlock
342  *
343  * Resize DOS memory block in place. Returns block size or -1 on error.
344  *
345  * If exact is TRUE, returned value is either old or requested block
346  * size. If exact is FALSE, block is expanded even if there is not
347  * enough space for full requested block size.
348  */
349 UINT DOSMEM_ResizeBlock(void *ptr, UINT size, BOOL exact)
350 {
351    char         *block = NULL;
352    dosmem_info  *info_block = DOSMEM_info_block;
353    dosmem_entry *dm;
354    dosmem_entry *next;
355    UINT blocksize;
356    UINT orgsize;
357
358    if( (ptr < (void*)(sizeof(dosmem_entry) + (char*)DOSMEM_RootBlock())) ||
359        (ptr >= (void*)DOSMEM_MemoryTop()) ||
360        (((((char*)ptr) - DOSMEM_dosmem) & 0xf) != 0) )
361      return (UINT)-1;
362
363    dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
364    if( dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL) )
365        return (UINT)-1;
366
367    next = next_block(dm);
368    orgsize = dm->size & DM_BLOCK_MASK;
369
370    /* collapse free blocks */
371    while ( next->size & DM_BLOCK_FREE )
372    {
373        dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
374        next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
375        next = next_block(dm);
376    }
377
378    blocksize = dm->size & DM_BLOCK_MASK;
379
380    /*
381     * If collapse didn't help we either expand block to maximum
382     * available size (exact == FALSE) or give collapsed blocks
383     * back to free storage (exact == TRUE).
384     */
385    if (blocksize < size)
386        size = exact ? orgsize : blocksize;
387
388    block = ((char*)dm) + sizeof(dosmem_entry);
389    if( blocksize - size > 0x20 )
390    {
391        /*
392         * split dm so that the next one stays
393         * paragraph-aligned (and next gains free bit) 
394         */
395
396        dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
397                    sizeof(dosmem_entry));
398        next = (dosmem_entry*)(((char*)dm) +
399                               sizeof(dosmem_entry) + dm->size);
400        next->size = (blocksize - (dm->size +
401                                   sizeof(dosmem_entry))) | DM_BLOCK_FREE;
402    } 
403    else 
404    {
405        dm->size &= DM_BLOCK_MASK;
406    }
407
408    /*
409     * Adjust available memory if block size changes.
410     */
411    info_block->free += orgsize - dm->size;
412
413    return size;
414 }
415
416 /***********************************************************************
417  *           DOSMEM_Available
418  */
419 UINT DOSMEM_Available(void)
420 {
421    UINT          blocksize, available = 0;
422    dosmem_entry *dm;
423
424    dm = DOSMEM_RootBlock();
425
426    while (dm && dm->size != DM_BLOCK_TERMINAL)
427    {
428 #ifdef __DOSMEM_DEBUG__
429        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
430        {
431             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
432             return NULL;
433        }
434        prev = dm;
435 #endif
436        if( dm->size & DM_BLOCK_FREE )
437        {
438            dosmem_entry  *next = next_block(dm);
439
440            while ( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
441            {
442                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
443                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
444                next = next_block(dm);
445            }
446
447            blocksize = dm->size & DM_BLOCK_MASK;
448            if ( blocksize > available ) available = blocksize;
449            dm = next;
450        }
451        else dm = next_block(dm);
452    }
453    return available;
454 }
455
456 /***********************************************************************
457  *           DOSMEM_InitMemory
458  *
459  * Initialises the DOS memory structures.
460  */
461 static void DOSMEM_InitMemory(char* addr)
462 {
463     dosmem_entry*       root_block;
464     dosmem_entry*       dm;
465
466     DOSMEM_FillBiosSegments();
467     DOSMEM_FillIsrTable();
468
469     DOSMEM_info_block = (dosmem_info*)addr;
470     root_block = DOSMEM_RootBlock();
471     root_block->size = DOSMEM_MemoryTop() - (((char*)root_block) + sizeof(dosmem_entry));
472
473     DOSMEM_info_block->blocks = 0;
474     DOSMEM_info_block->free = root_block->size;
475
476     dm = next_block(root_block);
477     dm->size = DM_BLOCK_TERMINAL;
478     root_block->size |= DM_BLOCK_FREE
479 #ifdef __DOSMEM_DEBUG__
480         | DM_BLOCK_DEBUG
481 #endif
482         ;
483
484     TRACE("DOS conventional memory initialized, %d bytes free.\n",
485           DOSMEM_Available());
486 }
487
488 /******************************************************************
489  *             DOSMEM_InitDosMemory
490  *
491  * When WineDOS is loaded, initializes the current DOS memory layout.
492  */
493 BOOL DOSMEM_InitDosMemory(void)
494 {
495     HMODULE16           hModule;
496     unsigned short      sel;
497     LDT_ENTRY           entry;
498     DWORD               reserve;
499
500     if (!(hModule = GetModuleHandle16("KERNEL"))) return FALSE;
501     /* KERNEL.194: __F000H */
502     sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(ULONG_PTR)194));
503     wine_ldt_get_entry(sel, &entry);
504     DOSMEM_dosmem = (char*)wine_ldt_get_base(&entry) - 0xF0000;
505     /* KERNEL.183: __0000H */
506     sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)183));
507     wine_ldt_get_entry(sel, &entry);
508     DOSMEM_sysmem = wine_ldt_get_base(&entry);
509
510     /*
511      * Reserve either:
512      * - lowest 64k for NULL pointer catching (Win16)
513      * - lowest 1k for interrupt handlers and
514      *   another 0.5k for BIOS, DOS and intra-application
515      *   areas (DOS)
516      */
517     if (DOSMEM_dosmem != DOSMEM_sysmem)
518         reserve = 0x10000; /* 64k */
519     else
520         reserve = 0x600; /* 1.5k */
521
522     /*
523      * Round to paragraph boundary in order to make
524      * sure the alignment is correct.
525      */
526     reserve = ((reserve + 15) >> 4) << 4;
527
528     /*
529      * Set DOS memory base and initialize conventional memory.
530      */
531     DOSMEM_InitMemory(DOSMEM_dosmem + reserve);
532     return TRUE;
533 }
534
535 /******************************************************************
536  *              DOSMEM_MapDosLayout
537  *
538  * Initialize the first MB of memory to look like a real DOS setup
539  */
540 BOOL DOSMEM_MapDosLayout(void)
541 {
542     static int already_mapped;
543
544     if (!already_mapped)
545     {
546         HMODULE16       hModule;
547         unsigned short  sel;
548         LDT_ENTRY       entry;
549
550         if (DOSMEM_dosmem)
551         {
552             ERR( "Needs access to the first megabyte for DOS mode\n" );
553             ExitProcess(1);
554         }
555         MESSAGE( "Warning: unprotecting memory to allow real-mode calls.\n"
556                  "         NULL pointer accesses will no longer be caught.\n" );
557         VirtualProtect( NULL, DOSMEM_SIZE, PAGE_EXECUTE_READWRITE, NULL );
558         /* copy the BIOS and ISR area down */
559         memcpy( DOSMEM_dosmem, DOSMEM_sysmem, 0x400 + 0x100 );
560         DOSMEM_sysmem = DOSMEM_dosmem;
561         hModule = GetModuleHandle16("KERNEL");
562         /* selector to 0000H */
563         sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)183));
564         wine_ldt_get_entry(sel, &entry);
565         wine_ldt_set_base(&entry, NULL);
566         wine_ldt_set_entry(sel, &entry);
567         /* selector to BiosData */
568         sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)193));
569         wine_ldt_get_entry(sel, &entry);
570         wine_ldt_set_base(&entry, (const void*)0x400);
571         wine_ldt_set_entry(sel, &entry);
572         /* we may now need the actual interrupt stubs, and since we've just moved the
573          * interrupt vector table away, we can fill the area with stubs instead... */
574         DOSMEM_MakeIsrStubs();
575         already_mapped = 1;
576     }
577     return TRUE;
578 }