/* * DOS interrupt 21h handler * * Copyright 1993, 1994 Erik Bos * Copyright 1996 Alexandre Julliard * Copyright 1997 Andreas Mohr * Copyright 1998 Uwe Bonnes * Copyright 1998, 1999 Ove Kaaven * Copyright 2003 Thomas Mertes * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include "wine/port.h" #include <stdarg.h> #include <stdio.h> #ifdef HAVE_SYS_STAT_H # include <sys/stat.h> #endif #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include "windef.h" #include "winbase.h" #include "winreg.h" #include "winternl.h" #include "wine/winbase16.h" #include "dosexe.h" #include "winerror.h" #include "winuser.h" #include "wine/unicode.h" #include "wine/server.h" #include "wine/debug.h" #include "wine/exception.h" BOOL WINAPI VerifyConsoleIoHandle(HANDLE); /* * Note: * - Most of the file related functions are wrong. NT's kernel32 * doesn't maintain a per drive current directory, while DOS does. * We should in fact keep track in here of those per drive * directories, and use this info while dealing with partial paths * (drive defined, but only relative paths). This could even be * created as an array of CDS (there should be an entry for that in * the LOL) */ /* * Forward declarations. */ static BOOL INT21_RenameFile( CONTEXT86 *context ); WINE_DEFAULT_DEBUG_CHANNEL(int21); #include "pshpack1.h" /* * Extended Drive Parameter Block. * This structure is compatible with standard DOS4+ DPB and * extended DOS7 DPB. */ typedef struct _INT21_DPB { BYTE drive; /* 00 drive number (0=A, ...) */ BYTE unit; /* 01 unit number within device driver */ WORD sector_bytes; /* 02 bytes per sector */ BYTE cluster_sectors; /* 04 highest sector number within a cluster */ BYTE shift; /* 05 shift count to convert clusters into sectors */ WORD num_reserved; /* 06 reserved sectors at beginning of drive */ BYTE num_FAT; /* 08 number of FATs */ WORD num_root_entries; /* 09 number of root directory entries */ WORD first_data_sector; /* 0b number of first sector containing user data */ WORD num_clusters1; /* 0d highest cluster number (number of data clusters + 1) */ WORD sectors_per_FAT; /* 0f number of sectors per FAT */ WORD first_dir_sector; /* 11 sector number of first directory sector */ SEGPTR driver_header; /* 13 address of device driver header */ BYTE media_ID; /* 17 media ID byte */ BYTE access_flag; /* 18 0x00 if disk accessed, 0xff if not */ SEGPTR next; /* 19 pointer to next DPB */ WORD search_cluster1; /* 1d cluster at which to start search for free space */ WORD free_clusters_lo; /* 1f number of free clusters on drive or 0xffff if unknown */ WORD free_clusters_hi; /* 21 hiword of clusters_free */ WORD mirroring_flags; /* 23 active FAT/mirroring flags */ WORD info_sector; /* 25 sector number of file system info sector or 0xffff for none */ WORD spare_boot_sector; /* 27 sector number of backup boot sector or 0xffff for none */ DWORD first_cluster_sector; /* 29 sector number of the first cluster */ DWORD num_clusters2; /* 2d maximum cluster number */ DWORD fat_clusters; /* 31 number of clusters occupied by FAT */ DWORD root_cluster; /* 35 cluster number of start of root directory */ DWORD search_cluster2; /* 39 cluster at which to start searching for free space */ } INT21_DPB; /* * Structure for DOS data that can be accessed directly from applications. * Real and protected mode pointers will be returned to this structure so * the structure must be correctly packed. */ typedef struct _INT21_HEAP { WORD uppercase_size; /* Size of the following table in bytes */ BYTE uppercase_table[128]; /* Uppercase equivalents of chars from 0x80 to 0xff. */ WORD lowercase_size; /* Size of the following table in bytes */ BYTE lowercase_table[256]; /* Lowercase equivalents of chars from 0x00 to 0xff. */ WORD collating_size; /* Size of the following table in bytes */ BYTE collating_table[256]; /* Values used to sort characters from 0x00 to 0xff. */ WORD filename_size; /* Size of the following filename data in bytes */ BYTE filename_reserved1; /* 0x01 for MS-DOS 3.30-6.00 */ BYTE filename_lowest; /* Lowest permissible character value for filename */ BYTE filename_highest; /* Highest permissible character value for filename */ BYTE filename_reserved2; /* 0x00 for MS-DOS 3.30-6.00 */ BYTE filename_exclude_first; /* First illegal character in permissible range */ BYTE filename_exclude_last; /* Last illegal character in permissible range */ BYTE filename_reserved3; /* 0x02 for MS-DOS 3.30-6.00 */ BYTE filename_illegal_size; /* Number of terminators in the following table */ BYTE filename_illegal_table[16]; /* Characters which terminate a filename */ WORD dbcs_size; /* Number of valid ranges in the following table */ BYTE dbcs_table[16]; /* Start/end bytes for N ranges and 00/00 as terminator */ BYTE misc_indos; /* Interrupt 21 nesting flag */ WORD misc_segment; /* Real mode segment for INT21_HEAP */ WORD misc_selector; /* Protected mode selector for INT21_HEAP */ INT21_DPB misc_dpb_list[MAX_DOS_DRIVES]; /* Drive parameter blocks for all drives */ } INT21_HEAP; struct FCB { BYTE drive_number; CHAR file_name[8]; CHAR file_extension[3]; WORD current_block_number; WORD logical_record_size; DWORD file_size; WORD date_of_last_write; WORD time_of_last_write; BYTE file_number; BYTE attributes; WORD starting_cluster; WORD sequence_number; BYTE file_attributes; BYTE unused; BYTE record_within_current_block; BYTE random_access_record_number[4]; }; struct XFCB { BYTE xfcb_signature; BYTE reserved[5]; BYTE xfcb_file_attribute; BYTE fcb[37]; }; /* DTA layout for FindFirst/FindNext */ typedef struct { BYTE drive; /* 00 drive letter */ char mask[11]; /* 01 search template */ BYTE search_attr; /* 0c search attributes */ WORD count; /* 0d entry count within directory */ WORD cluster; /* 0f cluster of parent directory */ WCHAR *fullPath; /* 11 full path (was: reserved) */ BYTE fileattr; /* 15 file attributes */ WORD filetime; /* 16 file time */ WORD filedate; /* 18 file date */ DWORD filesize; /* 1a file size */ char filename[13]; /* 1e file name + extension */ } FINDFILE_DTA; /* FCB layout for FindFirstFCB/FindNextFCB */ typedef struct { BYTE drive; /* 00 drive letter */ char filename[11]; /* 01 filename 8+3 format */ int count; /* 0c entry count (was: reserved) */ WCHAR *fullPath; /* 10 full path (was: reserved) */ } FINDFILE_FCB; /* DOS directory entry for FindFirstFCB/FindNextFCB */ typedef struct { char filename[11]; /* 00 filename 8+3 format */ BYTE fileattr; /* 0b file attributes */ BYTE reserved[10]; /* 0c reserved */ WORD filetime; /* 16 file time */ WORD filedate; /* 18 file date */ WORD cluster; /* 1a file first cluster */ DWORD filesize; /* 1c file size */ } DOS_DIRENTRY_LAYOUT; #include "poppack.h" /* dos file attributes */ #define FA_NORMAL 0x00 /* Normal file, no attributes */ #define FA_RDONLY 0x01 /* Read only attribute */ #define FA_HIDDEN 0x02 /* Hidden file */ #define FA_SYSTEM 0x04 /* System file */ #define FA_LABEL 0x08 /* Volume label */ #define FA_DIRECTORY 0x10 /* Directory */ #define FA_ARCHIVE 0x20 /* Archive */ #define FA_UNUSED 0x40 /* Unused */ /* Error codes */ #define ER_NoNetwork 0x49 /* Error classes */ #define EC_OutOfResource 0x01 #define EC_Temporary 0x02 #define EC_AccessDenied 0x03 #define EC_InternalError 0x04 #define EC_HardwareFailure 0x05 #define EC_SystemFailure 0x06 #define EC_ProgramError 0x07 #define EC_NotFound 0x08 #define EC_MediaError 0x0b #define EC_Exists 0x0c #define EC_Unknown 0x0d /* Suggested actions */ #define SA_Retry 0x01 #define SA_DelayedRetry 0x02 #define SA_Abort 0x04 #define SA_Ignore 0x06 #define SA_Ask4Retry 0x07 /* Error locus */ #define EL_Unknown 0x01 #define EL_Disk 0x02 #define EL_Network 0x03 #define EL_Serial 0x04 #define EL_Memory 0x05 /* BIOS Keyboard Scancodes */ #define KEY_LEFT 0x4B #define KEY_RIGHT 0x4D #define KEY_UP 0x48 #define KEY_DOWN 0x50 #define KEY_IC 0x52 /* insert char */ #define KEY_DC 0x53 /* delete char */ #define KEY_BACKSPACE 0x0E #define KEY_HOME 0x47 #define KEY_END 0x4F #define KEY_NPAGE 0x49 #define KEY_PPAGE 0x51 struct magic_device { WCHAR name[10]; HANDLE handle; LARGE_INTEGER index; void (*ioctl_handler)(CONTEXT86 *); }; static void INT21_IoctlScsiMgrHandler( CONTEXT86 * ); static void INT21_IoctlEMSHandler( CONTEXT86 * ); static void INT21_IoctlHPScanHandler( CONTEXT86 * ); static struct magic_device magic_devices[] = { { {'s','c','s','i','m','g','r','$',0}, NULL, { { 0, 0 } }, INT21_IoctlScsiMgrHandler }, { {'e','m','m','x','x','x','x','0',0}, NULL, { { 0, 0 } }, INT21_IoctlEMSHandler }, { {'h','p','s','c','a','n',0}, NULL, { { 0, 0 } }, INT21_IoctlHPScanHandler }, }; #define NB_MAGIC_DEVICES (sizeof(magic_devices)/sizeof(magic_devices[0])) /* Many calls translate a drive argument like this: drive number (00h = default, 01h = A:, etc) */ /****************************************************************** * INT21_DriveName * * Many calls translate a drive argument like this: * drive number (00h = default, 01h = A:, etc) */ static const char *INT21_DriveName(int drive) { if (drive > 0) { if (drive <= 26) return wine_dbg_sprintf("%c:", 'A' + drive - 1); else return wine_dbg_sprintf( "<Bad drive: %d>", drive); } return "default"; } /*********************************************************************** * INT21_GetCurrentDrive * * Return current drive using scheme (0=A:, 1=B:, 2=C:, ...) or * MAX_DOS_DRIVES on error. */ static BYTE INT21_GetCurrentDrive(void) { WCHAR current_directory[MAX_PATH]; if (!GetCurrentDirectoryW( MAX_PATH, current_directory ) || current_directory[1] != ':') { TRACE( "Failed to get current drive.\n" ); return MAX_DOS_DRIVES; } return toupperW( current_directory[0] ) - 'A'; } /*********************************************************************** * INT21_MapDrive * * Convert drive number from scheme (0=default, 1=A:, 2=B:, ...) into * scheme (0=A:, 1=B:, 2=C:, ...) or MAX_DOS_DRIVES on error. */ static BYTE INT21_MapDrive( BYTE drive ) { if (drive) { WCHAR drivespec[3] = {'A', ':', 0}; UINT drivetype; drivespec[0] += drive - 1; drivetype = GetDriveTypeW( drivespec ); if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR) return MAX_DOS_DRIVES; return drive - 1; } return INT21_GetCurrentDrive(); } /*********************************************************************** * INT21_SetCurrentDrive * * Set current drive. Uses scheme (0=A:, 1=B:, 2=C:, ...). */ static void INT21_SetCurrentDrive( BYTE drive ) { WCHAR drivespec[3] = {'A', ':', 0}; drivespec[0] += drive; if (!SetCurrentDirectoryW( drivespec )) TRACE( "Failed to set current drive.\n" ); } /*********************************************************************** * INT21_ReadChar * * Reads a character from the standard input. * Extended keycodes will be returned as two separate characters. */ static BOOL INT21_ReadChar( BYTE *input, CONTEXT86 *waitctx ) { static BYTE pending_scan = 0; if (pending_scan) { if (input) *input = pending_scan; if (waitctx) pending_scan = 0; return TRUE; } else { BYTE ascii; BYTE scan; if (!DOSVM_Int16ReadChar( &ascii, &scan, waitctx )) return FALSE; if (input) *input = ascii; if (waitctx && !ascii) pending_scan = scan; return TRUE; } } /*********************************************************************** * INT21_GetSystemCountryCode * * Return DOS country code for default system locale. */ static WORD INT21_GetSystemCountryCode( void ) { /* * FIXME: Determine country code. We should probably use * DOSCONF structure for that. */ return GetSystemDefaultLangID(); } /*********************************************************************** * INT21_FillCountryInformation * * Fill 34-byte buffer with country information data using * default system locale. */ static void INT21_FillCountryInformation( BYTE *buffer ) { /* 00 - WORD: date format * 00 = mm/dd/yy * 01 = dd/mm/yy * 02 = yy/mm/dd */ *(WORD*)(buffer + 0) = 0; /* FIXME: Get from locale */ /* 02 - BYTE[5]: ASCIIZ currency symbol string */ buffer[2] = '$'; /* FIXME: Get from locale */ buffer[3] = 0; /* 07 - BYTE[2]: ASCIIZ thousands separator */ buffer[7] = 0; /* FIXME: Get from locale */ buffer[8] = 0; /* 09 - BYTE[2]: ASCIIZ decimal separator */ buffer[9] = '.'; /* FIXME: Get from locale */ buffer[10] = 0; /* 11 - BYTE[2]: ASCIIZ date separator */ buffer[11] = '/'; /* FIXME: Get from locale */ buffer[12] = 0; /* 13 - BYTE[2]: ASCIIZ time separator */ buffer[13] = ':'; /* FIXME: Get from locale */ buffer[14] = 0; /* 15 - BYTE: Currency format * bit 2 = set if currency symbol replaces decimal point * bit 1 = number of spaces between value and currency symbol * bit 0 = 0 if currency symbol precedes value * 1 if currency symbol follows value */ buffer[15] = 0; /* FIXME: Get from locale */ /* 16 - BYTE: Number of digits after decimal in currency */ buffer[16] = 0; /* FIXME: Get from locale */ /* 17 - BYTE: Time format * bit 0 = 0 if 12-hour clock * 1 if 24-hour clock */ buffer[17] = 1; /* FIXME: Get from locale */ /* 18 - DWORD: Address of case map routine */ *(DWORD*)(buffer + 18) = 0; /* FIXME: ptr to case map routine */ /* 22 - BYTE[2]: ASCIIZ data-list separator */ buffer[22] = ','; /* FIXME: Get from locale */ buffer[23] = 0; /* 24 - BYTE[10]: Reserved */ memset( buffer + 24, 0, 10 ); } /*********************************************************************** * INT21_FillHeap * * Initialize DOS heap. * * Filename Terminator Table of w2k DE NTVDM: * 16 00 01 00 FF 00 00 20-02 0E 2E 22 2F 5C 5B 5D ....... ..."/\[] * 3A 7C 3C 3E 2B 3D 3B 2C-00 :|<>+=;, */ static void INT21_FillHeap( INT21_HEAP *heap ) { static const char terminators[] = ".\"/\\[]:|<>+=;,"; int i; /* * Uppercase table. */ heap->uppercase_size = 128; for (i = 0; i < 128; i++) heap->uppercase_table[i] = toupper( 128 + i ); /* * Lowercase table. */ heap->lowercase_size = 256; for (i = 0; i < 256; i++) heap->lowercase_table[i] = tolower( i ); /* * Collating table. */ heap->collating_size = 256; for (i = 0; i < 256; i++) heap->collating_table[i] = i; /* * Filename table. */ heap->filename_size = 8 + strlen(terminators); heap->filename_illegal_size = strlen(terminators); memcpy( heap->filename_illegal_table, terminators, heap->filename_illegal_size ); heap->filename_reserved1 = 0x01; heap->filename_lowest = 0x00; heap->filename_highest = 0xff; heap->filename_reserved2 = 0x00; heap->filename_exclude_first = 0x00; heap->filename_exclude_last = 0x20; heap->filename_reserved3 = 0x02; /* * DBCS lead byte table. This table is empty. */ heap->dbcs_size = 0; memset( heap->dbcs_table, 0, sizeof(heap->dbcs_table) ); /* * Initialize InDos flag. */ heap->misc_indos = 0; /* * FIXME: Should drive parameter blocks (DPB) be * initialized here and linked to DOS LOL? */ } /*********************************************************************** * INT21_GetHeapPointer * * Get pointer for DOS heap (INT21_HEAP). * Creates and initializes heap on first call. */ static INT21_HEAP *INT21_GetHeapPointer( void ) { static INT21_HEAP *heap_pointer = NULL; if (!heap_pointer) { WORD heap_segment; WORD heap_selector; heap_pointer = DOSVM_AllocDataUMB( sizeof(INT21_HEAP), &heap_segment, &heap_selector ); heap_pointer->misc_segment = heap_segment; heap_pointer->misc_selector = heap_selector; INT21_FillHeap( heap_pointer ); } return heap_pointer; } /*********************************************************************** * INT21_GetHeapSelector * * Get segment/selector for DOS heap (INT21_HEAP). * Creates and initializes heap on first call. */ static WORD INT21_GetHeapSelector( CONTEXT86 *context ) { INT21_HEAP *heap = INT21_GetHeapPointer(); if (!ISV86(context) && DOSVM_IsWin16()) return heap->misc_selector; else return heap->misc_segment; } /*********************************************************************** * INT21_FillDrivePB * * Fill DOS heap drive parameter block for the specified drive. * Return TRUE if drive was valid and there were * no errors while reading drive information. */ static BOOL INT21_FillDrivePB( BYTE drive ) { WCHAR drivespec[3] = {'A', ':', 0}; INT21_HEAP *heap = INT21_GetHeapPointer(); INT21_DPB *dpb; UINT drivetype; DWORD cluster_sectors; DWORD sector_bytes; DWORD free_clusters; DWORD total_clusters; if (drive >= MAX_DOS_DRIVES) return FALSE; dpb = &heap->misc_dpb_list[drive]; drivespec[0] += drive; drivetype = GetDriveTypeW( drivespec ); /* * FIXME: Does this check work correctly with floppy/cdrom drives? */ if (drivetype == DRIVE_NO_ROOT_DIR || drivetype == DRIVE_UNKNOWN) return FALSE; /* * FIXME: Does this check work correctly with floppy/cdrom drives? */ if (!GetDiskFreeSpaceW( drivespec, &cluster_sectors, §or_bytes, &free_clusters, &total_clusters )) return FALSE; /* * FIXME: Most of the values listed below are incorrect. * All values should be validated. */ dpb->drive = drive; dpb->unit = 0; dpb->sector_bytes = sector_bytes; dpb->cluster_sectors = cluster_sectors - 1; dpb->shift = 0; while (cluster_sectors > 1) { cluster_sectors /= 2; dpb->shift++; } dpb->num_reserved = 0; dpb->num_FAT = 1; dpb->num_root_entries = 2; dpb->first_data_sector = 2; dpb->num_clusters1 = total_clusters; dpb->sectors_per_FAT = 1; dpb->first_dir_sector = 1; dpb->driver_header = 0; dpb->media_ID = (drivetype == DRIVE_FIXED) ? 0xF8 : 0xF0; dpb->access_flag = 0; dpb->next = 0; dpb->search_cluster1 = 0; dpb->free_clusters_lo = LOWORD(free_clusters); dpb->free_clusters_hi = HIWORD(free_clusters); dpb->mirroring_flags = 0; dpb->info_sector = 0xffff; dpb->spare_boot_sector = 0xffff; dpb->first_cluster_sector = 0; dpb->num_clusters2 = total_clusters; dpb->fat_clusters = 32; dpb->root_cluster = 0; dpb->search_cluster2 = 0; return TRUE; } /*********************************************************************** * INT21_GetCurrentDirectory * * Handler for: * - function 0x47 * - subfunction 0x47 of function 0x71 */ static BOOL INT21_GetCurrentDirectory( CONTEXT86 *context, BOOL islong ) { char *buffer = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi); BYTE drive = INT21_MapDrive( DL_reg(context) ); WCHAR pathW[MAX_PATH]; char pathA[MAX_PATH]; WCHAR *ptr = pathW; TRACE( "drive %d\n", DL_reg(context) ); if (drive == MAX_DOS_DRIVES) { SetLastError(ERROR_INVALID_DRIVE); return FALSE; } /* * Grab current directory. */ if (!GetCurrentDirectoryW( MAX_PATH, pathW )) return FALSE; if (toupperW(pathW[0]) - 'A' != drive || pathW[1] != ':') { /* cwd is not on the requested drive, get the environment string instead */ WCHAR env_var[4]; env_var[0] = '='; env_var[1] = 'A' + drive; env_var[2] = ':'; env_var[3] = 0; if (!GetEnvironmentVariableW( env_var, pathW, MAX_PATH )) { /* return empty path */ buffer[0] = 0; return TRUE; } } /* * Convert into short format. */ if (!islong) { DWORD result = GetShortPathNameW( pathW, pathW, MAX_PATH ); if (!result) return FALSE; if (result > MAX_PATH) { WARN( "Short path too long!\n" ); SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */ return FALSE; } } /* * The returned pathname does not include * the drive letter, colon or leading backslash. */ if (ptr[0] == '\\') { /* * FIXME: We should probably just strip host part from name... */ FIXME( "UNC names are not supported.\n" ); SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */ return FALSE; } else if (!ptr[0] || ptr[1] != ':' || ptr[2] != '\\') { WARN( "Path is neither UNC nor DOS path: %s\n", wine_dbgstr_w(ptr) ); SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */ return FALSE; } else { /* Remove drive letter, colon and leading backslash. */ ptr += 3; } /* * Convert into OEM string. */ if (!WideCharToMultiByte(CP_OEMCP, 0, ptr, -1, pathA, MAX_PATH, NULL, NULL)) { WARN( "Cannot convert path!\n" ); SetLastError(ERROR_NETWORK_BUSY); /* Internal Wine error. */ return FALSE; } /* * Success. */ if (!islong) { /* Undocumented success code. */ SET_AX( context, 0x0100 ); /* Truncate buffer to 64 bytes. */ pathA[63] = 0; } TRACE( "%c:=%s\n", 'A' + drive, pathA ); strcpy( buffer, pathA ); return TRUE; } /*********************************************************************** * INT21_SetCurrentDirectory * * Handler for: * - function 0x3b * - subfunction 0x3b of function 0x71 */ static BOOL INT21_SetCurrentDirectory( CONTEXT86 *context ) { WCHAR dirW[MAX_PATH]; WCHAR env_var[4]; DWORD attr; char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); BYTE drive = INT21_GetCurrentDrive(); BOOL result; TRACE( "SET CURRENT DIRECTORY %s\n", dirA ); MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH); if (!GetFullPathNameW( dirW, MAX_PATH, dirW, NULL )) return FALSE; attr = GetFileAttributesW( dirW ); if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY)) { SetLastError( ERROR_PATH_NOT_FOUND ); return FALSE; } env_var[0] = '='; env_var[1] = dirW[0]; env_var[2] = ':'; env_var[3] = 0; result = SetEnvironmentVariableW( env_var, dirW ); /* only set current directory if on the current drive */ if (result && (toupperW(dirW[0]) - 'A' == drive)) result = SetCurrentDirectoryW( dirW ); return result; } /*********************************************************************** * INT21_CreateMagicDeviceHandle * * Create a dummy file handle for a "magic" device. */ static HANDLE INT21_CreateMagicDeviceHandle( LPCWSTR name ) { const char *dir = wine_get_server_dir(); int len; HANDLE ret; NTSTATUS status; OBJECT_ATTRIBUTES attr; UNICODE_STRING nameW; IO_STATUS_BLOCK io; len = MultiByteToWideChar( CP_UNIXCP, 0, dir, -1, NULL, 0 ); nameW.Length = (len + 1 + strlenW( name )) * sizeof(WCHAR); nameW.MaximumLength = nameW.Length + sizeof(WCHAR); if (!(nameW.Buffer = HeapAlloc( GetProcessHeap(), 0, nameW.Length ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return 0; } MultiByteToWideChar( CP_UNIXCP, 0, dir, -1, nameW.Buffer, len ); nameW.Buffer[len-1] = '/'; strcpyW( nameW.Buffer + len, name ); attr.Length = sizeof(attr); attr.RootDirectory = 0; attr.Attributes = 0; attr.ObjectName = &nameW; attr.SecurityDescriptor = NULL; attr.SecurityQualityOfService = NULL; status = NtCreateFile( &ret, GENERIC_READ|GENERIC_WRITE, &attr, &io, NULL, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_ALERT, NULL, 0 ); if (status) { ret = 0; SetLastError( RtlNtStatusToDosError(status) ); } RtlFreeUnicodeString( &nameW ); return ret; } /*********************************************************************** * INT21_OpenMagicDevice * * Open a file handle for "magic" devices like EMMXXXX0. */ static HANDLE INT21_OpenMagicDevice( LPCWSTR name, DWORD access ) { unsigned int i; const WCHAR *p; HANDLE handle; if (name[0] && (name[1] == ':')) name += 2; if ((p = strrchrW( name, '/' ))) name = p + 1; if ((p = strrchrW( name, '\\' ))) name = p + 1; for (i = 0; i < NB_MAGIC_DEVICES; i++) { int len = strlenW( magic_devices[i].name ); if (!strncmpiW( magic_devices[i].name, name, len ) && (!name[len] || name[len] == '.' || name[len] == ':')) break; } if (i == NB_MAGIC_DEVICES) return 0; if (!magic_devices[i].handle) /* need to open it */ { IO_STATUS_BLOCK io; FILE_INTERNAL_INFORMATION info; if (!(handle = INT21_CreateMagicDeviceHandle( magic_devices[i].name ))) return 0; NtQueryInformationFile( handle, &io, &info, sizeof(info), FileInternalInformation ); magic_devices[i].index = info.IndexNumber; magic_devices[i].handle = handle; } if (!DuplicateHandle( GetCurrentProcess(), magic_devices[i].handle, GetCurrentProcess(), &handle, access, FALSE, 0 )) handle = 0; return handle; } /*********************************************************************** * INT21_CreateFile * * Handler for: * - function 0x3c * - function 0x3d * - function 0x5b * - function 0x6c * - subfunction 0x6c of function 0x71 */ static BOOL INT21_CreateFile( CONTEXT86 *context, DWORD pathSegOff, BOOL returnStatus, WORD dosAccessShare, BYTE dosAction ) { WORD dosStatus; char *pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, pathSegOff); WCHAR pathW[MAX_PATH]; DWORD winAccess; DWORD winAttributes; HANDLE winHandle; DWORD winMode; DWORD winSharing; TRACE( "CreateFile called: function=%02x, action=%02x, access/share=%04x, " "create flags=%04x, file=%s.\n", AH_reg(context), dosAction, dosAccessShare, CX_reg(context), pathA ); /* * Application tried to create/open a file whose name * ends with a backslash. This is not allowed. * * FIXME: This needs to be validated, especially the return value. */ if (pathA[strlen(pathA) - 1] == '/') { SetLastError( ERROR_FILE_NOT_FOUND ); return FALSE; } /* * Convert DOS action flags into Win32 creation disposition parameter. */ switch(dosAction) { case 0x01: winMode = OPEN_EXISTING; break; case 0x02: winMode = TRUNCATE_EXISTING; break; case 0x10: winMode = CREATE_NEW; break; case 0x11: winMode = OPEN_ALWAYS; break; case 0x12: winMode = CREATE_ALWAYS; break; default: SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } /* * Convert DOS access/share flags into Win32 desired access parameter. */ switch(dosAccessShare & 0x07) { case OF_READ: winAccess = GENERIC_READ; break; case OF_WRITE: winAccess = GENERIC_WRITE; break; case OF_READWRITE: winAccess = GENERIC_READ | GENERIC_WRITE; break; case 0x04: /* * Read-only, do not modify file's last-access time (DOS7). * * FIXME: How to prevent modification of last-access time? */ winAccess = GENERIC_READ; break; default: winAccess = 0; } /* * Convert DOS access/share flags into Win32 share mode parameter. */ switch(dosAccessShare & 0x70) { case OF_SHARE_EXCLUSIVE: winSharing = 0; break; case OF_SHARE_DENY_WRITE: winSharing = FILE_SHARE_READ; break; case OF_SHARE_DENY_READ: winSharing = FILE_SHARE_WRITE; break; case OF_SHARE_DENY_NONE: case OF_SHARE_COMPAT: default: winSharing = FILE_SHARE_READ | FILE_SHARE_WRITE; } /* * FIXME: Bit (dosAccessShare & 0x80) represents inheritance. * What to do with this bit? * FIXME: Bits in the high byte of dosAccessShare are not supported. * See both function 0x6c and subfunction 0x6c of function 0x71 for * definition of these bits. */ /* * Convert DOS create attributes into Win32 flags and attributes parameter. */ if (winMode == OPEN_EXISTING || winMode == TRUNCATE_EXISTING) { winAttributes = 0; } else { WORD dosAttributes = CX_reg(context); if (dosAttributes & FA_LABEL) { /* * Application tried to create volume label entry. * This is difficult to support so we do not allow it. * * FIXME: If volume does not already have a label, * this function is supposed to succeed. */ SetLastError( ERROR_ACCESS_DENIED ); return TRUE; } winAttributes = dosAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE); } /* * Open the file. */ MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH); if ((winHandle = INT21_OpenMagicDevice( pathW, winAccess ))) { dosStatus = 1; } else { winHandle = CreateFileW( pathW, winAccess, winSharing, NULL, winMode, winAttributes, 0 ); /* DOS allows to open files on a CDROM R/W */ if( winHandle == INVALID_HANDLE_VALUE && GetLastError()== ERROR_WRITE_PROTECT) { winHandle = CreateFileW( pathW, winAccess & ~GENERIC_WRITE, winSharing, NULL, winMode, winAttributes, 0 ); } if (winHandle == INVALID_HANDLE_VALUE) return FALSE; /* * Determine DOS file status. * * 1 = file opened * 2 = file created * 3 = file replaced */ switch(winMode) { case OPEN_EXISTING: dosStatus = 1; break; case TRUNCATE_EXISTING: dosStatus = 3; break; case CREATE_NEW: dosStatus = 2; break; case OPEN_ALWAYS: dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 1 : 2; break; case CREATE_ALWAYS: dosStatus = (GetLastError() == ERROR_ALREADY_EXISTS) ? 3 : 2; break; default: dosStatus = 0; } } /* * Return DOS file handle and DOS status. */ SET_AX( context, Win32HandleToDosFileHandle(winHandle) ); if (returnStatus) SET_CX( context, dosStatus ); TRACE( "CreateFile finished: handle=%d, status=%d.\n", AX_reg(context), dosStatus ); return TRUE; } /*********************************************************************** * INT21_BufferedInput * * Handler for function 0x0a and reading from console using * function 0x3f. * * Reads a string of characters from standard input until * enter key is pressed. Returns either number of characters * read from console including terminating CR or * zero if capacity was zero. */ static WORD INT21_BufferedInput( CONTEXT86 *context, BYTE *ptr, WORD capacity ) { BYTE length = 0; /* * Return immediately if capacity is zero. */ if (capacity == 0) return 0; while(TRUE) { BYTE ascii; BYTE scan; DOSVM_Int16ReadChar( &ascii, &scan, context ); if (ascii == '\r' || ascii == '\n') { ptr[length] = '\r'; return length + 1; } /* * DOS handles only backspace and KEY_LEFT * perhaps we should do more */ if (ascii == '\b' || scan == KEY_LEFT) { if (length==0) continue; DOSVM_PutChar( '\b' ); length--; continue; } /* * If the buffer becomes filled to within one byte of * capacity, DOS rejects all further characters up to, * but not including, the terminating carriage return. */ if (ascii != 0 && length < capacity-1) { DOSVM_PutChar( ascii ); ptr[length] = ascii; length++; } } } /*********************************************************************** * INT21_GetCurrentDTA */ static BYTE *INT21_GetCurrentDTA( CONTEXT86 *context ) { TDB *pTask = GlobalLock16(GetCurrentTask()); /* FIXME: This assumes DTA was set correctly! */ return (BYTE *)CTX_SEG_OFF_TO_LIN( context, SELECTOROF(pTask->dta), (DWORD)OFFSETOF(pTask->dta) ); } /*********************************************************************** * INT21_OpenFileUsingFCB * * Handler for function 0x0f. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of unopened file * * RETURNS (in AL) * 0x00: successful * 0xff: failed * * NOTES * Opens a FCB file for read/write in compatibility mode. Upon calling * the FCB must have the drive_number, file_name, and file_extension * fields filled and all other bytes cleared. */ static void INT21_OpenFileUsingFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; char file_path[16]; char *pos; HANDLE handle; HFILE16 hfile16; BY_HANDLE_FILE_INFORMATION info; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ AL_result = 0; file_path[0] = 'A' + INT21_MapDrive( fcb->drive_number ); if (AL_result == 0) { file_path[1] = ':'; pos = &file_path[2]; memcpy(pos, fcb->file_name, 8); pos[8] = ' '; pos[9] = '\0'; pos = strchr(pos, ' '); *pos = '.'; pos++; memcpy(pos, fcb->file_extension, 3); pos[3] = ' '; pos[4] = '\0'; pos = strchr(pos, ' '); *pos = '\0'; handle = (HANDLE) _lopen(file_path, OF_READWRITE); if (handle == INVALID_HANDLE_VALUE) { TRACE("_lopen(\"%s\") failed: INVALID_HANDLE_VALUE\n", file_path); AL_result = 0xff; /* failed */ } else { hfile16 = Win32HandleToDosFileHandle(handle); if (hfile16 == HFILE_ERROR16) { TRACE("Win32HandleToDosFileHandle(%p) failed: HFILE_ERROR\n", handle); CloseHandle(handle); AL_result = 0xff; /* failed */ } else if (hfile16 > 255) { TRACE("hfile16 (=%d) larger than 255 for \"%s\"\n", hfile16, file_path); _lclose16(hfile16); AL_result = 0xff; /* failed */ } else { if (!GetFileInformationByHandle(handle, &info)) { TRACE("GetFileInformationByHandle(%d, %p) for \"%s\" failed\n", hfile16, handle, file_path); _lclose16(hfile16); AL_result = 0xff; /* failed */ } else { fcb->drive_number = file_path[0] - 'A' + 1; fcb->current_block_number = 0; fcb->logical_record_size = 128; fcb->file_size = info.nFileSizeLow; FileTimeToDosDateTime(&info.ftLastWriteTime, &fcb->date_of_last_write, &fcb->time_of_last_write); fcb->file_number = hfile16; fcb->attributes = 0xc2; fcb->starting_cluster = 0; /* don't know correct init value */ fcb->sequence_number = 0; /* don't know correct init value */ fcb->file_attributes = info.dwFileAttributes; /* The following fields are not initialized */ /* by the native function: */ /* unused */ /* record_within_current_block */ /* random_access_record_number */ TRACE("successful opened file \"%s\" as %d (handle %p)\n", file_path, hfile16, handle); AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ } /* if */ SET_AL(context, AL_result); } /*********************************************************************** * INT21_CloseFileUsingFCB * * Handler for function 0x10. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0x00: successful * 0xff: failed * * NOTES * Closes a FCB file. */ static void INT21_CloseFileUsingFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ if (_lclose16((HFILE16) fcb->file_number) != 0) { TRACE("_lclose16(%d) failed\n", fcb->file_number); AL_result = 0xff; /* failed */ } else { TRACE("successful closed file %d\n", fcb->file_number); AL_result = 0x00; /* successful */ } /* if */ SET_AL(context, AL_result); } /*********************************************************************** * INT21_SequentialReadFromFCB * * Handler for function 0x14. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: end of file, no data read * 2: segment wrap in DTA, no data read (not returned now) * 3: end of file, partial record read * * NOTES * Reads a record with the size FCB->logical_record_size from the FCB * to the disk transfer area. The position of the record is specified * with FCB->current_block_number and FCB->record_within_current_block. * Then FCB->current_block_number and FCB->record_within_current_block * are updated to point to the next record. If a partial record is * read, it is filled with zeros up to the FCB->logical_record_size. */ static void INT21_SequentialReadFromFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT bytes_read; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); AL_result = 0x01; /* end of file, no data read */ } else { record_number = 128 * fcb->current_block_number + fcb->record_within_current_block; position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); AL_result = 0x01; /* end of file, no data read */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size); if (bytes_read != fcb->logical_record_size) { TRACE("_lread(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read); if (bytes_read == 0) { AL_result = 0x01; /* end of file, no data read */ } else { memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read); AL_result = 0x03; /* end of file, partial record read */ } /* if */ } else { TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_read, record_number, position, fcb->file_number, handle); AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ if (AL_result == 0x00 || AL_result == 0x03) { if (fcb->record_within_current_block == 127) { fcb->record_within_current_block = 0; fcb->current_block_number++; } else { fcb->record_within_current_block++; } /* if */ } /* if */ SET_AL(context, AL_result); } /*********************************************************************** * INT21_SequentialWriteToFCB * * Handler for function 0x15. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: disk full * 2: segment wrap in DTA (not returned now) * * NOTES * Writes a record with the size FCB->logical_record_size from the disk * transfer area to the FCB. The position of the record is specified * with FCB->current_block_number and FCB->record_within_current_block. * Then FCB->current_block_number and FCB->record_within_current_block * are updated to point to the next record. */ static void INT21_SequentialWriteToFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT bytes_written; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); AL_result = 0x01; /* disk full */ } else { record_number = 128 * fcb->current_block_number + fcb->record_within_current_block; position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); AL_result = 0x01; /* disk full */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, fcb->logical_record_size); if (bytes_written != fcb->logical_record_size) { TRACE("_lwrite(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written); AL_result = 0x01; /* disk full */ } else { TRACE("successful written %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_written, record_number, position, fcb->file_number, handle); AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ if (AL_result == 0x00) { if (fcb->record_within_current_block == 127) { fcb->record_within_current_block = 0; fcb->current_block_number++; } else { fcb->record_within_current_block++; } /* if */ } /* if */ SET_AL(context, AL_result); } /*********************************************************************** * INT21_ReadRandomRecordFromFCB * * Handler for function 0x21. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: end of file, no data read * 2: segment wrap in DTA, no data read (not returned now) * 3: end of file, partial record read * * NOTES * Reads a record with the size FCB->logical_record_size from * the FCB to the disk transfer area. The position of the record * is specified with FCB->random_access_record_number. The * FCB->random_access_record_number is not updated. If a partial record * is read, it is filled with zeros up to the FCB->logical_record_size. */ static void INT21_ReadRandomRecordFromFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT bytes_read; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ memcpy(&record_number, fcb->random_access_record_number, 4); handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); AL_result = 0x01; /* end of file, no data read */ } else { position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); AL_result = 0x01; /* end of file, no data read */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); bytes_read = _lread((HFILE) handle, disk_transfer_area, fcb->logical_record_size); if (bytes_read != fcb->logical_record_size) { TRACE("_lread(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_read); if (bytes_read == 0) { AL_result = 0x01; /* end of file, no data read */ } else { memset(&disk_transfer_area[bytes_read], 0, fcb->logical_record_size - bytes_read); AL_result = 0x03; /* end of file, partial record read */ } /* if */ } else { TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_read, record_number, position, fcb->file_number, handle); AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ fcb->current_block_number = record_number / 128; fcb->record_within_current_block = record_number % 128; SET_AL(context, AL_result); } /*********************************************************************** * INT21_WriteRandomRecordToFCB * * Handler for function 0x22. * * PARAMS * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: disk full * 2: segment wrap in DTA (not returned now) * * NOTES * Writes a record with the size FCB->logical_record_size from * the disk transfer area to the FCB. The position of the record * is specified with FCB->random_access_record_number. The * FCB->random_access_record_number is not updated. */ static void INT21_WriteRandomRecordToFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT bytes_written; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ memcpy(&record_number, fcb->random_access_record_number, 4); handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); AL_result = 0x01; /* disk full */ } else { position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); AL_result = 0x01; /* disk full */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, fcb->logical_record_size); if (bytes_written != fcb->logical_record_size) { TRACE("_lwrite(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, fcb->logical_record_size, bytes_written); AL_result = 0x01; /* disk full */ } else { TRACE("successful written %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_written, record_number, position, fcb->file_number, handle); AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ fcb->current_block_number = record_number / 128; fcb->record_within_current_block = record_number % 128; SET_AL(context, AL_result); } /*********************************************************************** * INT21_RandomBlockReadFromFCB * * Handler for function 0x27. * * PARAMS * CX [I/O] Number of records to read * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: end of file, no data read * 2: segment wrap in DTA, no data read (not returned now) * 3: end of file, partial record read * * NOTES * Reads several records with the size FCB->logical_record_size from * the FCB to the disk transfer area. The number of records to be * read is specified in the CX register. The position of the first * record is specified with FCB->random_access_record_number. The * FCB->random_access_record_number, the FCB->current_block_number * and FCB->record_within_current_block are updated to point to the * next record after the records read. If a partial record is read, * it is filled with zeros up to the FCB->logical_record_size. The * CX register is set to the number of successfully read records. */ static void INT21_RandomBlockReadFromFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT records_requested; UINT bytes_requested; UINT bytes_read; UINT records_read; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ memcpy(&record_number, fcb->random_access_record_number, 4); handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); records_read = 0; AL_result = 0x01; /* end of file, no data read */ } else { position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); records_read = 0; AL_result = 0x01; /* end of file, no data read */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); records_requested = CX_reg(context); bytes_requested = (UINT) records_requested * fcb->logical_record_size; bytes_read = _lread((HFILE) handle, disk_transfer_area, bytes_requested); if (bytes_read != bytes_requested) { TRACE("_lread(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, bytes_requested, bytes_read); records_read = bytes_read / fcb->logical_record_size; if (bytes_read % fcb->logical_record_size == 0) { AL_result = 0x01; /* end of file, no data read */ } else { records_read++; memset(&disk_transfer_area[bytes_read], 0, records_read * fcb->logical_record_size - bytes_read); AL_result = 0x03; /* end of file, partial record read */ } /* if */ } else { TRACE("successful read %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_read, record_number, position, fcb->file_number, handle); records_read = records_requested; AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ record_number += records_read; memcpy(fcb->random_access_record_number, &record_number, 4); fcb->current_block_number = record_number / 128; fcb->record_within_current_block = record_number % 128; SET_CX(context, records_read); SET_AL(context, AL_result); } /*********************************************************************** * INT21_RandomBlockWriteToFCB * * Handler for function 0x28. * * PARAMS * CX [I/O] Number of records to write * DX:DX [I/O] File control block (FCB or XFCB) of open file * * RETURNS (in AL) * 0: successful * 1: disk full * 2: segment wrap in DTA (not returned now) * * NOTES * Writes several records with the size FCB->logical_record_size from * the disk transfer area to the FCB. The number of records to be * written is specified in the CX register. The position of the first * record is specified with FCB->random_access_record_number. The * FCB->random_access_record_number, the FCB->current_block_number * and FCB->record_within_current_block are updated to point to the * next record after the records written. The CX register is set to * the number of successfully written records. */ static void INT21_RandomBlockWriteToFCB( CONTEXT86 *context ) { struct FCB *fcb; struct XFCB *xfcb; HANDLE handle; DWORD record_number; long position; BYTE *disk_transfer_area; UINT records_requested; UINT bytes_requested; UINT bytes_written; UINT records_written; BYTE AL_result; fcb = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (fcb->drive_number == 0xff) { xfcb = (struct XFCB *) fcb; fcb = (struct FCB *) xfcb->fcb; } /* if */ memcpy(&record_number, fcb->random_access_record_number, 4); handle = DosFileHandleToWin32Handle((HFILE16) fcb->file_number); if (handle == INVALID_HANDLE_VALUE) { TRACE("DosFileHandleToWin32Handle(%d) failed: INVALID_HANDLE_VALUE\n", fcb->file_number); records_written = 0; AL_result = 0x01; /* disk full */ } else { position = SetFilePointer(handle, record_number * fcb->logical_record_size, NULL, 0); if (position != record_number * fcb->logical_record_size) { TRACE("seek(%d, %d, 0) failed with %ld\n", fcb->file_number, record_number * fcb->logical_record_size, position); records_written = 0; AL_result = 0x01; /* disk full */ } else { disk_transfer_area = INT21_GetCurrentDTA(context); records_requested = CX_reg(context); bytes_requested = (UINT) records_requested * fcb->logical_record_size; bytes_written = _lwrite((HFILE) handle, (LPCSTR)disk_transfer_area, bytes_requested); if (bytes_written != bytes_requested) { TRACE("_lwrite(%d, %p, %d) failed with %d\n", fcb->file_number, disk_transfer_area, bytes_requested, bytes_written); records_written = bytes_written / fcb->logical_record_size; AL_result = 0x01; /* disk full */ } else { TRACE("successful write %d bytes from record %d (position %ld) of file %d (handle %p)\n", bytes_written, record_number, position, fcb->file_number, handle); records_written = records_requested; AL_result = 0x00; /* successful */ } /* if */ } /* if */ } /* if */ record_number += records_written; memcpy(fcb->random_access_record_number, &record_number, 4); fcb->current_block_number = record_number / 128; fcb->record_within_current_block = record_number % 128; SET_CX(context, records_written); SET_AL(context, AL_result); } /*********************************************************************** * INT21_CreateDirectory * * Handler for: * - function 0x39 * - subfunction 0x39 of function 0x71 * - subfunction 0xff of function 0x43 (CL == 0x39) */ static BOOL INT21_CreateDirectory( CONTEXT86 *context ) { WCHAR dirW[MAX_PATH]; char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "CREATE DIRECTORY %s\n", dirA ); MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH); if (CreateDirectoryW(dirW, NULL)) return TRUE; /* * FIXME: CreateDirectory's LastErrors will clash with the ones * used by DOS. AH=39 only returns 3 (path not found) and * 5 (access denied), while CreateDirectory return several * ones. Remap some of them. -Marcus */ switch (GetLastError()) { case ERROR_ALREADY_EXISTS: case ERROR_FILENAME_EXCED_RANGE: case ERROR_DISK_FULL: SetLastError(ERROR_ACCESS_DENIED); break; default: break; } return FALSE; } /*********************************************************************** * INT21_ExtendedCountryInformation * * Handler for function 0x65. */ static void INT21_ExtendedCountryInformation( CONTEXT86 *context ) { BYTE *dataptr = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi ); BYTE buffsize = CX_reg (context); TRACE( "GET EXTENDED COUNTRY INFORMATION, subfunction %02x\n", AL_reg(context) ); /* * Check subfunctions that are passed country and code page. */ if (AL_reg(context) >= 0x01 && AL_reg(context) <= 0x07) { WORD country = DX_reg(context); WORD codepage = BX_reg(context); if (country != 0xffff && country != INT21_GetSystemCountryCode()) FIXME( "Requested info on non-default country %04x\n", country ); if (codepage != 0xffff && codepage != GetOEMCP()) FIXME( "Requested info on non-default code page %04x\n", codepage ); } switch (AL_reg(context)) { case 0x00: /* SET GENERAL INTERNATIONALIZATION INFO */ INT_BARF( context, 0x21 ); SET_CFLAG( context ); break; case 0x01: /* GET GENERAL INTERNATIONALIZATION INFO */ TRACE( "Get general internationalization info\n" ); dataptr[0] = 0x01; /* Info ID */ *(WORD*)(dataptr+1) = 38; /* Size of the following info */ *(WORD*)(dataptr+3) = INT21_GetSystemCountryCode(); /* Country ID */ *(WORD*)(dataptr+5) = GetOEMCP(); /* Code page */ /* FIXME: fill buffer partially up to buffsize bytes*/ if (buffsize >= 0x29){ INT21_FillCountryInformation( dataptr + 7 ); SET_CX( context, 0x29 ); /* Size of returned info */ }else{ SET_CX( context, 0x07 ); /* Size of returned info */ } break; case 0x02: /* GET POINTER TO UPPERCASE TABLE */ case 0x04: /* GET POINTER TO FILENAME UPPERCASE TABLE */ TRACE( "Get pointer to uppercase table\n" ); dataptr[0] = AL_reg(context); /* Info ID */ *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ), offsetof(INT21_HEAP, uppercase_size) ); SET_CX( context, 5 ); /* Size of returned info */ break; case 0x03: /* GET POINTER TO LOWERCASE TABLE */ TRACE( "Get pointer to lowercase table\n" ); dataptr[0] = 0x03; /* Info ID */ *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ), offsetof(INT21_HEAP, lowercase_size) ); SET_CX( context, 5 ); /* Size of returned info */ break; case 0x05: /* GET POINTER TO FILENAME TERMINATOR TABLE */ TRACE("Get pointer to filename terminator table\n"); dataptr[0] = 0x05; /* Info ID */ *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ), offsetof(INT21_HEAP, filename_size) ); SET_CX( context, 5 ); /* Size of returned info */ break; case 0x06: /* GET POINTER TO COLLATING SEQUENCE TABLE */ TRACE("Get pointer to collating sequence table\n"); dataptr[0] = 0x06; /* Info ID */ *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ), offsetof(INT21_HEAP, collating_size) ); SET_CX( context, 5 ); /* Size of returned info */ break; case 0x07: /* GET POINTER TO DBCS LEAD BYTE TABLE */ TRACE("Get pointer to DBCS lead byte table\n"); dataptr[0] = 0x07; /* Info ID */ *(DWORD*)(dataptr+1) = MAKESEGPTR( INT21_GetHeapSelector( context ), offsetof(INT21_HEAP, dbcs_size) ); SET_CX( context, 5 ); /* Size of returned info */ break; case 0x20: /* CAPITALIZE CHARACTER */ case 0xa0: /* CAPITALIZE FILENAME CHARACTER */ TRACE("Convert char to uppercase\n"); SET_DL( context, toupper(DL_reg(context)) ); break; case 0x21: /* CAPITALIZE STRING */ case 0xa1: /* CAPITALIZE COUNTED FILENAME STRING */ TRACE("Convert string to uppercase with length\n"); { char *ptr = (char *)CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx ); WORD len = CX_reg(context); while (len--) { *ptr = toupper(*ptr); ptr++; } } break; case 0x22: /* CAPITALIZE ASCIIZ STRING */ case 0xa2: /* CAPITALIZE ASCIIZ FILENAME */ TRACE("Convert ASCIIZ string to uppercase\n"); { char *p = CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx ); for ( ; *p; p++) *p = toupper(*p); } break; case 0x23: /* DETERMINE IF CHARACTER REPRESENTS YES/NO RESPONSE */ INT_BARF( context, 0x21 ); SET_CFLAG( context ); break; default: INT_BARF( context, 0x21 ); SET_CFLAG(context); break; } } /*********************************************************************** * INT21_FileAttributes * * Handler for: * - function 0x43 * - subfunction 0x43 of function 0x71 */ static BOOL INT21_FileAttributes( CONTEXT86 *context, BYTE subfunction, BOOL islong ) { WCHAR fileW[MAX_PATH]; char *fileA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); HANDLE handle; BOOL status; FILETIME filetime; DWORD result; WORD date, time; switch (subfunction) { case 0x00: /* GET FILE ATTRIBUTES */ TRACE( "GET FILE ATTRIBUTES for %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); result = GetFileAttributesW( fileW ); if (result == INVALID_FILE_ATTRIBUTES) return FALSE; else { SET_CX( context, (WORD)result ); if (!islong) SET_AX( context, (WORD)result ); /* DR DOS */ } break; case 0x01: /* SET FILE ATTRIBUTES */ TRACE( "SET FILE ATTRIBUTES 0x%02x for %s\n", CX_reg(context), fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); if (!SetFileAttributesW( fileW, CX_reg(context) )) return FALSE; break; case 0x02: /* GET COMPRESSED FILE SIZE */ TRACE( "GET COMPRESSED FILE SIZE for %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); result = GetCompressedFileSizeW( fileW, NULL ); if (result == INVALID_FILE_SIZE) return FALSE; else { SET_AX( context, LOWORD(result) ); SET_DX( context, HIWORD(result) ); } break; case 0x03: /* SET FILE LAST-WRITTEN DATE AND TIME */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); handle = CreateFileW( fileW, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; DosDateTimeToFileTime( DI_reg(context), CX_reg(context), &filetime ); status = SetFileTime( handle, NULL, NULL, &filetime ); CloseHandle( handle ); return status; } break; case 0x04: /* GET FILE LAST-WRITTEN DATE AND TIME */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, file %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); handle = CreateFileW( fileW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; status = GetFileTime( handle, NULL, NULL, &filetime ); if (status) { FileTimeToDosDateTime( &filetime, &date, &time ); SET_DI( context, date ); SET_CX( context, time ); } CloseHandle( handle ); return status; } break; case 0x05: /* SET FILE LAST ACCESS DATE */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "SET FILE LAST ACCESS DATE, file %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); handle = CreateFileW( fileW, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; DosDateTimeToFileTime( DI_reg(context), 0, &filetime ); status = SetFileTime( handle, NULL, &filetime, NULL ); CloseHandle( handle ); return status; } break; case 0x06: /* GET FILE LAST ACCESS DATE */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "GET FILE LAST ACCESS DATE, file %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); handle = CreateFileW( fileW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; status = GetFileTime( handle, NULL, &filetime, NULL ); if (status) { FileTimeToDosDateTime( &filetime, &date, NULL ); SET_DI( context, date ); } CloseHandle( handle ); return status; } break; case 0x07: /* SET FILE CREATION DATE AND TIME */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "SET FILE CREATION DATE AND TIME, file %s\n", fileA ); handle = CreateFileW( fileW, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; /* * FIXME: SI has number of 10-millisecond units past time in CX. */ DosDateTimeToFileTime( DI_reg(context), CX_reg(context), &filetime ); status = SetFileTime( handle, &filetime, NULL, NULL ); CloseHandle( handle ); return status; } break; case 0x08: /* GET FILE CREATION DATE AND TIME */ if (!islong) INT_BARF( context, 0x21 ); else { TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n", BX_reg(context) ); handle = CreateFileW( fileW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle == INVALID_HANDLE_VALUE) return FALSE; status = GetFileTime( handle, &filetime, NULL, NULL ); if (status) { FileTimeToDosDateTime( &filetime, &date, &time ); SET_DI( context, date ); SET_CX( context, time ); /* * FIXME: SI has number of 10-millisecond units past * time in CX. */ SET_SI( context, 0 ); } CloseHandle(handle); return status; } break; case 0xff: /* EXTENDED-LENGTH FILENAME OPERATIONS */ if (islong || context->Ebp != 0x5053) INT_BARF( context, 0x21 ); else { switch(CL_reg(context)) { case 0x39: if (!INT21_CreateDirectory( context )) return FALSE; break; case 0x56: if (!INT21_RenameFile( context )) return FALSE; break; default: INT_BARF( context, 0x21 ); } } break; default: INT_BARF( context, 0x21 ); } return TRUE; } /*********************************************************************** * INT21_FileDateTime * * Handler for function 0x57. */ static BOOL INT21_FileDateTime( CONTEXT86 *context ) { HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context)); FILETIME filetime; WORD date, time; switch (AL_reg(context)) { case 0x00: /* Get last-written stamp */ TRACE( "GET FILE LAST-WRITTEN DATE AND TIME, handle %d\n", BX_reg(context) ); { if (!GetFileTime( handle, NULL, NULL, &filetime )) return FALSE; FileTimeToDosDateTime( &filetime, &date, &time ); SET_DX( context, date ); SET_CX( context, time ); break; } case 0x01: /* Set last-written stamp */ TRACE( "SET FILE LAST-WRITTEN DATE AND TIME, handle %d\n", BX_reg(context) ); { DosDateTimeToFileTime( DX_reg(context), CX_reg(context), &filetime ); if (!SetFileTime( handle, NULL, NULL, &filetime )) return FALSE; break; } case 0x04: /* Get last access stamp, DOS 7 */ TRACE( "GET FILE LAST ACCESS DATE AND TIME, handle %d\n", BX_reg(context) ); { if (!GetFileTime( handle, NULL, &filetime, NULL )) return FALSE; FileTimeToDosDateTime( &filetime, &date, &time ); SET_DX( context, date ); SET_CX( context, time ); break; } case 0x05: /* Set last access stamp, DOS 7 */ TRACE( "SET FILE LAST ACCESS DATE AND TIME, handle %d\n", BX_reg(context) ); { DosDateTimeToFileTime( DX_reg(context), CX_reg(context), &filetime ); if (!SetFileTime( handle, NULL, &filetime, NULL )) return FALSE; break; } case 0x06: /* Get creation stamp, DOS 7 */ TRACE( "GET FILE CREATION DATE AND TIME, handle %d\n", BX_reg(context) ); { if (!GetFileTime( handle, &filetime, NULL, NULL )) return FALSE; FileTimeToDosDateTime( &filetime, &date, &time ); SET_DX( context, date ); SET_CX( context, time ); /* * FIXME: SI has number of 10-millisecond units past time in CX. */ SET_SI( context, 0 ); break; } case 0x07: /* Set creation stamp, DOS 7 */ TRACE( "SET FILE CREATION DATE AND TIME, handle %d\n", BX_reg(context) ); { /* * FIXME: SI has number of 10-millisecond units past time in CX. */ DosDateTimeToFileTime( DX_reg(context), CX_reg(context), &filetime ); if (!SetFileTime( handle, &filetime, NULL, NULL )) return FALSE; break; } default: INT_BARF( context, 0x21 ); break; } return TRUE; } /*********************************************************************** * INT21_GetPSP * * Handler for functions 0x51 and 0x62. */ static void INT21_GetPSP( CONTEXT86 *context ) { TRACE( "GET CURRENT PSP ADDRESS (%02x)\n", AH_reg(context) ); /* * FIXME: should we return the original DOS PSP upon * Windows startup ? */ if (!ISV86(context) && DOSVM_IsWin16()) SET_BX( context, LOWORD(GetCurrentPDB16()) ); else SET_BX( context, DOSVM_psp ); } static inline void setword( BYTE *ptr, WORD w ) { ptr[0] = (BYTE)w; ptr[1] = (BYTE)(w >> 8); } static void CreateBPB(int drive, BYTE *data, BOOL16 limited) /* limited == TRUE is used with INT 0x21/0x440d */ { /* FIXME: we're forcing some values without checking that those are valid */ if (drive > 1) { setword(data, 512); data[2] = 2; setword(&data[3], 0); data[5] = 2; setword(&data[6], 240); setword(&data[8], 64000); data[0x0a] = 0xf8; setword(&data[0x0b], 40); setword(&data[0x0d], 56); setword(&data[0x0f], 2); setword(&data[0x11], 0); if (!limited) { setword(&data[0x1f], 800); data[0x21] = 5; setword(&data[0x22], 1); } } else { /* 1.44mb */ setword(data, 512); data[2] = 2; setword(&data[3], 0); data[5] = 2; setword(&data[6], 240); setword(&data[8], 2880); data[0x0a] = 0xf8; setword(&data[0x0b], 6); setword(&data[0x0d], 18); setword(&data[0x0f], 2); setword(&data[0x11], 0); if (!limited) { setword(&data[0x1f], 80); data[0x21] = 7; setword(&data[0x22], 2); } } } static inline DWORD INT21_Ioctl_CylHeadSect2Lin(DWORD cyl, WORD head, WORD sec, WORD cyl_cnt, WORD head_cnt, WORD sec_cnt) { DWORD res = (cyl * head_cnt*sec_cnt + head * sec_cnt + sec); return res; } /*********************************************************************** * INT21_Ioctl_Block * * Handler for block device IOCTLs. */ static void INT21_Ioctl_Block( CONTEXT86 *context ) { BYTE *dataptr; BYTE drive = INT21_MapDrive( BL_reg(context) ); WCHAR drivespec[4] = {'A', ':', '\\', 0}; UINT drivetype; drivespec[0] += drive; drivetype = GetDriveTypeW( drivespec ); RESET_CFLAG(context); if (drivetype == DRIVE_UNKNOWN || drivetype == DRIVE_NO_ROOT_DIR) { TRACE( "IOCTL - SUBFUNCTION %d - INVALID DRIVE %c:\n", AL_reg(context), 'A' + drive ); SetLastError( ERROR_INVALID_DRIVE ); SET_AX( context, ERROR_INVALID_DRIVE ); SET_CFLAG( context ); return; } switch (AL_reg(context)) { case 0x04: /* READ FROM BLOCK DEVICE CONTROL CHANNEL */ case 0x05: /* WRITE TO BLOCK DEVICE CONTROL CHANNEL */ INT_BARF( context, 0x21 ); break; case 0x08: /* CHECK IF BLOCK DEVICE REMOVABLE */ TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOVABLE - %c:\n", 'A' + drive ); if (drivetype == DRIVE_REMOVABLE) SET_AX( context, 0 ); /* removable */ else SET_AX( context, 1 ); /* not removable */ break; case 0x09: /* CHECK IF BLOCK DEVICE REMOTE */ TRACE( "IOCTL - CHECK IF BLOCK DEVICE REMOTE - %c:\n", 'A' + drive ); if (drivetype == DRIVE_REMOTE) SET_DX( context, (1<<9) | (1<<12) ); /* remote + no direct IO */ else SET_DX( context, 0 ); /* FIXME: use driver attr here */ break; case 0x0d: /* GENERIC BLOCK DEVICE REQUEST */ /* Get pointer to IOCTL parameter block */ dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); switch (CX_reg(context)) { case 0x0841: /* write logical device track */ TRACE( "GENERIC IOCTL - Write logical device track - %c:\n", 'A' + drive); { WORD head = *(WORD *)(dataptr+1); WORD cyl = *(WORD *)(dataptr+3); WORD sect = *(WORD *)(dataptr+5); WORD nrsect = *(WORD *)(dataptr+7); BYTE *data = CTX_SEG_OFF_TO_LIN(context, *(WORD *)(dataptr+11), *(WORD *)(dataptr+9)); WORD cyl_cnt, head_cnt, sec_cnt; /* FIXME: we're faking some values here */ if (drive > 1) { /* cyl_cnt = 0x300; head_cnt = 16; sec_cnt = 255; */ SET_AX( context, ERROR_WRITE_FAULT ); SET_CFLAG(context); break; } else { /* floppy */ cyl_cnt = 80; head_cnt = 2; sec_cnt = 18; } if (!DOSVM_RawWrite(drive, INT21_Ioctl_CylHeadSect2Lin(cyl, head, sect, cyl_cnt, head_cnt, sec_cnt), nrsect, data, FALSE)) { SET_AX( context, ERROR_WRITE_FAULT ); SET_CFLAG(context); } } break; case 0x084a: /* lock logical volume */ TRACE( "GENERIC IOCTL - Lock logical volume, level %d mode %d - %c:\n", BH_reg(context), DX_reg(context), 'A' + drive ); break; case 0x0860: /* get device parameters */ /* FIXME: we're faking some values here */ /* used by w4wgrp's winfile */ memset(dataptr, 0, 0x20); /* DOS 6.22 uses 0x20 bytes */ dataptr[0] = 0x04; dataptr[6] = 0; /* media type */ if (drive > 1) { dataptr[1] = 0x05; /* fixed disk */ setword(&dataptr[2], 0x01); /* non removable */ setword(&dataptr[4], 0x300); /* # of cylinders */ } else { dataptr[1] = 0x07; /* block dev, floppy */ setword(&dataptr[2], 0x02); /* removable */ setword(&dataptr[4], 80); /* # of cylinders */ } CreateBPB(drive, &dataptr[7], TRUE); RESET_CFLAG(context); break; case 0x0861: /* read logical device track */ TRACE( "GENERIC IOCTL - Read logical device track - %c:\n", 'A' + drive); { WORD head = *(WORD *)(dataptr+1); WORD cyl = *(WORD *)(dataptr+3); WORD sect = *(WORD *)(dataptr+5); WORD nrsect = *(WORD *)(dataptr+7); BYTE *data = CTX_SEG_OFF_TO_LIN(context, *(WORD *)(dataptr+11), *(WORD *)(dataptr+9)); WORD cyl_cnt, head_cnt, sec_cnt; /* FIXME: we're faking some values here */ if (drive > 1) { cyl_cnt = 0x300; head_cnt = 16; sec_cnt = 255; } else { /* floppy */ cyl_cnt = 80; head_cnt = 2; sec_cnt = 18; } if (!DOSVM_RawRead(drive, INT21_Ioctl_CylHeadSect2Lin(cyl, head, sect, cyl_cnt, head_cnt, sec_cnt), nrsect, data, FALSE)) { SET_AX( context, ERROR_READ_FAULT ); SET_CFLAG(context); } } break; case 0x0866: /* get volume serial number */ { WCHAR label[12],fsname[9]; DWORD serial; drivespec[0] += drive; GetVolumeInformationW(drivespec, label, 12, &serial, NULL, NULL, fsname, 9); *(WORD*)dataptr = 0; memcpy(dataptr+2,&serial,4); WideCharToMultiByte(CP_OEMCP, 0, label, 11, (LPSTR)dataptr + 6, 11, NULL, NULL); WideCharToMultiByte(CP_OEMCP, 0, fsname, 8, (LPSTR)dataptr + 17, 8, NULL, NULL); } break; case 0x086a: /* unlock logical volume */ TRACE( "GENERIC IOCTL - Logical volume unlocked - %c:\n", 'A' + drive ); break; case 0x086f: /* get drive map information */ memset(dataptr+1, '\0', dataptr[0]-1); dataptr[1] = dataptr[0]; dataptr[2] = 0x07; /* protected mode driver; no eject; no notification */ dataptr[3] = 0xFF; /* no physical drive */ break; case 0x0872: /* Trial and error implementation */ SET_AX( context, drivetype == DRIVE_UNKNOWN ? 0x0f : 0x01 ); SET_CFLAG(context); /* Seems to be set all the time */ break; default: INT_BARF( context, 0x21 ); } break; case 0x0e: /* GET LOGICAL DRIVE MAP */ TRACE( "IOCTL - GET LOGICAL DRIVE MAP - %c:\n", 'A' + drive ); /* FIXME: this is not correct if drive has mappings */ SET_AL( context, 0 ); /* drive has no mapping */ break; case 0x0f: /* SET LOGICAL DRIVE MAP */ TRACE("IOCTL - SET LOGICAL DRIVE MAP for drive %s\n", INT21_DriveName( BL_reg(context))); /* FIXME: as of today, we don't support logical drive mapping... */ SET_AL( context, 0 ); break; case 0x11: /* QUERY GENERIC IOCTL CAPABILITY */ default: INT_BARF( context, 0x21 ); } } /*********************************************************************** * INT21_IoctlScsiMgrHandler * * IOCTL handler for the SCSIMGR device. */ static void INT21_IoctlScsiMgrHandler( CONTEXT86 *context ) { switch (AL_reg(context)) { case 0x00: /* GET DEVICE INFORMATION */ SET_DX( context, 0xc0c0 ); break; case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */ DOSVM_ASPIHandler(context); break; case 0x0a: /* CHECK IF HANDLE IS REMOTE */ SET_DX( context, 0 ); break; case 0x01: /* SET DEVICE INFORMATION */ case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */ case 0x06: /* GET INPUT STATUS */ case 0x07: /* GET OUTPUT STATUS */ case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */ case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */ default: INT_BARF( context, 0x21 ); break; } } /*********************************************************************** * INT21_IoctlEMSHandler * * IOCTL handler for the EMXXXX0 device. */ static void INT21_IoctlEMSHandler( CONTEXT86 *context ) { EMS_Ioctl_Handler(context); } /*********************************************************************** * INT21_IoctlHPScanHandler * * IOCTL handler for the HPSCAN device. */ static void INT21_IoctlHPScanHandler( CONTEXT86 *context ) { switch (AL_reg(context)) { case 0x00: /* GET DEVICE INFORMATION */ SET_DX( context, 0xc0c0 ); break; case 0x0a: /* CHECK IF HANDLE IS REMOTE */ SET_DX( context, 0 ); break; case 0x01: /* SET DEVICE INFORMATION */ case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */ case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */ case 0x06: /* GET INPUT STATUS */ case 0x07: /* GET OUTPUT STATUS */ case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */ case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */ default: INT_BARF( context, 0x21 ); break; } } /*********************************************************************** * INT21_Ioctl_Char * * Handler for character device IOCTLs. */ static void INT21_Ioctl_Char( CONTEXT86 *context ) { int status, i; int IsConsoleIOHandle = 0; IO_STATUS_BLOCK io; FILE_INTERNAL_INFORMATION info; HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context)); status = NtQueryInformationFile( handle, &io, &info, sizeof(info), FileInternalInformation ); if (status) { if( VerifyConsoleIoHandle( handle)) IsConsoleIOHandle = 1; else { SET_AX( context, RtlNtStatusToDosError(status) ); SET_CFLAG( context ); return; } } else { for (i = 0; i < NB_MAGIC_DEVICES; i++) { if (!magic_devices[i].handle) continue; if (magic_devices[i].index.QuadPart == info.IndexNumber.QuadPart) { /* found it */ magic_devices[i].ioctl_handler( context ); return; } } } /* no magic device found, do default handling */ switch (AL_reg(context)) { case 0x00: /* GET DEVICE INFORMATION */ TRACE( "IOCTL - GET DEVICE INFORMATION - %d\n", BX_reg(context) ); if (IsConsoleIOHandle || GetFileType(handle) == FILE_TYPE_CHAR) { /* * Returns attribute word in DX: * Bit 14 - Device driver can process IOCTL requests. * Bit 13 - Output until busy supported. * Bit 11 - Driver supports OPEN/CLOSE calls. * Bit 8 - Unknown. * Bit 7 - Set (indicates device). * Bit 6 - EOF on input. * Bit 5 - Raw (binary) mode. * Bit 4 - Device is special (uses int29). * Bit 3 - Clock device. * Bit 2 - NUL device. * Bit 1 - Console output device. * Bit 0 - Console input device. */ SET_DX( context, IsConsoleIOHandle ? 0x80c3 : 0x80c0 /* FIXME */ ); } else { /* * Returns attribute word in DX: * Bit 15 - File is remote. * Bit 14 - Don't set file date/time on closing. * Bit 11 - Media not removable. * Bit 8 - Generate int24 if no disk space on write * or read past end of file * Bit 7 - Clear (indicates file). * Bit 6 - File has not been written. * Bit 5..0 - Drive number (0=A:,...) * * FIXME: Should check if file is on remote or removable drive. * FIXME: Should use drive file is located on (and not current). */ SET_DX( context, 0x0140 + INT21_GetCurrentDrive() ); } break; case 0x0a: /* CHECK IF HANDLE IS REMOTE */ TRACE( "IOCTL - CHECK IF HANDLE IS REMOTE - %d\n", BX_reg(context) ); /* * Returns attribute word in DX: * Bit 15 - Set if remote. * Bit 14 - Set if date/time not set on close. * * FIXME: Should check if file is on remote drive. */ SET_DX( context, 0 ); break; case 0x01: /* SET DEVICE INFORMATION */ case 0x02: /* READ FROM CHARACTER DEVICE CONTROL CHANNEL */ case 0x03: /* WRITE TO CHARACTER DEVICE CONTROL CHANNEL */ case 0x06: /* GET INPUT STATUS */ case 0x07: /* GET OUTPUT STATUS */ case 0x0c: /* GENERIC CHARACTER DEVICE REQUEST */ case 0x10: /* QUERY GENERIC IOCTL CAPABILITY */ default: INT_BARF( context, 0x21 ); break; } } /*********************************************************************** * INT21_Ioctl * * Handler for function 0x44. */ static void INT21_Ioctl( CONTEXT86 *context ) { switch (AL_reg(context)) { case 0x00: case 0x01: case 0x02: case 0x03: INT21_Ioctl_Char( context ); break; case 0x04: case 0x05: INT21_Ioctl_Block( context ); break; case 0x06: case 0x07: INT21_Ioctl_Char( context ); break; case 0x08: case 0x09: INT21_Ioctl_Block( context ); break; case 0x0a: INT21_Ioctl_Char( context ); break; case 0x0b: /* SET SHARING RETRY COUNT */ TRACE( "SET SHARING RETRY COUNT: Pause %d, retries %d.\n", CX_reg(context), DX_reg(context) ); if (!CX_reg(context)) { SET_AX( context, 1 ); SET_CFLAG( context ); } else { DOSDEV_SetSharingRetry( CX_reg(context), DX_reg(context) ); RESET_CFLAG( context ); } break; case 0x0c: INT21_Ioctl_Char( context ); break; case 0x0d: case 0x0e: case 0x0f: INT21_Ioctl_Block( context ); break; case 0x10: INT21_Ioctl_Char( context ); break; case 0x11: INT21_Ioctl_Block( context ); break; case 0x12: /* DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION) */ TRACE( "DR DOS - DETERMINE DOS TYPE (OBSOLETE FUNCTION)\n" ); SET_CFLAG(context); /* Error / This is not DR DOS. */ SET_AX( context, 0x0001 ); /* Invalid function */ break; case 0x52: /* DR DOS - DETERMINE DOS TYPE */ TRACE( "DR DOS - DETERMINE DOS TYPE\n" ); SET_CFLAG(context); /* Error / This is not DR DOS. */ SET_AX( context, 0x0001 ); /* Invalid function */ break; case 0xe0: /* Sun PC-NFS API */ TRACE( "Sun PC-NFS API\n" ); /* not installed */ break; default: INT_BARF( context, 0x21 ); } } /*********************************************************************** * INT21_Fat32 * * Handler for function 0x73. */ static BOOL INT21_Fat32( CONTEXT86 *context ) { switch (AL_reg(context)) { case 0x02: /* FAT32 - GET EXTENDED DPB */ { BYTE drive = INT21_MapDrive( DL_reg(context) ); WORD *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi); INT21_DPB *target = (INT21_DPB*)(ptr + 1); INT21_DPB *source; TRACE( "FAT32 - GET EXTENDED DPB %d\n", DL_reg(context) ); if ( CX_reg(context) < sizeof(INT21_DPB) + 2 || *ptr < sizeof(INT21_DPB) ) { SetLastError( ERROR_BAD_LENGTH ); return FALSE; } if ( !INT21_FillDrivePB( drive ) ) { SetLastError( ERROR_INVALID_DRIVE ); return FALSE; } source = &INT21_GetHeapPointer()->misc_dpb_list[drive]; *ptr = sizeof(INT21_DPB); memcpy( target, source, sizeof(INT21_DPB)); if (LOWORD(context->Esi) != 0xF1A6) { target->driver_header = 0; target->next = 0; } else { FIXME( "Caller requested driver and next DPB pointers!\n" ); } } break; case 0x03: /* FAT32 - GET EXTENDED FREE SPACE ON DRIVE */ { WCHAR dirW[MAX_PATH]; char *dirA = CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx ); BYTE *data = CTX_SEG_OFF_TO_LIN( context, context->SegEs, context->Edi ); DWORD cluster_sectors; DWORD sector_bytes; DWORD free_clusters; DWORD total_clusters; TRACE( "FAT32 - GET EXTENDED FREE SPACE ON DRIVE %s\n", dirA ); MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH); if (CX_reg(context) < 44) { SetLastError( ERROR_BAD_LENGTH ); return FALSE; } if (!GetDiskFreeSpaceW( dirW, &cluster_sectors, §or_bytes, &free_clusters, &total_clusters )) return FALSE; *(WORD*) (data + 0) = 44; /* size of structure */ *(WORD*) (data + 2) = 0; /* version */ *(DWORD*)(data + 4) = cluster_sectors; *(DWORD*)(data + 8) = sector_bytes; *(DWORD*)(data + 12) = free_clusters; *(DWORD*)(data + 16) = total_clusters; /* * Below we have free/total sectors and * free/total allocation units without adjustment * for compression. We fake both using cluster information. */ *(DWORD*)(data + 20) = free_clusters * cluster_sectors; *(DWORD*)(data + 24) = total_clusters * cluster_sectors; *(DWORD*)(data + 28) = free_clusters; *(DWORD*)(data + 32) = total_clusters; /* * Between (data + 36) and (data + 43) there * are eight reserved bytes. */ } break; default: INT_BARF( context, 0x21 ); } return TRUE; } static void INT21_ConvertFindDataWtoA(WIN32_FIND_DATAA *dataA, const WIN32_FIND_DATAW *dataW) { dataA->dwFileAttributes = dataW->dwFileAttributes; dataA->ftCreationTime = dataW->ftCreationTime; dataA->ftLastAccessTime = dataW->ftLastAccessTime; dataA->ftLastWriteTime = dataW->ftLastWriteTime; dataA->nFileSizeHigh = dataW->nFileSizeHigh; dataA->nFileSizeLow = dataW->nFileSizeLow; WideCharToMultiByte( CP_OEMCP, 0, dataW->cFileName, -1, dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL ); WideCharToMultiByte( CP_OEMCP, 0, dataW->cAlternateFileName, -1, dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL ); } /*********************************************************************** * INT21_LongFilename * * Handler for function 0x71. */ static void INT21_LongFilename( CONTEXT86 *context ) { BOOL bSetDOSExtendedError = FALSE; WCHAR pathW[MAX_PATH]; char* pathA; if (HIBYTE(HIWORD(GetVersion16())) < 0x07) { TRACE( "LONG FILENAME - functions supported only under DOS7\n" ); SET_CFLAG( context ); SET_AL( context, 0 ); return; } switch (AL_reg(context)) { case 0x0d: /* RESET DRIVE */ INT_BARF( context, 0x21 ); break; case 0x39: /* LONG FILENAME - MAKE DIRECTORY */ if (!INT21_CreateDirectory( context )) bSetDOSExtendedError = TRUE; break; case 0x3a: /* LONG FILENAME - REMOVE DIRECTORY */ pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "LONG FILENAME - REMOVE DIRECTORY %s\n", pathA); MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH); if (!RemoveDirectoryW( pathW )) bSetDOSExtendedError = TRUE; break; case 0x3b: /* LONG FILENAME - CHANGE DIRECTORY */ if (!INT21_SetCurrentDirectory( context )) bSetDOSExtendedError = TRUE; break; case 0x41: /* LONG FILENAME - DELETE FILE */ pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "LONG FILENAME - DELETE FILE %s\n", pathA ); MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH); if (!DeleteFileW( pathW )) bSetDOSExtendedError = TRUE; break; case 0x43: /* LONG FILENAME - EXTENDED GET/SET FILE ATTRIBUTES */ if (!INT21_FileAttributes( context, BL_reg(context), TRUE )) bSetDOSExtendedError = TRUE; break; case 0x47: /* LONG FILENAME - GET CURRENT DIRECTORY */ if (!INT21_GetCurrentDirectory( context, TRUE )) bSetDOSExtendedError = TRUE; break; case 0x4e: /* LONG FILENAME - FIND FIRST MATCHING FILE */ { HANDLE handle; HGLOBAL16 h16; WIN32_FIND_DATAW dataW; WIN32_FIND_DATAA* dataA; pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx); TRACE(" LONG FILENAME - FIND FIRST MATCHING FILE for %s\n", pathA); MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH); handle = FindFirstFileW(pathW, &dataW); dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi); if (handle != INVALID_HANDLE_VALUE && (h16 = GlobalAlloc16(GMEM_MOVEABLE, sizeof(handle)))) { HANDLE* ptr = GlobalLock16( h16 ); *ptr = handle; GlobalUnlock16( h16 ); SET_AX( context, h16 ); INT21_ConvertFindDataWtoA(dataA, &dataW); } else { if (handle != INVALID_HANDLE_VALUE) FindClose(handle); SET_AX( context, INVALID_HANDLE_VALUE16); bSetDOSExtendedError = TRUE; } } break; case 0x4f: /* LONG FILENAME - FIND NEXT MATCHING FILE */ { HGLOBAL16 h16 = BX_reg(context); HANDLE* ptr; WIN32_FIND_DATAW dataW; WIN32_FIND_DATAA* dataA; TRACE("LONG FILENAME - FIND NEXT MATCHING FILE for handle %d\n", BX_reg(context)); dataA = (WIN32_FIND_DATAA *)CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi); if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 ))) { if (!FindNextFileW(*ptr, &dataW)) bSetDOSExtendedError = TRUE; else INT21_ConvertFindDataWtoA(dataA, &dataW); GlobalUnlock16( h16 ); } else { SetLastError( ERROR_INVALID_HANDLE ); bSetDOSExtendedError = TRUE; } } break; case 0x56: /* LONG FILENAME - RENAME FILE */ if (!INT21_RenameFile(context)) bSetDOSExtendedError = TRUE; break; case 0x60: /* LONG FILENAME - CONVERT PATH */ { WCHAR res[MAX_PATH]; switch (CL_reg(context)) { case 0x00: /* "truename" - Canonicalize path */ /* * FIXME: This is not 100% equal to 0x01 case, * if you fix this, fix int21 subfunction 0x60, too. */ case 0x01: /* Get short filename or path */ MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH); if (!GetShortPathNameW(pathW, res, 67)) bSetDOSExtendedError = TRUE; else { SET_AX( context, 0 ); WideCharToMultiByte(CP_OEMCP, 0, res, -1, CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 67, NULL, NULL); } break; case 0x02: /* Get canonical long filename or path */ MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH); if (!GetFullPathNameW(pathW, 128, res, NULL)) bSetDOSExtendedError = TRUE; else { SET_AX( context, 0 ); WideCharToMultiByte(CP_OEMCP, 0, res, -1, CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 128, NULL, NULL); } break; default: FIXME("Unimplemented long file name function:\n"); INT_BARF( context, 0x21 ); SET_CFLAG(context); SET_AL( context, 0 ); break; } } break; case 0x6c: /* LONG FILENAME - CREATE OR OPEN FILE */ if (!INT21_CreateFile( context, context->Esi, TRUE, BX_reg(context), DL_reg(context) )) bSetDOSExtendedError = TRUE; break; case 0xa0: /* LONG FILENAME - GET VOLUME INFORMATION */ { DWORD filename_len, flags; WCHAR dstW[8]; pathA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx); TRACE("LONG FILENAME - GET VOLUME INFORMATION for drive having root dir '%s'.\n", pathA); SET_AX( context, 0 ); MultiByteToWideChar(CP_OEMCP, 0, pathA, -1, pathW, MAX_PATH); if (!GetVolumeInformationW( pathW, NULL, 0, NULL, &filename_len, &flags, dstW, 8 )) { INT_BARF( context, 0x21 ); SET_CFLAG(context); break; } SET_BX( context, flags | 0x4000 ); /* support for LFN functions */ SET_CX( context, filename_len ); SET_DX( context, MAX_PATH ); /* FIXME: which len if DRIVE_SHORT_NAMES ? */ WideCharToMultiByte(CP_OEMCP, 0, dstW, -1, CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 8, NULL, NULL); } break; case 0xa1: /* LONG FILENAME - "FindClose" - TERMINATE DIRECTORY SEARCH */ { HGLOBAL16 h16 = BX_reg(context); HANDLE* ptr; TRACE("LONG FILENAME - FINDCLOSE for handle %d\n", BX_reg(context)); if (h16 != INVALID_HANDLE_VALUE16 && (ptr = GlobalLock16( h16 ))) { if (!FindClose( *ptr )) bSetDOSExtendedError = TRUE; GlobalUnlock16( h16 ); GlobalFree16( h16 ); } else { SetLastError( ERROR_INVALID_HANDLE ); bSetDOSExtendedError = TRUE; } } break; case 0xa6: /* LONG FILENAME - GET FILE INFO BY HANDLE */ { HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context)); BY_HANDLE_FILE_INFORMATION *info = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "LONG FILENAME - GET FILE INFO BY HANDLE\n" ); if (!GetFileInformationByHandle(handle, info)) bSetDOSExtendedError = TRUE; } break; case 0xa7: /* LONG FILENAME - CONVERT TIME */ switch (BL_reg(context)) { case 0x00: /* FILE TIME TO DOS TIME */ { WORD date, time; FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi); TRACE( "LONG FILENAME - FILE TIME TO DOS TIME\n" ); FileTimeToDosDateTime( filetime, &date, &time ); SET_DX( context, date ); SET_CX( context, time ); /* * FIXME: BH has number of 10-millisecond units * past time in CX. */ SET_BH( context, 0 ); } break; case 0x01: /* DOS TIME TO FILE TIME */ { FILETIME *filetime = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi); TRACE( "LONG FILENAME - DOS TIME TO FILE TIME\n" ); /* * FIXME: BH has number of 10-millisecond units * past time in CX. */ DosDateTimeToFileTime( DX_reg(context), CX_reg(context), filetime ); } break; default: INT_BARF( context, 0x21 ); break; } break; case 0xa8: /* LONG FILENAME - GENERATE SHORT FILENAME */ case 0xa9: /* LONG FILENAME - SERVER CREATE OR OPEN FILE */ case 0xaa: /* LONG FILENAME - SUBST */ default: FIXME("Unimplemented long file name function:\n"); INT_BARF( context, 0x21 ); SET_CFLAG(context); SET_AL( context, 0 ); break; } if (bSetDOSExtendedError) { SET_AX( context, GetLastError() ); SET_CFLAG( context ); } } /*********************************************************************** * INT21_RenameFile * * Handler for: * - function 0x56 * - subfunction 0x56 of function 0x71 * - subfunction 0xff of function 0x43 (CL == 0x56) */ static BOOL INT21_RenameFile( CONTEXT86 *context ) { WCHAR fromW[MAX_PATH]; WCHAR toW[MAX_PATH]; char *fromA = CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Edx); char *toA = CTX_SEG_OFF_TO_LIN(context, context->SegEs,context->Edi); TRACE( "RENAME FILE %s to %s\n", fromA, toA ); MultiByteToWideChar(CP_OEMCP, 0, fromA, -1, fromW, MAX_PATH); MultiByteToWideChar(CP_OEMCP, 0, toA, -1, toW, MAX_PATH); return MoveFileW( fromW, toW ); } /*********************************************************************** * INT21_NetworkFunc * * Handler for: * - function 0x5e */ static BOOL INT21_NetworkFunc (CONTEXT86 *context) { switch (AL_reg(context)) { case 0x00: /* Get machine name. */ { WCHAR dstW[MAX_COMPUTERNAME_LENGTH + 1]; DWORD s = sizeof(dstW) / sizeof(WCHAR); int len; char *dst = CTX_SEG_OFF_TO_LIN (context,context->SegDs,context->Edx); TRACE("getting machine name to %p\n", dst); if (!GetComputerNameW(dstW, &s) || !WideCharToMultiByte(CP_OEMCP, 0, dstW, -1, dst, 16, NULL, NULL)) { WARN("failed!\n"); SetLastError( ER_NoNetwork ); return TRUE; } for (len = strlen(dst); len < 15; len++) dst[len] = ' '; dst[15] = 0; SET_CH( context, 1 ); /* Valid */ SET_CL( context, 1 ); /* NETbios number??? */ TRACE("returning %s\n", debugstr_an(dst, 16)); return FALSE; } default: SetLastError( ER_NoNetwork ); return TRUE; } } /****************************************************************** * INT21_GetDiskSerialNumber * */ static int INT21_GetDiskSerialNumber( CONTEXT86 *context ) { BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); WCHAR path[] = {'A',':',0}, label[11]; DWORD serial; path[0] += INT21_MapDrive(BL_reg(context)); if (!GetVolumeInformationW( path, label, 11, &serial, NULL, NULL, NULL, 0)) { SetLastError( ERROR_INVALID_DRIVE ); return 0; } *(WORD *)dataptr = 0; memcpy(dataptr + 2, &serial, sizeof(DWORD)); WideCharToMultiByte(CP_OEMCP, 0, label, 11, (LPSTR)dataptr + 6, 11, NULL, NULL); memcpy(dataptr + 17, "FAT16 ", 8); return 1; } /****************************************************************** * INT21_SetDiskSerialNumber * */ static int INT21_SetDiskSerialNumber( CONTEXT86 *context ) { #if 0 BYTE *dataptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); int drive = INT21_MapDrive(BL_reg(context)); if (!is_valid_drive(drive)) { SetLastError( ERROR_INVALID_DRIVE ); return 0; } DRIVE_SetSerialNumber( drive, *(DWORD *)(dataptr + 2) ); return 1; #else FIXME("Setting drive serial number is no longer supported\n"); SetLastError( ERROR_NOT_SUPPORTED ); return 0; #endif } /****************************************************************** * INT21_GetFreeDiskSpace * */ static int INT21_GetFreeDiskSpace( CONTEXT86 *context ) { DWORD cluster_sectors, sector_bytes, free_clusters, total_clusters; WCHAR root[] = {'A',':','\\',0}; const DWORD max_clusters = 0x3d83; const DWORD max_sectors_per_cluster = 0x7f; const DWORD max_bytes_per_sector = 0x200; root[0] += INT21_MapDrive(DL_reg(context)); if (!GetDiskFreeSpaceW( root, &cluster_sectors, §or_bytes, &free_clusters, &total_clusters )) return 0; /* Some old win31 apps (Lotus SmartSuite 5.1) crap out if there's too * much disk space, so Windows XP seems to apply the following limits: * cluster_sectors <= 0x7f * sector size <= 0x200 * clusters <= 0x3D83 * This means total reported space is limited to about 1GB. */ /* Make sure bytes-per-sector is in [, max] */ while (sector_bytes > max_bytes_per_sector) { sector_bytes >>= 1; free_clusters <<= 1; total_clusters <<= 1; } /* Then make sure sectors-per-cluster is in [max/2, max]. */ while (cluster_sectors <= max_sectors_per_cluster/2) { cluster_sectors <<= 1; free_clusters >>= 1; total_clusters >>= 1; } while (cluster_sectors > max_sectors_per_cluster) { cluster_sectors >>= 1; free_clusters <<= 1; total_clusters <<= 1; } /* scale up sectors_per_cluster to exactly max_sectors_per_cluster. * We could skip this, but that would impose an artificially low * limit on reported disk space. * To avoid overflow, first apply a preliminary cap on sector count; * this will not affect the correctness of the final result, * because if the preliminary cap hits, the final one will, too. */ if (total_clusters > 4 * max_clusters) total_clusters = 4 * max_clusters; if (free_clusters > 4 * max_clusters) free_clusters = 4 * max_clusters; if (cluster_sectors < max_sectors_per_cluster) { free_clusters *= cluster_sectors; free_clusters /= max_sectors_per_cluster; total_clusters *= cluster_sectors; total_clusters /= max_sectors_per_cluster; cluster_sectors = max_sectors_per_cluster; } /* Finally, apply real cluster count cap. */ if (total_clusters > max_clusters) total_clusters = max_clusters; if (free_clusters > max_clusters) free_clusters = max_clusters; SET_AX( context, cluster_sectors ); SET_BX( context, free_clusters ); SET_CX( context, sector_bytes ); SET_DX( context, total_clusters ); return 1; } /****************************************************************** * INT21_GetDriveAllocInfo * */ static int INT21_GetDriveAllocInfo( CONTEXT86 *context, BYTE drive ) { INT21_DPB *dpb; drive = INT21_MapDrive( drive ); if (!INT21_FillDrivePB( drive )) return 0; dpb = &(INT21_GetHeapPointer()->misc_dpb_list[drive]); SET_AL( context, dpb->cluster_sectors + 1 ); SET_CX( context, dpb->sector_bytes ); SET_DX( context, dpb->num_clusters1 ); context->SegDs = INT21_GetHeapSelector( context ); SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive].media_ID ) ); return 1; } /*********************************************************************** * INT21_GetExtendedError */ static void INT21_GetExtendedError( CONTEXT86 *context ) { BYTE class, action, locus; WORD error = GetLastError(); switch(error) { case ERROR_SUCCESS: class = action = locus = 0; break; case ERROR_DIR_NOT_EMPTY: class = EC_Exists; action = SA_Ignore; locus = EL_Disk; break; case ERROR_ACCESS_DENIED: class = EC_AccessDenied; action = SA_Abort; locus = EL_Disk; break; case ERROR_CANNOT_MAKE: class = EC_AccessDenied; action = SA_Abort; locus = EL_Unknown; break; case ERROR_DISK_FULL: case ERROR_HANDLE_DISK_FULL: class = EC_MediaError; action = SA_Abort; locus = EL_Disk; break; case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS: class = EC_Exists; action = SA_Abort; locus = EL_Disk; break; case ERROR_FILE_NOT_FOUND: class = EC_NotFound; action = SA_Abort; locus = EL_Disk; break; case ERROR_GEN_FAILURE: class = EC_SystemFailure; action = SA_Abort; locus = EL_Unknown; break; case ERROR_INVALID_DRIVE: class = EC_MediaError; action = SA_Abort; locus = EL_Disk; break; case ERROR_INVALID_HANDLE: class = EC_ProgramError; action = SA_Abort; locus = EL_Disk; break; case ERROR_LOCK_VIOLATION: class = EC_AccessDenied; action = SA_Abort; locus = EL_Disk; break; case ERROR_NO_MORE_FILES: class = EC_MediaError; action = SA_Abort; locus = EL_Disk; break; case ER_NoNetwork: class = EC_NotFound; action = SA_Abort; locus = EL_Network; break; case ERROR_NOT_ENOUGH_MEMORY: class = EC_OutOfResource; action = SA_Abort; locus = EL_Memory; break; case ERROR_PATH_NOT_FOUND: class = EC_NotFound; action = SA_Abort; locus = EL_Disk; break; case ERROR_SEEK: class = EC_NotFound; action = SA_Ignore; locus = EL_Disk; break; case ERROR_SHARING_VIOLATION: class = EC_Temporary; action = SA_Retry; locus = EL_Disk; break; case ERROR_TOO_MANY_OPEN_FILES: class = EC_ProgramError; action = SA_Abort; locus = EL_Disk; break; default: FIXME("Unknown error %d\n", error ); class = EC_SystemFailure; action = SA_Abort; locus = EL_Unknown; break; } TRACE("GET EXTENDED ERROR code 0x%02x class 0x%02x action 0x%02x locus %02x\n", error, class, action, locus ); SET_AX( context, error ); SET_BH( context, class ); SET_BL( context, action ); SET_CH( context, locus ); } static BOOL INT21_CreateTempFile( CONTEXT86 *context ) { static int counter = 0; char *name = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx ); char *p = name + strlen(name); /* despite what Ralf Brown says, some programs seem to call without * ending backslash (DOS accepts that, so we accept it too) */ if ((p == name) || (p[-1] != '\\')) *p++ = '\\'; for (;;) { sprintf( p, "wine%04x.%03d", (int)getpid(), counter ); counter = (counter + 1) % 1000; SET_AX( context, Win32HandleToDosFileHandle( CreateFileA( name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_NEW, 0, 0 ) ) ); if (AX_reg(context) != HFILE_ERROR16) { TRACE("created %s\n", name ); return TRUE; } if (GetLastError() != ERROR_FILE_EXISTS) return FALSE; } } /*********************************************************************** * DOSFS_ToDosFCBFormat * * Convert a file name to DOS FCB format (8+3 chars, padded with blanks), * expanding wild cards and converting to upper-case in the process. * File name can be terminated by '\0', '\\' or '/'. * Return FALSE if the name is not a valid DOS name. * 'buffer' must be at least 12 characters long. */ /* Chars we don't want to see in DOS file names */ static BOOL INT21_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer ) { static const WCHAR invalid_chars[] = {'*','?','<','>','|','\\','"','+','=',',',';','[',']',' ','\345',0}; LPCWSTR p = name; int i; /* Check for "." and ".." */ if (*p == '.') { p++; buffer[0] = '.'; for(i = 1; i < 11; i++) buffer[i] = ' '; buffer[11] = 0; if (*p == '.') { buffer[1] = '.'; p++; } return (!*p || (*p == '/') || (*p == '\\')); } for (i = 0; i < 8; i++) { switch(*p) { case '\0': case '\\': case '/': case '.': buffer[i] = ' '; break; case '?': p++; /* fall through */ case '*': buffer[i] = '?'; break; default: if (strchrW( invalid_chars, *p )) return FALSE; buffer[i] = toupperW(*p); p++; break; } } if (*p == '*') { /* Skip all chars after wildcard up to first dot */ while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++; } else { /* Check if name too long */ if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE; } if (*p == '.') p++; /* Skip dot */ for (i = 8; i < 11; i++) { switch(*p) { case '\0': case '\\': case '/': buffer[i] = ' '; break; case '.': return FALSE; /* Second extension not allowed */ case '?': p++; /* fall through */ case '*': buffer[i] = '?'; break; default: if (strchrW( invalid_chars, *p )) return FALSE; buffer[i] = toupperW(*p); p++; break; } } buffer[11] = '\0'; /* at most 3 character of the extension are processed * is something behind this ? */ while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */ return (!*p || (*p == '/') || (*p == '\\')); } static HANDLE INT21_FindHandle; static const WCHAR *INT21_FindPath; /* will point to current dta->fullPath search */ /****************************************************************** * INT21_FindFirst */ static int INT21_FindFirst( CONTEXT86 *context ) { WCHAR *p, *q; const char *path; FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context); WCHAR maskW[12], pathW[MAX_PATH]; static const WCHAR wildcardW[] = {'*','.','*',0}; path = (const char *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); MultiByteToWideChar(CP_OEMCP, 0, path, -1, pathW, MAX_PATH); p = strrchrW( pathW, '\\'); q = strrchrW( pathW, '/'); if (q>p) p = q; if (!p) { if (pathW[0] && pathW[1] == ':') p = pathW + 2; else p = pathW; } else p++; /* Note: terminating NULL in dta->mask overwrites dta->search_attr * (doesn't matter as it is set below anyway) */ if (!INT21_ToDosFCBFormat( p, maskW )) { SetLastError( ERROR_FILE_NOT_FOUND ); SET_AX( context, ERROR_FILE_NOT_FOUND ); SET_CFLAG(context); return 0; } WideCharToMultiByte(CP_OEMCP, 0, maskW, 12, dta->mask, sizeof(dta->mask), NULL, NULL); dta->fullPath = HeapAlloc( GetProcessHeap(), 0, sizeof(wildcardW) + (p - pathW)*sizeof(WCHAR) ); memcpy( dta->fullPath, pathW, (p - pathW) * sizeof(WCHAR) ); memcpy( dta->fullPath + (p - pathW), wildcardW, sizeof(wildcardW) ); /* we must have a fully qualified file name in dta->fullPath * (we could have a UNC path, but this would lead to some errors later on) */ dta->drive = toupperW(dta->fullPath[0]) - 'A'; dta->count = 0; dta->search_attr = CL_reg(context); return 1; } /****************************************************************** * match_short * * Check is a short path name (DTA unicode) matches a mask (FCB ansi) */ static BOOL match_short(LPCWSTR shortW, LPCSTR maskA) { WCHAR mask[11], file[12]; int i; if (!INT21_ToDosFCBFormat( shortW, file )) return FALSE; MultiByteToWideChar(CP_OEMCP, 0, maskA, 11, mask, 11); for (i = 0; i < 11; i++) if (mask[i] != '?' && mask[i] != file[i]) return FALSE; return TRUE; } static unsigned INT21_FindHelper(LPCWSTR fullPath, unsigned drive, unsigned count, LPCSTR mask, unsigned search_attr, WIN32_FIND_DATAW* entry) { unsigned ncalls; if ((search_attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL) { WCHAR path[] = {' ',':',0}; if (count) return 0; path[0] = drive + 'A'; if (!GetVolumeInformationW(path, entry->cAlternateFileName, 13, NULL, NULL, NULL, NULL, 0)) return 0; RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftCreationTime ); RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastAccessTime ); RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastWriteTime ); entry->dwFileAttributes = FA_LABEL; entry->nFileSizeHigh = entry->nFileSizeLow = 0; TRACE("returning %s as label\n", debugstr_w(entry->cAlternateFileName)); return 1; } if (!INT21_FindHandle || INT21_FindPath != fullPath || count == 0) { if (INT21_FindHandle) FindClose(INT21_FindHandle); INT21_FindHandle = FindFirstFileW(fullPath, entry); if (INT21_FindHandle == INVALID_HANDLE_VALUE) { INT21_FindHandle = 0; return 0; } INT21_FindPath = fullPath; /* we need to resync search */ ncalls = count; } else ncalls = 1; while (ncalls-- != 0) { if (!FindNextFileW(INT21_FindHandle, entry)) { FindClose(INT21_FindHandle); INT21_FindHandle = 0; return 0; } } while (count < 0xffff) { count++; /* Check the file attributes, and path */ if (!(entry->dwFileAttributes & ~search_attr) && match_short(entry->cAlternateFileName[0] ? entry->cAlternateFileName : entry->cFileName, mask)) { return count; } if (!FindNextFileW(INT21_FindHandle, entry)) { FindClose(INT21_FindHandle); INT21_FindHandle = 0; return 0; } } WARN("Too many directory entries in %s\n", debugstr_w(fullPath) ); return 0; } /****************************************************************** * INT21_FindNext */ static int INT21_FindNext( CONTEXT86 *context ) { FINDFILE_DTA *dta = (FINDFILE_DTA *)INT21_GetCurrentDTA(context); DWORD attr = dta->search_attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY; WIN32_FIND_DATAW entry; int n; if (!dta->fullPath) return 0; n = INT21_FindHelper(dta->fullPath, dta->drive, dta->count, dta->mask, attr, &entry); if (n) { dta->fileattr = entry.dwFileAttributes; dta->filesize = entry.nFileSizeLow; FileTimeToDosDateTime( &entry.ftLastWriteTime, &dta->filedate, &dta->filetime ); if (entry.cAlternateFileName[0]) WideCharToMultiByte(CP_OEMCP, 0, entry.cAlternateFileName, -1, dta->filename, 13, NULL, NULL); else WideCharToMultiByte(CP_OEMCP, 0, entry.cFileName, -1, dta->filename, 13, NULL, NULL); if (!memchr(dta->mask,'?',11)) { /* wildcardless search, release resources in case no findnext will * be issued, and as a workaround in case file creation messes up * findnext, as sometimes happens with pkunzip */ HeapFree( GetProcessHeap(), 0, dta->fullPath ); INT21_FindPath = dta->fullPath = NULL; } dta->count = n; return 1; } HeapFree( GetProcessHeap(), 0, dta->fullPath ); INT21_FindPath = dta->fullPath = NULL; return 0; } /* microsoft's programmers should be shot for using CP/M style int21 calls in Windows for Workgroup's winfile.exe */ /****************************************************************** * INT21_FindFirstFCB * */ static int INT21_FindFirstFCB( CONTEXT86 *context ) { BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); FINDFILE_FCB *pFCB; int drive; WCHAR p[] = {' ',':',}; if (*fcb == 0xff) pFCB = (FINDFILE_FCB *)(fcb + 7); else pFCB = (FINDFILE_FCB *)fcb; drive = INT21_MapDrive( pFCB->drive ); if (drive == MAX_DOS_DRIVES) return 0; p[0] = 'A' + drive; pFCB->fullPath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); if (!pFCB->fullPath) return 0; GetLongPathNameW(p, pFCB->fullPath, MAX_PATH); pFCB->count = 0; return 1; } /****************************************************************** * INT21_FindNextFCB * */ static int INT21_FindNextFCB( CONTEXT86 *context ) { BYTE *fcb = (BYTE *)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); FINDFILE_FCB *pFCB; LPBYTE pResult = INT21_GetCurrentDTA(context); DOS_DIRENTRY_LAYOUT *ddl; WIN32_FIND_DATAW entry; BYTE attr; int n; WCHAR nameW[12]; if (*fcb == 0xff) /* extended FCB ? */ { attr = fcb[6]; pFCB = (FINDFILE_FCB *)(fcb + 7); } else { attr = 0; pFCB = (FINDFILE_FCB *)fcb; } if (!pFCB->fullPath) return 0; n = INT21_FindHelper(pFCB->fullPath, INT21_MapDrive( pFCB->drive ), pFCB->count, pFCB->filename, attr, &entry); if (!n) { HeapFree( GetProcessHeap(), 0, pFCB->fullPath ); INT21_FindPath = pFCB->fullPath = NULL; return 0; } pFCB->count += n; if (*fcb == 0xff) { /* place extended FCB header before pResult if called with extended FCB */ *pResult = 0xff; pResult += 6; /* leave reserved field behind */ *pResult++ = entry.dwFileAttributes; } *pResult++ = INT21_MapDrive( pFCB->drive ); /* DOS_DIRENTRY_LAYOUT after current drive number */ ddl = (DOS_DIRENTRY_LAYOUT*)pResult; ddl->fileattr = entry.dwFileAttributes; ddl->cluster = 0; /* what else? */ ddl->filesize = entry.nFileSizeLow; memset( ddl->reserved, 0, sizeof(ddl->reserved) ); FileTimeToDosDateTime( &entry.ftLastWriteTime, &ddl->filedate, &ddl->filetime ); /* Convert file name to FCB format */ if (entry.cAlternateFileName[0]) INT21_ToDosFCBFormat( entry.cAlternateFileName, nameW ); else INT21_ToDosFCBFormat( entry.cFileName, nameW ); WideCharToMultiByte(CP_OEMCP, 0, nameW, 11, ddl->filename, 11, NULL, NULL); return 1; } /****************************************************************** * INT21_ParseFileNameIntoFCB * */ static void INT21_ParseFileNameIntoFCB( CONTEXT86 *context ) { char *filename = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi ); char *fcb = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi ); char *s; WCHAR *buffer; WCHAR fcbW[12]; INT buffer_len, len; SET_AL( context, 0xff ); /* failed */ TRACE("filename: '%s'\n", filename); s = filename; while (*s && (*s != ' ') && (*s != '\r') && (*s != '\n')) s++; len = filename - s; buffer_len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, NULL, 0); buffer = HeapAlloc( GetProcessHeap(), 0, (buffer_len + 1) * sizeof(WCHAR)); len = MultiByteToWideChar(CP_OEMCP, 0, filename, len, buffer, buffer_len); buffer[len] = 0; INT21_ToDosFCBFormat(buffer, fcbW); HeapFree(GetProcessHeap(), 0, buffer); WideCharToMultiByte(CP_OEMCP, 0, fcbW, 12, fcb + 1, 12, NULL, NULL); *fcb = 0; TRACE("FCB: '%s'\n", fcb + 1); SET_AL( context, ((strchr(filename, '*')) || (strchr(filename, '$'))) != 0 ); /* point DS:SI to first unparsed character */ SET_SI( context, context->Esi + (int)s - (int)filename ); } static BOOL INT21_Dup2(HFILE16 hFile1, HFILE16 hFile2) { HFILE16 res = HFILE_ERROR16; HANDLE handle, new_handle; #define DOS_TABLE_SIZE 256 DWORD map[DOS_TABLE_SIZE / 32]; int i; handle = DosFileHandleToWin32Handle(hFile1); if (handle == INVALID_HANDLE_VALUE) return FALSE; _lclose16(hFile2); /* now loop to allocate the same one... */ memset(map, 0, sizeof(map)); for (i = 0; i < DOS_TABLE_SIZE; i++) { if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &new_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { res = HFILE_ERROR16; break; } res = Win32HandleToDosFileHandle(new_handle); if (res == HFILE_ERROR16 || res == hFile2) break; map[res / 32] |= 1 << (res % 32); } /* clean up the allocated slots */ for (i = 0; i < DOS_TABLE_SIZE; i++) { if (map[i / 32] & (1 << (i % 32))) _lclose16((HFILE16)i); } return res == hFile2; } /*********************************************************************** * DOSVM_Int21Handler * * Interrupt 0x21 handler. */ void WINAPI DOSVM_Int21Handler( CONTEXT86 *context ) { BOOL bSetDOSExtendedError = FALSE; TRACE( "AX=%04x BX=%04x CX=%04x DX=%04x " "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08x\n", AX_reg(context), BX_reg(context), CX_reg(context), DX_reg(context), SI_reg(context), DI_reg(context), (WORD)context->SegDs, (WORD)context->SegEs, context->EFlags ); /* * Extended error is used by (at least) functions 0x2f to 0x62. * Function 0x59 returns extended error and error should not * be cleared before handling the function. */ if (AH_reg(context) >= 0x2f && AH_reg(context) != 0x59) SetLastError(0); RESET_CFLAG(context); /* Not sure if this is a good idea. */ switch(AH_reg(context)) { case 0x00: /* TERMINATE PROGRAM */ TRACE("TERMINATE PROGRAM\n"); if (DOSVM_IsWin16()) ExitThread( 0 ); else if(ISV86(context)) MZ_Exit( context, FALSE, 0 ); else ERR( "Called from DOS protected mode\n" ); break; case 0x01: /* READ CHARACTER FROM STANDARD INPUT, WITH ECHO */ { BYTE ascii; TRACE("DIRECT CHARACTER INPUT WITH ECHO\n"); INT21_ReadChar( &ascii, context ); SET_AL( context, ascii ); /* * FIXME: What to echo when extended keycodes are read? */ DOSVM_PutChar(AL_reg(context)); } break; case 0x02: /* WRITE CHARACTER TO STANDARD OUTPUT */ TRACE("Write Character to Standard Output\n"); DOSVM_PutChar(DL_reg(context)); break; case 0x03: /* READ CHARACTER FROM STDAUX */ case 0x04: /* WRITE CHARACTER TO STDAUX */ case 0x05: /* WRITE CHARACTER TO PRINTER */ INT_BARF( context, 0x21 ); break; case 0x06: /* DIRECT CONSOLE IN/OUTPUT */ if (DL_reg(context) == 0xff) { TRACE("Direct Console Input\n"); if (INT21_ReadChar( NULL, NULL )) { BYTE ascii; INT21_ReadChar( &ascii, context ); SET_AL( context, ascii ); RESET_ZFLAG( context ); } else { /* no character available */ SET_AL( context, 0 ); SET_ZFLAG( context ); } } else { TRACE("Direct Console Output\n"); DOSVM_PutChar(DL_reg(context)); /* * At least DOS versions 2.1-7.0 return character * that was written in AL register. */ SET_AL( context, DL_reg(context) ); } break; case 0x07: /* DIRECT CHARACTER INPUT WITHOUT ECHO */ { BYTE ascii; TRACE("DIRECT CHARACTER INPUT WITHOUT ECHO\n"); INT21_ReadChar( &ascii, context ); SET_AL( context, ascii ); } break; case 0x08: /* CHARACTER INPUT WITHOUT ECHO */ { BYTE ascii; TRACE("CHARACTER INPUT WITHOUT ECHO\n"); INT21_ReadChar( &ascii, context ); SET_AL( context, ascii ); } break; case 0x09: /* WRITE STRING TO STANDARD OUTPUT */ TRACE("WRITE '$'-terminated string from %04X:%04X to stdout\n", context->SegDs, DX_reg(context) ); { LPSTR data = CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx ); LPSTR p = data; DWORD w; /* * Do NOT use strchr() to calculate the string length, * as '\0' is valid string content, too! * Maybe we should check for non-'$' strings, but DOS doesn't. */ while (*p != '$') p++; if (DOSVM_IsWin16()) WriteFile( DosFileHandleToWin32Handle(1), data, p - data, &w, NULL ); else for(; data != p; data++) DOSVM_PutChar( *data ); SET_AL( context, '$' ); /* yes, '$' (0x24) gets returned in AL */ } break; case 0x0a: /* BUFFERED INPUT */ { BYTE *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); WORD result; TRACE( "BUFFERED INPUT (size=%d)\n", ptr[0] ); /* * FIXME: Some documents state that * ptr[1] holds number of chars from last input which * may be recalled on entry, other documents do not mention * this at all. */ if (ptr[1]) TRACE( "Handle old chars in buffer!\n" ); /* * ptr[0] - capacity (includes terminating CR) * ptr[1] - characters read (excludes terminating CR) */ result = INT21_BufferedInput( context, ptr + 2, ptr[0] ); if (result > 0) ptr[1] = (BYTE)result - 1; else ptr[1] = 0; } break; case 0x0b: /* GET STDIN STATUS */ TRACE( "GET STDIN STATUS\n" ); { if (INT21_ReadChar( NULL, NULL )) SET_AL( context, 0xff ); /* character available */ else SET_AL( context, 0 ); /* no character available */ } break; case 0x0c: /* FLUSH BUFFER AND READ STANDARD INPUT */ { BYTE al = AL_reg(context); /* Input function to execute after flush. */ TRACE( "FLUSH BUFFER AND READ STANDARD INPUT - 0x%02x\n", al ); /* FIXME: buffers are not flushed */ /* * If AL is one of 0x01, 0x06, 0x07, 0x08, or 0x0a, * int21 function identified by AL will be called. */ if(al == 0x01 || al == 0x06 || al == 0x07 || al == 0x08 || al == 0x0a) { SET_AH( context, al ); DOSVM_Int21Handler( context ); } } break; case 0x0d: /* DISK BUFFER FLUSH */ TRACE("DISK BUFFER FLUSH ignored\n"); break; case 0x0e: /* SELECT DEFAULT DRIVE */ TRACE( "SELECT DEFAULT DRIVE - %c:\n", 'A' + DL_reg(context) ); INT21_SetCurrentDrive( DL_reg(context) ); SET_AL( context, MAX_DOS_DRIVES ); break; case 0x0f: /* OPEN FILE USING FCB */ INT21_OpenFileUsingFCB( context ); break; case 0x10: /* CLOSE FILE USING FCB */ INT21_CloseFileUsingFCB( context ); break; case 0x11: /* FIND FIRST MATCHING FILE USING FCB */ TRACE("FIND FIRST MATCHING FILE USING FCB %p\n", CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx)); if (!INT21_FindFirstFCB(context)) { SET_AL( context, 0xff ); break; } /* else fall through */ case 0x12: /* FIND NEXT MATCHING FILE USING FCB */ SET_AL( context, INT21_FindNextFCB(context) ? 0x00 : 0xff ); break; case 0x13: /* DELETE FILE USING FCB */ INT_BARF( context, 0x21 ); break; case 0x14: /* SEQUENTIAL READ FROM FCB FILE */ INT21_SequentialReadFromFCB( context ); break; case 0x15: /* SEQUENTIAL WRITE TO FCB FILE */ INT21_SequentialWriteToFCB( context ); break; case 0x16: /* CREATE OR TRUNCATE FILE USING FCB */ case 0x17: /* RENAME FILE USING FCB */ INT_BARF( context, 0x21 ); break; case 0x18: /* NULL FUNCTION FOR CP/M COMPATIBILITY */ SET_AL( context, 0 ); break; case 0x19: /* GET CURRENT DEFAULT DRIVE */ SET_AL( context, INT21_GetCurrentDrive() ); TRACE( "GET CURRENT DRIVE -> %c:\n", 'A' + AL_reg( context ) ); break; case 0x1a: /* SET DISK TRANSFER AREA ADDRESS */ TRACE( "SET DISK TRANSFER AREA ADDRESS %04X:%04X\n", context->SegDs, DX_reg(context) ); { TDB *task = GlobalLock16( GetCurrentTask() ); task->dta = MAKESEGPTR( context->SegDs, DX_reg(context) ); } break; case 0x1b: /* GET ALLOCATION INFORMATION FOR DEFAULT DRIVE */ if (!INT21_GetDriveAllocInfo(context, 0)) SET_AX( context, 0xffff ); break; case 0x1c: /* GET ALLOCATION INFORMATION FOR SPECIFIC DRIVE */ if (!INT21_GetDriveAllocInfo(context, DL_reg(context))) SET_AX( context, 0xffff ); break; case 0x1d: /* NULL FUNCTION FOR CP/M COMPATIBILITY */ case 0x1e: /* NULL FUNCTION FOR CP/M COMPATIBILITY */ SET_AL( context, 0 ); break; case 0x1f: /* GET DRIVE PARAMETER BLOCK FOR DEFAULT DRIVE */ { BYTE drive = INT21_MapDrive( 0 ); TRACE( "GET DPB FOR DEFAULT DRIVE\n" ); if (INT21_FillDrivePB( drive )) { SET_AL( context, 0x00 ); /* success */ SET_BX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) ); context->SegDs = INT21_GetHeapSelector( context ); } else { SET_AL( context, 0xff ); /* invalid or network drive */ } } break; case 0x20: /* NULL FUNCTION FOR CP/M COMPATIBILITY */ SET_AL( context, 0 ); break; case 0x21: /* READ RANDOM RECORD FROM FCB FILE */ INT21_ReadRandomRecordFromFCB( context ); break; case 0x22: /* WRITE RANDOM RECORD TO FCB FILE */ INT21_WriteRandomRecordToFCB( context ); break; case 0x23: /* GET FILE SIZE FOR FCB */ case 0x24: /* SET RANDOM RECORD NUMBER FOR FCB */ INT_BARF( context, 0x21 ); break; case 0x25: /* SET INTERRUPT VECTOR */ TRACE("SET INTERRUPT VECTOR 0x%02x\n",AL_reg(context)); { FARPROC16 ptr = (FARPROC16)MAKESEGPTR( context->SegDs, DX_reg(context) ); if (!ISV86(context) && DOSVM_IsWin16()) DOSVM_SetPMHandler16( AL_reg(context), ptr ); else DOSVM_SetRMHandler( AL_reg(context), ptr ); } break; case 0x26: /* CREATE NEW PROGRAM SEGMENT PREFIX */ INT_BARF( context, 0x21 ); break; case 0x27: /* RANDOM BLOCK READ FROM FCB FILE */ INT21_RandomBlockReadFromFCB( context ); break; case 0x28: /* RANDOM BLOCK WRITE TO FCB FILE */ INT21_RandomBlockWriteToFCB( context ); break; case 0x29: /* PARSE FILENAME INTO FCB */ INT21_ParseFileNameIntoFCB(context); break; case 0x2a: /* GET SYSTEM DATE */ TRACE( "GET SYSTEM DATE\n" ); { SYSTEMTIME systime; GetLocalTime( &systime ); SET_CX( context, systime.wYear ); SET_DH( context, systime.wMonth ); SET_DL( context, systime.wDay ); SET_AL( context, systime.wDayOfWeek ); } break; case 0x2b: /* SET SYSTEM DATE */ TRACE( "SET SYSTEM DATE\n" ); { WORD year = CX_reg(context); BYTE month = DH_reg(context); BYTE day = DL_reg(context); if (year >= 1980 && year <= 2099 && month >= 1 && month <= 12 && day >= 1 && day <= 31) { FIXME( "SetSystemDate(%02d/%02d/%04d): not allowed\n", day, month, year ); SET_AL( context, 0 ); /* Let's pretend we succeeded */ } else { SET_AL( context, 0xff ); /* invalid date */ TRACE( "SetSystemDate(%02d/%02d/%04d): invalid date\n", day, month, year ); } } break; case 0x2c: /* GET SYSTEM TIME */ TRACE( "GET SYSTEM TIME\n" ); { SYSTEMTIME systime; GetLocalTime( &systime ); SET_CH( context, systime.wHour ); SET_CL( context, systime.wMinute ); SET_DH( context, systime.wSecond ); SET_DL( context, systime.wMilliseconds / 10 ); } break; case 0x2d: /* SET SYSTEM TIME */ if( CH_reg(context) >= 24 || CL_reg(context) >= 60 || DH_reg(context) >= 60 || DL_reg(context) >= 100 ) { TRACE("SetSystemTime(%02d:%02d:%02d.%02d): wrong time\n", CH_reg(context), CL_reg(context), DH_reg(context), DL_reg(context) ); SET_AL( context, 0xFF ); } else { FIXME("SetSystemTime(%02d:%02d:%02d.%02d): not allowed\n", CH_reg(context), CL_reg(context), DH_reg(context), DL_reg(context) ); SET_AL( context, 0 ); /* Let's pretend we succeeded */ } break; case 0x2e: /* SET VERIFY FLAG */ TRACE("SET VERIFY FLAG ignored\n"); /* we cannot change the behaviour anyway, so just ignore it */ break; case 0x2f: /* GET DISK TRANSFER AREA ADDRESS */ TRACE( "GET DISK TRANSFER AREA ADDRESS\n" ); { TDB *task = GlobalLock16( GetCurrentTask() ); context->SegEs = SELECTOROF( task->dta ); SET_BX( context, OFFSETOF( task->dta ) ); } break; case 0x30: /* GET DOS VERSION */ TRACE( "GET DOS VERSION - %s requested\n", (AL_reg(context) == 0x00) ? "OEM number" : "version flag" ); if (AL_reg(context) == 0x00) SET_BH( context, 0xff ); /* OEM number => undefined */ else SET_BH( context, 0x08 ); /* version flag => DOS is in ROM */ SET_AL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major version */ SET_AH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor version */ SET_BL( context, 0x12 ); /* 0x123456 is 24-bit Wine's serial # */ SET_CX( context, 0x3456 ); break; case 0x31: /* TERMINATE AND STAY RESIDENT */ FIXME("TERMINATE AND STAY RESIDENT stub\n"); break; case 0x32: /* GET DOS DRIVE PARAMETER BLOCK FOR SPECIFIC DRIVE */ { BYTE drive = INT21_MapDrive( DL_reg(context) ); TRACE( "GET DPB FOR SPECIFIC DRIVE %d\n", DL_reg(context) ); if (INT21_FillDrivePB( drive )) { SET_AL( context, 0x00 ); /* success */ SET_DX( context, offsetof( INT21_HEAP, misc_dpb_list[drive] ) ); context->SegDs = INT21_GetHeapSelector( context ); } else { SET_AL( context, 0xff ); /* invalid or network drive */ } } break; case 0x33: /* MULTIPLEXED */ switch (AL_reg(context)) { case 0x00: /* GET CURRENT EXTENDED BREAK STATE */ TRACE("GET CURRENT EXTENDED BREAK STATE\n"); SET_DL( context, DOSCONF_GetConfig()->brk_flag ); break; case 0x01: /* SET EXTENDED BREAK STATE */ TRACE("SET CURRENT EXTENDED BREAK STATE\n"); DOSCONF_GetConfig()->brk_flag = (DL_reg(context) > 0) ? 1 : 0; break; case 0x02: /* GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE*/ TRACE("GET AND SET EXTENDED CONTROL-BREAK CHECKING STATE\n"); /* ugly coding in order to stay reentrant */ if (DL_reg(context)) { SET_DL( context, DOSCONF_GetConfig()->brk_flag ); DOSCONF_GetConfig()->brk_flag = 1; } else { SET_DL( context, DOSCONF_GetConfig()->brk_flag ); DOSCONF_GetConfig()->brk_flag = 0; } break; case 0x05: /* GET BOOT DRIVE */ TRACE("GET BOOT DRIVE\n"); SET_DL( context, 3 ); /* c: is Wine's bootdrive (a: is 1)*/ break; case 0x06: /* GET TRUE VERSION NUMBER */ TRACE("GET TRUE VERSION NUMBER\n"); SET_BL( context, HIBYTE(HIWORD(GetVersion16())) ); /* major */ SET_BH( context, LOBYTE(HIWORD(GetVersion16())) ); /* minor */ SET_DL( context, 0x00 ); /* revision */ SET_DH( context, 0x08 ); /* DOS is in ROM */ break; default: INT_BARF( context, 0x21 ); break; } break; case 0x34: /* GET ADDRESS OF INDOS FLAG */ TRACE( "GET ADDRESS OF INDOS FLAG\n" ); context->SegEs = INT21_GetHeapSelector( context ); SET_BX( context, offsetof(INT21_HEAP, misc_indos) ); break; case 0x35: /* GET INTERRUPT VECTOR */ TRACE("GET INTERRUPT VECTOR 0x%02x\n",AL_reg(context)); { FARPROC16 addr; if (!ISV86(context) && DOSVM_IsWin16()) addr = DOSVM_GetPMHandler16( AL_reg(context) ); else addr = DOSVM_GetRMHandler( AL_reg(context) ); context->SegEs = SELECTOROF(addr); SET_BX( context, OFFSETOF(addr) ); } break; case 0x36: /* GET FREE DISK SPACE */ TRACE("GET FREE DISK SPACE FOR DRIVE %s (limited to about 1GB)\n", INT21_DriveName( DL_reg(context) )); if (!INT21_GetFreeDiskSpace(context)) SET_AX( context, 0xffff ); break; case 0x37: /* SWITCHAR */ { switch (AL_reg(context)) { case 0x00: /* "SWITCHAR" - GET SWITCH CHARACTER */ TRACE( "SWITCHAR - GET SWITCH CHARACTER\n" ); SET_AL( context, 0x00 ); /* success*/ SET_DL( context, '/' ); break; case 0x01: /*"SWITCHAR" - SET SWITCH CHARACTER*/ FIXME( "SWITCHAR - SET SWITCH CHARACTER: %c\n", DL_reg( context )); SET_AL( context, 0x00 ); /* success*/ break; default: INT_BARF( context, 0x21 ); break; } } break; case 0x38: /* GET COUNTRY-SPECIFIC INFORMATION */ TRACE( "GET COUNTRY-SPECIFIC INFORMATION\n" ); if (AL_reg(context)) { WORD country = AL_reg(context); if (country == 0xff) country = BX_reg(context); if (country != INT21_GetSystemCountryCode()) { FIXME( "Requested info on non-default country %04x\n", country ); SET_AX(context, 2); SET_CFLAG(context); } } if(AX_reg(context) != 2 ) { INT21_FillCountryInformation( CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx) ); SET_AX( context, INT21_GetSystemCountryCode() ); SET_BX( context, INT21_GetSystemCountryCode() ); RESET_CFLAG(context); } break; case 0x39: /* "MKDIR" - CREATE SUBDIRECTORY */ if (!INT21_CreateDirectory( context )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x3a: /* "RMDIR" - REMOVE DIRECTORY */ { WCHAR dirW[MAX_PATH]; char *dirA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "REMOVE DIRECTORY %s\n", dirA ); MultiByteToWideChar(CP_OEMCP, 0, dirA, -1, dirW, MAX_PATH); if (!RemoveDirectoryW( dirW )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); } break; case 0x3b: /* "CHDIR" - SET CURRENT DIRECTORY */ if (!INT21_SetCurrentDirectory( context )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x3c: /* "CREAT" - CREATE OR TRUNCATE FILE */ if (!INT21_CreateFile( context, context->Edx, FALSE, OF_READWRITE | OF_SHARE_COMPAT, 0x12 )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x3d: /* "OPEN" - OPEN EXISTING FILE */ if (!INT21_CreateFile( context, context->Edx, FALSE, AL_reg(context), 0x01 )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x3e: /* "CLOSE" - CLOSE FILE */ TRACE( "CLOSE handle %d\n", BX_reg(context) ); if (_lclose16( BX_reg(context) ) == HFILE_ERROR16) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x3f: /* "READ" - READ FROM FILE OR DEVICE */ TRACE( "READ from %d to %04X:%04X for %d bytes\n", BX_reg(context), context->SegDs, DX_reg(context), CX_reg(context) ); { DWORD result; WORD count = CX_reg(context); BYTE *buffer = CTX_SEG_OFF_TO_LIN( context, context->SegDs, context->Edx ); /* Some programs pass a count larger than the allocated buffer */ if (DOSVM_IsWin16()) { DWORD maxcount = GetSelectorLimit16( context->SegDs ) - DX_reg(context) + 1; if (count > maxcount) count = maxcount; } /* * FIXME: Reading from console (BX=1) in DOS mode * does not work as it is supposed to work. */ RESET_CFLAG(context); /* set if error */ if (!DOSVM_IsWin16() && BX_reg(context) == 0) { result = INT21_BufferedInput( context, buffer, count ); SET_AX( context, (WORD)result ); } else if (ReadFile( DosFileHandleToWin32Handle(BX_reg(context)), buffer, count, &result, NULL )) SET_AX( context, (WORD)result ); else bSetDOSExtendedError = TRUE; } break; case 0x40: /* "WRITE" - WRITE TO FILE OR DEVICE */ TRACE( "WRITE from %04X:%04X to handle %d for %d byte\n", context->SegDs, DX_reg(context), BX_reg(context), CX_reg(context) ); { char *ptr = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); if (!DOSVM_IsWin16() && (BX_reg(context) == 1 || BX_reg(context) == 2)) { int i; for(i=0; i<CX_reg(context); i++) DOSVM_PutChar(ptr[i]); SET_AX(context, CX_reg(context)); RESET_CFLAG(context); } else { HFILE handle = (HFILE)DosFileHandleToWin32Handle(BX_reg(context)); LONG result = _hwrite( handle, ptr, CX_reg(context) ); if (result == HFILE_ERROR) bSetDOSExtendedError = TRUE; else { SET_AX( context, (WORD)result ); RESET_CFLAG(context); } } } break; case 0x41: /* "UNLINK" - DELETE FILE */ { WCHAR fileW[MAX_PATH]; char *fileA = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); TRACE( "UNLINK %s\n", fileA ); MultiByteToWideChar(CP_OEMCP, 0, fileA, -1, fileW, MAX_PATH); if (!DeleteFileW( fileW )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); } break; case 0x42: /* "LSEEK" - SET CURRENT FILE POSITION */ TRACE( "LSEEK handle %d offset %d from %s\n", BX_reg(context), MAKELONG( DX_reg(context), CX_reg(context) ), (AL_reg(context) == 0) ? "start of file" : ((AL_reg(context) == 1) ? "current file position" : "end of file") ); { HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context)); LONG offset = MAKELONG( DX_reg(context), CX_reg(context) ); DWORD status = SetFilePointer( handle, offset, NULL, AL_reg(context) ); if (status == INVALID_SET_FILE_POINTER) bSetDOSExtendedError = TRUE; else { SET_AX( context, LOWORD(status) ); SET_DX( context, HIWORD(status) ); RESET_CFLAG(context); } } break; case 0x43: /* FILE ATTRIBUTES */ if (!INT21_FileAttributes( context, AL_reg(context), FALSE )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x44: /* IOCTL */ INT21_Ioctl( context ); break; case 0x45: /* "DUP" - DUPLICATE FILE HANDLE */ TRACE( "DUPLICATE FILE HANDLE %d\n", BX_reg(context) ); { HANDLE handle32; HFILE handle16 = HFILE_ERROR; if (DuplicateHandle( GetCurrentProcess(), DosFileHandleToWin32Handle(BX_reg(context)), GetCurrentProcess(), &handle32, 0, TRUE, DUPLICATE_SAME_ACCESS )) handle16 = Win32HandleToDosFileHandle(handle32); if (handle16 == HFILE_ERROR) bSetDOSExtendedError = TRUE; else { SET_AX( context, handle16 ); RESET_CFLAG(context); } } break; case 0x46: /* "DUP2", "FORCEDUP" - FORCE DUPLICATE FILE HANDLE */ TRACE( "FORCEDUP - FORCE DUPLICATE FILE HANDLE %d to %d\n", BX_reg(context), CX_reg(context) ); if (!INT21_Dup2(BX_reg(context), CX_reg(context))) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x47: /* "CWD" - GET CURRENT DIRECTORY */ if (!INT21_GetCurrentDirectory( context, FALSE )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x48: /* ALLOCATE MEMORY */ TRACE( "ALLOCATE MEMORY for %d paragraphs\n", BX_reg(context) ); { WORD selector = 0; DWORD bytes = (DWORD)BX_reg(context) << 4; if (!ISV86(context) && DOSVM_IsWin16()) { DWORD rv = GlobalDOSAlloc16( bytes ); selector = LOWORD( rv ); } else DOSMEM_AllocBlock( bytes, &selector ); if (selector) { SET_AX( context, selector ); RESET_CFLAG(context); } else { SET_CFLAG(context); SET_AX( context, 0x0008 ); /* insufficient memory */ SET_BX( context, DOSMEM_Available() >> 4 ); } } break; case 0x49: /* FREE MEMORY */ TRACE( "FREE MEMORY segment %04X\n", context->SegEs ); { BOOL ok; if (!ISV86(context) && DOSVM_IsWin16()) { ok = !GlobalDOSFree16( context->SegEs ); /* If we don't reset ES_reg, we will fail in the relay code */ if (ok) context->SegEs = 0; } else ok = DOSMEM_FreeBlock( PTR_REAL_TO_LIN(context->SegEs, 0) ); if (!ok) { TRACE("FREE MEMORY failed\n"); SET_CFLAG(context); SET_AX( context, 0x0009 ); /* memory block address invalid */ } } break; case 0x4a: /* RESIZE MEMORY BLOCK */ TRACE( "RESIZE MEMORY segment %04X to %d paragraphs\n", context->SegEs, BX_reg(context) ); { DWORD newsize = (DWORD)BX_reg(context) << 4; if (!ISV86(context) && DOSVM_IsWin16()) { FIXME( "Resize memory block - unsupported under Win16\n" ); SET_CFLAG(context); } else { LPVOID address = (void*)((DWORD)context->SegEs << 4); UINT blocksize = DOSMEM_ResizeBlock( address, newsize, FALSE ); RESET_CFLAG(context); if (blocksize == (UINT)-1) { SET_CFLAG( context ); SET_AX( context, 0x0009 ); /* illegal address */ } else if(blocksize != newsize) { SET_CFLAG( context ); SET_AX( context, 0x0008 ); /* insufficient memory */ SET_BX( context, blocksize >> 4 ); /* new block size */ } } } break; case 0x4b: /* "EXEC" - LOAD AND/OR EXECUTE PROGRAM */ { char *program = CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx); BYTE *paramblk = CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Ebx); TRACE( "EXEC %s\n", program ); RESET_CFLAG(context); if (DOSVM_IsWin16()) { HINSTANCE16 instance = WinExec16( program, SW_NORMAL ); if (instance < 32) { SET_CFLAG( context ); SET_AX( context, instance ); } } else { if (!MZ_Exec( context, program, AL_reg(context), paramblk)) bSetDOSExtendedError = TRUE; } } break; case 0x4c: /* "EXIT" - TERMINATE WITH RETURN CODE */ TRACE( "EXIT with return code %d\n", AL_reg(context) ); if (DOSVM_IsWin16()) ExitThread( AL_reg(context) ); else if(ISV86(context)) MZ_Exit( context, FALSE, AL_reg(context) ); else { /* * Exit from DPMI. */ ULONG_PTR rv = AL_reg(context); RaiseException( EXCEPTION_VM86_INTx, 0, 1, &rv ); } break; case 0x4d: /* GET RETURN CODE */ TRACE("GET RETURN CODE (ERRORLEVEL)\n"); SET_AX( context, DOSVM_retval ); DOSVM_retval = 0; break; case 0x4e: /* "FINDFIRST" - FIND FIRST MATCHING FILE */ TRACE("FINDFIRST mask 0x%04x spec %s\n",CX_reg(context), (LPCSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Edx)); if (!INT21_FindFirst(context)) break; /* fall through */ case 0x4f: /* "FINDNEXT" - FIND NEXT MATCHING FILE */ TRACE("FINDNEXT\n"); if (!INT21_FindNext(context)) { SetLastError( ERROR_NO_MORE_FILES ); SET_AX( context, ERROR_NO_MORE_FILES ); SET_CFLAG(context); } else SET_AX( context, 0 ); /* OK */ break; case 0x50: /* SET CURRENT PROCESS ID (SET PSP ADDRESS) */ TRACE("SET CURRENT PROCESS ID (SET PSP ADDRESS)\n"); DOSVM_psp = BX_reg(context); break; case 0x51: /* GET PSP ADDRESS */ INT21_GetPSP( context ); break; case 0x52: /* "SYSVARS" - GET LIST OF LISTS */ { SEGPTR ptr = DOSDEV_GetLOL( ISV86(context) || !DOSVM_IsWin16() ); context->SegEs = SELECTOROF(ptr); SET_BX( context, OFFSETOF(ptr) ); } break; case 0x54: /* Get Verify Flag */ TRACE("Get Verify Flag - Not Supported\n"); SET_AL( context, 0x00 ); /* pretend we can tell. 00h = off 01h = on */ break; case 0x56: /* "RENAME" - RENAME FILE */ if (!INT21_RenameFile( context )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x57: /* FILE DATE AND TIME */ if (!INT21_FileDateTime( context )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x58: /* GET OR SET MEMORY ALLOCATION STRATEGY */ switch (AL_reg(context)) { case 0x00: /* GET MEMORY ALLOCATION STRATEGY */ TRACE( "GET MEMORY ALLOCATION STRATEGY\n" ); SET_AX( context, 0 ); /* low memory first fit */ break; case 0x01: /* SET ALLOCATION STRATEGY */ TRACE( "SET MEMORY ALLOCATION STRATEGY to %d - ignored\n", BL_reg(context) ); break; case 0x02: /* GET UMB LINK STATE */ TRACE( "GET UMB LINK STATE\n" ); SET_AL( context, 0 ); /* UMBs not part of DOS memory chain */ break; case 0x03: /* SET UMB LINK STATE */ TRACE( "SET UMB LINK STATE to %d - ignored\n", BX_reg(context) ); break; default: INT_BARF( context, 0x21 ); } break; case 0x59: /* GET EXTENDED ERROR INFO */ INT21_GetExtendedError( context ); break; case 0x5a: /* CREATE TEMPORARY FILE */ TRACE("CREATE TEMPORARY FILE\n"); bSetDOSExtendedError = !INT21_CreateTempFile(context); break; case 0x5b: /* CREATE NEW FILE */ if (!INT21_CreateFile( context, context->Edx, FALSE, OF_READWRITE | OF_SHARE_COMPAT, 0x10 )) bSetDOSExtendedError = TRUE; else RESET_CFLAG(context); break; case 0x5c: /* "FLOCK" - RECORD LOCKING */ { DWORD offset = MAKELONG(DX_reg(context), CX_reg(context)); DWORD length = MAKELONG(DI_reg(context), SI_reg(context)); HANDLE handle = DosFileHandleToWin32Handle(BX_reg(context)); RESET_CFLAG(context); switch (AL_reg(context)) { case 0x00: /* LOCK */ TRACE( "lock handle %d offset %d length %d\n", BX_reg(context), offset, length ); if (!LockFile( handle, offset, 0, length, 0 )) bSetDOSExtendedError = TRUE; break; case 0x01: /* UNLOCK */ TRACE( "unlock handle %d offset %d length %d\n", BX_reg(context), offset, length ); if (!UnlockFile( handle, offset, 0, length, 0 )) bSetDOSExtendedError = TRUE; break; default: INT_BARF( context, 0x21 ); } } break; case 0x5d: /* NETWORK 5D */ FIXME( "Network function 5D not implemented.\n" ); SetLastError( ER_NoNetwork ); bSetDOSExtendedError = TRUE; break; case 0x5e: /* NETWORK 5E */ bSetDOSExtendedError = INT21_NetworkFunc( context); break; case 0x5f: /* NETWORK 5F */ /* FIXME: supporting this would need to 1: * - implement per drive current directory (as kernel32 doesn't) * - assign enabled/disabled flag on a per drive basis */ /* network software not installed */ TRACE("NETWORK function AX=%04x not implemented\n",AX_reg(context)); SetLastError( ER_NoNetwork ); bSetDOSExtendedError = TRUE; break; case 0x60: /* "TRUENAME" - CANONICALIZE FILENAME OR PATH */ { WCHAR pathW[MAX_PATH], res[MAX_PATH]; /* FIXME: likely to be broken */ TRACE("TRUENAME %s\n", (LPCSTR)CTX_SEG_OFF_TO_LIN(context, context->SegDs,context->Esi)); MultiByteToWideChar(CP_OEMCP, 0, CTX_SEG_OFF_TO_LIN(context, context->SegDs, context->Esi), -1, pathW, MAX_PATH); if (!GetFullPathNameW( pathW, 128, res, NULL )) bSetDOSExtendedError = TRUE; else { SET_AX( context, 0 ); WideCharToMultiByte(CP_OEMCP, 0, res, -1, CTX_SEG_OFF_TO_LIN(context, context->SegEs, context->Edi), 128, NULL, NULL); } } break; case 0x61: /* UNUSED */ SET_AL( context, 0 ); break; case 0x62: /* GET PSP ADDRESS */ INT21_GetPSP( context ); break; case 0x63: /* MISC. LANGUAGE SUPPORT */ switch (AL_reg(context)) { case 0x00: /* GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE */ TRACE( "GET DOUBLE BYTE CHARACTER SET LEAD-BYTE TABLE\n" ); context->SegDs = INT21_GetHeapSelector( context ); SET_SI( context, offsetof(INT21_HEAP, dbcs_table) ); SET_AL( context, 0 ); /* success */ break; } break; case 0x64: /* OS/2 DOS BOX */ INT_BARF( context, 0x21 ); SET_CFLAG(context); break; case 0x65: /* EXTENDED COUNTRY INFORMATION */ INT21_ExtendedCountryInformation( context ); break; case 0x66: /* GLOBAL CODE PAGE TABLE */ switch (AL_reg(context)) { case 0x01: TRACE( "GET GLOBAL CODE PAGE TABLE\n" ); SET_BX( context, GetOEMCP() ); SET_DX( context, GetOEMCP() ); break; case 0x02: FIXME( "SET GLOBAL CODE PAGE TABLE, active %d, system %d - ignored\n", BX_reg(context), DX_reg(context) ); break; } break; case 0x67: /* SET HANDLE COUNT */ TRACE( "SET HANDLE COUNT to %d\n", BX_reg(context) ); if (SetHandleCount( BX_reg(context) ) < BX_reg(context) ) bSetDOSExtendedError = TRUE; break; case 0x68: /* "FFLUSH" - COMMIT FILE */ TRACE( "FFLUSH - handle %d\n", BX_reg(context) ); if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) )) bSetDOSExtendedError = TRUE; break; case 0x69: /* DISK SERIAL NUMBER */ switch (AL_reg(context)) { case 0x00: TRACE("GET DISK SERIAL NUMBER for drive %s\n", INT21_DriveName(BL_reg(context))); if (!INT21_GetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE; else SET_AX( context, 0 ); break; case 0x01: TRACE("SET DISK SERIAL NUMBER for drive %s\n", INT21_DriveName(BL_reg(context))); if (!INT21_SetDiskSerialNumber(context)) bSetDOSExtendedError = TRUE; else SET_AX( context, 1 ); break; } break; case 0x6a: /* COMMIT FILE */ TRACE( "COMMIT FILE - handle %d\n", BX_reg(context) ); if (!FlushFileBuffers( DosFileHandleToWin32Handle(BX_reg(context)) )) bSetDOSExtendedError = TRUE; break; case 0x6b: /* NULL FUNCTION FOR CP/M COMPATIBILITY */ SET_AL( context, 0 ); break; case 0x6c: /* EXTENDED OPEN/CREATE */ if (!INT21_CreateFile( context, context->Esi, TRUE, BX_reg(context), DL_reg(context) )) bSetDOSExtendedError = TRUE; break; case 0x70: /* MSDOS 7 - GET/SET INTERNATIONALIZATION INFORMATION */ FIXME( "MS-DOS 7 - GET/SET INTERNATIONALIZATION INFORMATION\n" ); SET_CFLAG( context ); SET_AL( context, 0 ); break; case 0x71: /* MSDOS 7 - LONG FILENAME FUNCTIONS */ INT21_LongFilename( context ); break; case 0x73: /* MSDOS7 - FAT32 */ RESET_CFLAG( context ); if (!INT21_Fat32( context )) bSetDOSExtendedError = TRUE; break; case 0xdc: /* CONNECTION SERVICES - GET CONNECTION NUMBER */ TRACE( "CONNECTION SERVICES - GET CONNECTION NUMBER - ignored\n" ); break; case 0xea: /* NOVELL NETWARE - RETURN SHELL VERSION */ TRACE( "NOVELL NETWARE - RETURN SHELL VERSION - ignored\n" ); break; case 0xff: /* DOS32 EXTENDER (DOS/4GW) - API */ /* we don't implement a DOS32 extender */ TRACE( "DOS32 EXTENDER API - ignored\n" ); break; default: INT_BARF( context, 0x21 ); break; } /* END OF SWITCH */ /* Set general error condition. */ if (bSetDOSExtendedError) { SET_AX( context, GetLastError() ); SET_CFLAG( context ); } /* Print error code if carry flag is set. */ if (context->EFlags & 0x0001) TRACE("failed, error %d\n", GetLastError() ); TRACE( "returning: AX=%04x BX=%04x CX=%04x DX=%04x " "SI=%04x DI=%04x DS=%04x ES=%04x EFL=%08x\n", AX_reg(context), BX_reg(context), CX_reg(context), DX_reg(context), SI_reg(context), DI_reg(context), (WORD)context->SegDs, (WORD)context->SegEs, context->EFlags ); }