/* * sys_windows.cpp - System dependent routines, Windows implementation * * Basilisk II (C) 1997-2008 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdeps.h" #include #include typedef std::basic_string tstring; #include using std::min; #include "main.h" #include "util_windows.h" #include "macos_util.h" #include "prefs.h" #include "user_strings.h" #include "sys.h" #include "cd_defs.h" #include "cdenable/ntcd.h" #include "cdenable/cache.h" #include "cdenable/eject_nt.h" #if defined(BINCUE) #include "bincue.h" #endif #define DEBUG 0 #include "debug.h" // File handles are pointers to these structures struct file_handle { TCHAR *name; // Copy of device/file name HANDLE fh; bool is_file; // Flag: plain file or physical device? bool is_floppy; // Flag: floppy device bool is_cdrom; // Flag: CD-ROM device bool read_only; // Copy of Sys_open() flag loff_t start_byte; // Size of file header (if any) loff_t file_size; // Size of file data (only valid if is_file is true) cachetype cache; bool is_media_present; #if defined(BINCUE) bool is_bincue; // Flag: BIN CUE file void *bincue_fd; file_handle() {is_bincue = false;} // default bincue false #endif }; // Open file handles struct open_file_handle { file_handle *fh; open_file_handle *next; }; static open_file_handle *open_file_handles = NULL; // File handle of first floppy drive (for SysMountFirstFloppy()) static file_handle *first_floppy = NULL; // CD-ROM variables static const int CD_READ_AHEAD_SECTORS = 16; static char *sector_buffer = NULL; // Prototypes static bool is_cdrom_readable(file_handle *fh); static DWORD file_offset_read(HANDLE fh, loff_t offset, int count, char *buf); /* * Initialization */ void SysInit(void) { // Initialize CD-ROM driver sector_buffer = (char *)VirtualAlloc(NULL, 8192, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); CdenableSysInstallStart(); } /* * Deinitialization */ void SysExit(void) { if (sector_buffer) { VirtualFree(sector_buffer, 0, MEM_RELEASE ); sector_buffer = NULL; } } /* * Manage open file handles */ static void sys_add_file_handle(file_handle *fh) { open_file_handle *p = new open_file_handle; p->fh = fh; p->next = open_file_handles; open_file_handles = p; } static void sys_remove_file_handle(file_handle *fh) { open_file_handle *p = open_file_handles; open_file_handle *q = NULL; while (p) { if (p->fh == fh) { if (q) q->next = p->next; else open_file_handles = p->next; delete p; break; } q = p; p = p->next; } } /* * Mount removable media now */ void mount_removable_media(int media) { for (open_file_handle *p = open_file_handles; p != NULL; p = p->next) { file_handle * const fh = p->fh; if (fh->is_cdrom && (media & MEDIA_CD)) { cache_clear(&fh->cache); fh->start_byte = 0; if (fh->fh && fh->fh != INVALID_HANDLE_VALUE) CloseHandle(fh->fh); // Re-open device TCHAR device_name[MAX_PATH]; _sntprintf(device_name, lengthof(device_name), TEXT("\\\\.\\%c:"), fh->name[0]); fh->fh = CreateFile( device_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (fh->fh != INVALID_HANDLE_VALUE) { fh->is_media_present = is_cdrom_readable(fh); if (fh->is_media_present) MountVolume(fh); } else { fh->is_media_present = false; } } } } /* * Account for media that has just arrived */ void SysMediaArrived(void) { mount_removable_media(MEDIA_REMOVABLE); } /* * Account for media that has just been removed */ void SysMediaRemoved(void) { } /* * Mount first floppy disk */ void SysMountFirstFloppy(void) { if (first_floppy) MountVolume(first_floppy); } /* * This gets called when no "floppy" prefs items are found * It scans for available floppy drives and adds appropriate prefs items */ void SysAddFloppyPrefs(void) { } /* * This gets called when no "disk" prefs items are found * It scans for available HFS volumes and adds appropriate prefs items */ void SysAddDiskPrefs(void) { } /* * This gets called when no "cdrom" prefs items are found * It scans for available CD-ROM drives and adds appropriate prefs items */ void SysAddCDROMPrefs(void) { // Don't scan for drives if nocdrom option given if (PrefsFindBool("nocdrom")) return; char rootdir[] = "C:\\"; for (; rootdir[0] <= 'Z'; rootdir[0]++) { if (GetDriveTypeA(rootdir) == DRIVE_CDROM) PrefsAddString("cdrom", rootdir); } } /* * Add default serial prefs (must be added, even if no ports present) */ void SysAddSerialPrefs(void) { PrefsAddString("seriala", "COM1"); PrefsAddString("serialb", "COM2"); } /* * Read CD-ROM * Must give cd some time to settle * Can't give too much however, would be annoying, this is difficult.. */ static inline int cd_read_with_retry(file_handle *fh, ULONG offset, int count, char *buf ) { if (!fh || !fh->fh) return 0; DWORD bytes_read = CdenableSysReadCdBytes(fh->fh, offset, count, buf); if (bytes_read == 0) { // fall back to logical volume handle read in the case where there's no cdenable bytes_read = file_offset_read(fh->fh, offset, count, buf); } return bytes_read; } /* * Generic offset read function for a file or a device that behaves like one */ static DWORD file_offset_read(HANDLE fh, loff_t offset, int count, char *buf) { // Seek to position LONG lo = (LONG)offset; LONG hi = (LONG)(offset >> 32); DWORD r = SetFilePointer(fh, lo, &hi, FILE_BEGIN); if (r == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { return 0; } DWORD bytes_read; // Read data if (ReadFile(fh, buf, count, &bytes_read, NULL) == 0) bytes_read = 0; return bytes_read; } static int cd_read(file_handle *fh, cachetype *cptr, ULONG LBA, int count, char *buf) { ULONG l1, l2, cc; int i, c_count, got_bytes = 0, nblocks, s_inx, ss, first_block; int ok_bytes = 0; char *ptr, *ttptr = 0, *tmpbuf; if (count <= 0) return 0; if (!fh || !fh->fh) return 0; ss = 2048; l1 = (LBA / ss) * ss; l2 = ((LBA + count - 1 + ss) / ss) * ss; cc = l2 - l1; nblocks = cc / ss; first_block = LBA / ss; ptr = buf; s_inx = LBA - l1; c_count = ss - s_inx; if (c_count > count) c_count = count; for (i = 0; i < nblocks; i++) { if (!cache_get(cptr, first_block + i, sector_buffer)) break; memcpy(ptr, sector_buffer + s_inx, c_count); ok_bytes += c_count; ptr += c_count; s_inx = 0; c_count = ss; if (c_count > count - ok_bytes) c_count = count - ok_bytes; } if (i != nblocks && count != ok_bytes) { int bytes_left = count - ok_bytes; int blocks_left = nblocks - i; int alignedleft; // NEW read ahead code: int ahead = CD_READ_AHEAD_SECTORS; if (blocks_left < ahead) { nblocks += (ahead - blocks_left); blocks_left = ahead; } alignedleft = blocks_left*ss; tmpbuf = (char *)VirtualAlloc( NULL, alignedleft, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (tmpbuf) { got_bytes = cd_read_with_retry(fh, (first_block + i) * ss, alignedleft, tmpbuf); if (got_bytes != alignedleft) { // should never happen // Yes it does ... if (got_bytes < 0) got_bytes = 0; if (c_count > got_bytes) c_count = got_bytes; if (c_count > 0) { ttptr = tmpbuf; memcpy(ptr, ttptr + s_inx, c_count); ok_bytes += c_count; } VirtualFree(tmpbuf, 0, MEM_RELEASE ); return ok_bytes; } ttptr = tmpbuf; for ( ; i < nblocks; i++) { if (c_count > 0) { memcpy(ptr, ttptr + s_inx, c_count); ok_bytes += c_count; ptr += c_count; } s_inx = 0; c_count = ss; if (c_count > count - ok_bytes) c_count = count - ok_bytes; cache_put(cptr, first_block + i, ttptr, ss); ttptr += ss; } VirtualFree(tmpbuf, 0, MEM_RELEASE ); } } return ok_bytes; } /* * Check if file handle FH represents a readable CD-ROM */ static bool is_cdrom_readable(file_handle *fh) { if (!fh || !fh->fh) return false; cache_clear(&fh->cache); DWORD dummy; bool result = (0 != DeviceIoControl( fh->fh, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &dummy, NULL)); if (!result) { const size_t n_bytes = 2048; char *buffer = (char *)VirtualAlloc(NULL, n_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (buffer) { result = (cd_read_with_retry(fh, 0, n_bytes, buffer) == n_bytes); VirtualFree(buffer, 0, MEM_RELEASE); } } return result; } /* * Check if NAME represents a read-only file */ static bool is_read_only_path(const TCHAR *name) { DWORD attrib = GetFileAttributes(name); return (attrib != INVALID_FILE_ATTRIBUTES && ((attrib & FILE_ATTRIBUTE_READONLY) != 0)); } /* * Open file/device, create new file handle (returns NULL on error) */ void *Sys_open(const char *path_name, bool read_only, bool is_cdrom) { file_handle * fh = NULL; // Parse path name and options TCHAR name[MAX_PATH]; tcslcpy(name, path_name, lengthof(name)); // Normalize floppy / cd path int name_len = _tcslen(name); if (name_len == 1 && _istalpha(name[0])) _tcscat(name, TEXT(":\\")); if (name_len > 0 && name[name_len - 1] == TEXT(':')) _tcscat(name, TEXT("\\")); name_len = _tcslen(name); D(bug(TEXT("Sys_open(%s, %s)\n"), name, read_only ? TEXT("read-only") : TEXT("read/write"))); if (name_len > 0 && name[name_len - 1] == TEXT('\\')) { int type = GetDriveType(name); if (type == DRIVE_CDROM) { read_only = true; TCHAR device_name[MAX_PATH]; _sntprintf(device_name, lengthof(device_name), TEXT("\\\\.\\%c:"), name[0]); // Open device HANDLE h = CreateFile( device_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h != INVALID_HANDLE_VALUE) { fh = new file_handle; fh->name = _tcsdup(name); fh->fh = h; fh->is_file = false; fh->read_only = read_only; fh->start_byte = 0; fh->is_floppy = false; fh->is_cdrom = true; memset(&fh->cache, 0, sizeof(cachetype)); cache_init(&fh->cache); cache_clear(&fh->cache); if (!PrefsFindBool("nocdrom")) fh->is_media_present = is_cdrom_readable(fh); } } } else { // Hard file // Check if write access is allowed, set read-only flag if not if (!read_only && is_read_only_path(name)) read_only = true; // Open file #if defined(BINCUE) void *binfd = open_bincue(name); // check if bincue #endif HANDLE h = CreateFile( name, read_only ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE && !read_only) { // Read-write failed, try read-only read_only = true; h = CreateFile( name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } if (h != INVALID_HANDLE_VALUE) { fh = new file_handle; fh->name = _tcsdup(name); fh->fh = h; fh->is_file = true; fh->read_only = read_only; fh->start_byte = 0; fh->is_floppy = false; fh->is_cdrom = false; #if defined(BINCUE) if (binfd) { fh->bincue_fd = binfd; fh->is_bincue = true; fh->is_media_present = true; sys_add_file_handle(fh); return fh; } #endif // Detect disk image file layout loff_t size = GetFileSize(h, NULL); DWORD bytes_read; uint8 data[256]; ReadFile(h, data, sizeof(data), &bytes_read, NULL); FileDiskLayout(size, data, fh->start_byte, fh->file_size); } } if (fh) { if (fh->is_floppy && first_floppy == NULL) first_floppy = fh; sys_add_file_handle(fh); } return fh; } /* * Close file/device, delete file handle */ void Sys_close(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return; sys_remove_file_handle(fh); #if defined(BINCUE) if (fh->is_bincue) close_bincue(fh->bincue_fd); #endif if (fh->is_cdrom) { cache_final(&fh->cache); SysAllowRemoval((void *)fh); } if (fh->fh != NULL) { CloseHandle(fh->fh); fh->fh = NULL; } if (fh->name) free(fh->name); delete fh; } /* * Read "length" bytes from file/device, starting at "offset", to "buffer", * returns number of bytes read (or 0) */ size_t Sys_read(void *arg, void *buffer, loff_t offset, size_t length) { file_handle *fh = (file_handle *)arg; if (!fh) return 0; #if defined(BINCUE) if (fh->is_bincue) return read_bincue(fh->bincue_fd, buffer, offset, length); #endif DWORD bytes_read = 0; if (fh->is_file) { bytes_read = file_offset_read(fh->fh, offset, length, (char *)buffer); } else if (fh->is_cdrom) { int bytes_left, try_bytes, got_bytes; char *b = (char *)buffer; bytes_left = length; while (bytes_left) { try_bytes = min(bytes_left, 32768); if (fh->is_cdrom) { got_bytes = cd_read(fh, &fh->cache, (DWORD)offset, try_bytes, b); if (got_bytes != try_bytes && !PrefsFindBool("nocdrom")) fh->is_media_present = is_cdrom_readable(fh); } b += got_bytes; offset += got_bytes; bytes_read += got_bytes; bytes_left -= got_bytes; if (got_bytes != try_bytes) bytes_left = 0; } } // TODO: other media return bytes_read; } /* * Write "length" bytes from "buffer" to file/device, starting at "offset", * returns number of bytes written (or 0) */ size_t Sys_write(void *arg, void *buffer, loff_t offset, size_t length) { file_handle *fh = (file_handle *)arg; if (!fh) return 0; DWORD bytes_written = 0; if (fh->is_file) { // Seek to position LONG lo = (LONG)offset; LONG hi = (LONG)(offset >> 32); DWORD r = SetFilePointer(fh->fh, lo, &hi, FILE_BEGIN); if (r == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) return 0; // Write data if (WriteFile(fh->fh, buffer, length, &bytes_written, NULL) == 0) bytes_written = 0; } // TODO: other media return bytes_written; } /* * Return size of file/device (minus header) */ loff_t SysGetFileSize(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return true; #if defined(BINCUE) if (fh->is_bincue) return size_bincue(fh->bincue_fd); #endif if (fh->is_file) return fh->file_size; else if (fh->is_cdrom) return 0x28A00000; // FIXME: get real CD-ROM size else { // TODO: other media return 0; } } /* * Eject volume (if applicable) */ void SysEject(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return; #if defined(BINCUE) if (fh->is_bincue) { fh->is_media_present = false; return; } #endif if (fh->is_cdrom && fh->fh) { fh->is_media_present = false; // Commented out because there was some problems, but can't remember // exactly ... need to find out // EjectVolume(toupper(*fh->name),false); // Preventing is cumulative, try to make sure it's indeed released now for (int i = 0; i < 10; i++) PreventRemovalOfVolume(fh->fh, false); if (!PrefsFindBool("nocdrom")) { DWORD dummy; DeviceIoControl( fh->fh, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dummy, NULL ); } cache_clear(&fh->cache); fh->start_byte = 0; } // TODO: handle floppies } /* * Format volume (if applicable) */ bool SysFormat(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return false; //!! return true; } /* * Check if file/device is read-only (this includes the read-only flag on Sys_open()) */ bool SysIsReadOnly(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return true; return fh->read_only; } /* * Check if the given file handle refers to a fixed or a removable disk */ bool SysIsFixedDisk(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return true; if (fh->is_file) return true; else if (fh->is_floppy || fh->is_cdrom) return false; else return true; } /* * Check if a disk is inserted in the drive (always true for files) */ bool SysIsDiskInserted(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return false; if (fh->is_file) return true; else if (fh->is_cdrom && !PrefsFindBool("nocdrom")) { if (PrefsFindBool("pollmedia")) fh->is_media_present = is_cdrom_readable(fh); return fh->is_media_present; } else { // TODO: other media } return false; } /* * Prevent medium removal (if applicable) */ void SysPreventRemoval(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return; if (fh->is_cdrom && fh->fh) PreventRemovalOfVolume(fh->fh, true); } /* * Allow medium removal (if applicable) */ void SysAllowRemoval(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return; if (fh->is_cdrom && fh->fh) PreventRemovalOfVolume(fh->fh, false); } /* * Read CD-ROM TOC (binary MSF format, 804 bytes max.) */ bool SysCDReadTOC(void *arg, uint8 *toc) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return readtoc_bincue(fh->bincue_fd, toc); #endif if (fh->is_cdrom) { DWORD dummy; return DeviceIoControl(fh->fh, IOCTL_CDROM_READ_TOC, NULL, 0, toc, min((int)sizeof(CDROM_TOC), 804), &dummy, NULL) != FALSE; } else return false; } /* * Read CD-ROM position data (Sub-Q Channel, 16 bytes, see SCSI standard) */ bool SysCDGetPosition(void *arg, uint8 *pos) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return GetPosition_bincue(fh->bincue_fd, pos); #endif if (fh->is_cdrom) { SUB_Q_CHANNEL_DATA q_data; CDROM_SUB_Q_DATA_FORMAT q_format; q_format.Format = IOCTL_CDROM_CURRENT_POSITION; q_format.Track = 0; // used only by ISRC reads DWORD dwBytesReturned = 0; bool ok = DeviceIoControl(fh->fh, IOCTL_CDROM_READ_Q_CHANNEL, &q_format, sizeof(CDROM_SUB_Q_DATA_FORMAT), &q_data, sizeof(SUB_Q_CHANNEL_DATA), &dwBytesReturned, NULL) != FALSE; if (ok) memcpy(pos, &q_data.CurrentPosition, sizeof(SUB_Q_CURRENT_POSITION)); return ok; } else return false; } /* * Play CD audio */ bool SysCDPlay(void *arg, uint8 start_m, uint8 start_s, uint8 start_f, uint8 end_m, uint8 end_s, uint8 end_f) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return CDPlay_bincue(fh->bincue_fd, start_m, start_s, start_f, end_m, end_s, end_f); #endif if (fh->is_cdrom) { CDROM_PLAY_AUDIO_MSF msf; msf.StartingM = start_m; msf.StartingS = start_s; msf.StartingF = start_f; msf.EndingM = end_m; msf.EndingS = end_s; msf.EndingF = end_f; DWORD dwBytesReturned = 0; return DeviceIoControl(fh->fh, IOCTL_CDROM_PLAY_AUDIO_MSF, &msf, sizeof(CDROM_PLAY_AUDIO_MSF), NULL, 0, &dwBytesReturned, NULL) != FALSE; } else return false; } /* * Pause CD audio */ bool SysCDPause(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return CDPause_bincue(fh->bincue_fd); #endif if (fh->is_cdrom) { DWORD dwBytesReturned = 0; return DeviceIoControl(fh->fh, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &dwBytesReturned, NULL) != FALSE; } else return false; } /* * Resume paused CD audio */ bool SysCDResume(void *arg) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return CDResume_bincue(fh->bincue_fd); #endif if (fh->is_cdrom) { DWORD dwBytesReturned = 0; return DeviceIoControl(fh->fh, IOCTL_CDROM_RESUME_AUDIO, NULL, 0, NULL, 0, &dwBytesReturned, NULL) != FALSE; } else return false; } /* * Stop CD audio */ bool SysCDStop(void *arg, uint8 lead_out_m, uint8 lead_out_s, uint8 lead_out_f) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return CDStop_bincue(fh->bincue_fd); #endif if (fh->is_cdrom) { DWORD dwBytesReturned = 0; return DeviceIoControl(fh->fh, IOCTL_CDROM_STOP_AUDIO, NULL, 0, NULL, 0, &dwBytesReturned, NULL) != FALSE; } else return false; } /* * Perform CD audio fast-forward/fast-reverse operation starting from specified address */ bool SysCDScan(void *arg, uint8 start_m, uint8 start_s, uint8 start_f, bool reverse) { file_handle *fh = (file_handle *)arg; if (!fh) return false; #if defined(BINCUE) if (fh->is_bincue) return CDScan_bincue(fh->bincue_fd,start_m,start_s,start_f,reverse); #endif if (fh->is_cdrom) { CDROM_SEEK_AUDIO_MSF msf; msf.M = start_m; msf.S = start_s; msf.F = start_f; DWORD dwBytesReturned = 0; return DeviceIoControl(fh->fh, IOCTL_CDROM_SEEK_AUDIO_MSF, &msf, sizeof(CDROM_SEEK_AUDIO_MSF), NULL, 0, &dwBytesReturned, NULL) != FALSE; } else return false; } /* * Set CD audio volume (0..255 each channel) */ void SysCDSetVolume(void *arg, uint8 left, uint8 right) { file_handle *fh = (file_handle *)arg; if (!fh) return; #if defined(BINCUE) if (fh->is_bincue) CDSetVol_bincue(fh->bincue_fd,left,right); #endif if (fh->is_cdrom) { VOLUME_CONTROL vc; vc.PortVolume[0] = left; vc.PortVolume[1] = right; vc.PortVolume[2] = left; vc.PortVolume[3] = right; DWORD dwBytesReturned = 0; DeviceIoControl(fh->fh, IOCTL_CDROM_SET_VOLUME, &vc, sizeof(VOLUME_CONTROL), NULL, 0, &dwBytesReturned, NULL); } } /* * Get CD audio volume (0..255 each channel) */ void SysCDGetVolume(void *arg, uint8 &left, uint8 &right) { file_handle *fh = (file_handle *)arg; if (!fh) return; left = right = 0; #if defined(BINCUE) if (fh->is_bincue) CDGetVol_bincue(fh->bincue_fd,&left,&right); #endif if (fh->is_cdrom) { VOLUME_CONTROL vc; memset(&vc, 0, sizeof(vc)); DWORD dwBytesReturned = 0; if (DeviceIoControl(fh->fh, IOCTL_CDROM_GET_VOLUME, NULL, 0, &vc, sizeof(VOLUME_CONTROL), &dwBytesReturned, NULL)) { left = vc.PortVolume[0]; right = vc.PortVolume[1]; } } }