2001-10-03 17:10:38 +04:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
2006-03-26 04:38:58 +04:00
|
|
|
// $Id: cdrom.cc,v 1.89 2006-03-26 00:38:57 vruppert Exp $
|
2001-10-03 17:10:38 +04:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2002-02-01 19:46:27 +03:00
|
|
|
// Copyright (C) 2002 MandrakeSoft S.A.
|
2001-04-10 05:04:59 +04:00
|
|
|
//
|
|
|
|
// MandrakeSoft S.A.
|
|
|
|
// 43, rue d'Aboukir
|
|
|
|
// 75002 Paris - France
|
|
|
|
// http://www.linux-mandrake.com/
|
|
|
|
// http://www.mandrakesoft.com/
|
|
|
|
//
|
|
|
|
// 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
|
|
|
|
// These are the low-level CDROM functions which are called
|
|
|
|
// from 'harddrv.cc'. They effect the OS specific functionality
|
|
|
|
// needed by the CDROM emulation in 'harddrv.cc'. Mostly, just
|
|
|
|
// ioctl() calls and such. Should be fairly easy to add support
|
|
|
|
// for your OS if it is not supported yet.
|
|
|
|
|
2006-03-07 21:16:41 +03:00
|
|
|
#ifndef BX_IODEV_CDROM_H
|
|
|
|
#define BX_IODEV_CDROM_H
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-10-25 01:07:56 +04:00
|
|
|
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
|
|
|
|
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
|
|
|
|
// is used to know when we are exporting symbols and when we are importing.
|
|
|
|
#define BX_PLUGGABLE
|
|
|
|
|
2004-09-05 14:30:19 +04:00
|
|
|
#include "bochs.h"
|
2002-11-19 08:47:45 +03:00
|
|
|
#if BX_SUPPORT_CDROM
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2004-09-05 14:30:19 +04:00
|
|
|
#include "cdrom.h"
|
|
|
|
|
merge in BRANCH-io-cleanup.
To see the commit logs for this use either cvsweb or
cvs update -r BRANCH-io-cleanup and then 'cvs log' the various files.
In general this provides a generic interface for logging.
logfunctions:: is a class that is inherited by some classes, and also
. allocated as a standalone global called 'genlog'. All logging uses
. one of the ::info(), ::error(), ::ldebug(), ::panic() methods of this
. class through 'BX_INFO(), BX_ERROR(), BX_DEBUG(), BX_PANIC()' macros
. respectively.
.
. An example usage:
. BX_INFO(("Hello, World!\n"));
iofunctions:: is a class that is allocated once by default, and assigned
as the iofunction of each logfunctions instance. It is this class that
maintains the file descriptor and other output related code, at this
point using vfprintf(). At some future point, someone may choose to
write a gui 'console' for bochs to which messages would be redirected
simply by assigning a different iofunction class to the various logfunctions
objects.
More cleanup is coming, but this works for now. If you want to see alot
of debugging output, in main.cc, change onoff[LOGLEV_DEBUG]=0 to =1.
Comments, bugs, flames, to me: todd@fries.net
2001-05-15 18:49:57 +04:00
|
|
|
#define LOG_THIS /* no SMF tricks here, not needed */
|
|
|
|
|
2001-10-07 07:34:54 +04:00
|
|
|
extern "C" {
|
2001-10-06 21:32:58 +04:00
|
|
|
#include <errno.h>
|
2001-10-07 07:34:54 +04:00
|
|
|
}
|
2001-10-06 21:32:58 +04:00
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
#ifdef __linux__
|
|
|
|
extern "C" {
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <linux/cdrom.h>
|
|
|
|
// I use the framesize in non OS specific code too
|
|
|
|
#define BX_CD_FRAMESIZE CD_FRAMESIZE
|
|
|
|
}
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(__GNU__) || (defined(__CYGWIN32__) && !defined(WIN32))
|
2002-05-31 13:56:58 +04:00
|
|
|
extern "C" {
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#define BX_CD_FRAMESIZE 2048
|
|
|
|
#define CD_FRAMESIZE 2048
|
|
|
|
}
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif BX_WITH_MACOS
|
2002-12-12 18:29:45 +03:00
|
|
|
#define BX_CD_FRAMESIZE 2048
|
|
|
|
#define CD_FRAMESIZE 2048
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(__sun)
|
2001-04-10 06:15:31 +04:00
|
|
|
extern "C" {
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/cdio.h>
|
|
|
|
#define BX_CD_FRAMESIZE CDROM_BLK_2048
|
|
|
|
}
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(__DJGPP__)
|
2002-11-20 15:23:42 +03:00
|
|
|
extern "C" {
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#define BX_CD_FRAMESIZE 2048
|
|
|
|
#define CD_FRAMESIZE 2048
|
|
|
|
}
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(__BEOS__)
|
2002-02-07 20:38:33 +03:00
|
|
|
#include "cdrom_beos.h"
|
2001-12-08 16:07:07 +03:00
|
|
|
#define BX_CD_FRAMESIZE 2048
|
|
|
|
|
2004-09-11 19:39:53 +04:00
|
|
|
#elif (defined(__NetBSD__) || defined(__NetBSD_kernel__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__))
|
2001-05-08 23:51:46 +04:00
|
|
|
// OpenBSD pre version 2.7 may require extern "C" { } structure around
|
2001-06-19 01:14:00 +04:00
|
|
|
// all the includes, because the i386 sys/disklabel.h contains code which
|
2001-05-08 23:51:46 +04:00
|
|
|
// c++ considers invalid.
|
2001-04-10 05:04:59 +04:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/file.h>
|
|
|
|
#include <sys/cdio.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/disklabel.h>
|
2003-01-11 01:59:44 +03:00
|
|
|
// ntohl(x) et al have been moved out of sys/param.h in FreeBSD 5
|
|
|
|
#include <netinet/in.h>
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
// XXX
|
|
|
|
#define BX_CD_FRAMESIZE 2048
|
2005-10-27 21:01:11 +04:00
|
|
|
#define CD_FRAMESIZE 2048
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(__APPLE__)
|
2002-11-01 19:36:27 +03:00
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/ioctl.h>
|
2005-07-24 11:25:02 +04:00
|
|
|
#if defined (__GNUC__) && ( __GNUC__ >= 4 )
|
|
|
|
#include <sys/disk.h>
|
|
|
|
#else
|
2002-11-01 19:36:27 +03:00
|
|
|
#include <dev/disk.h>
|
2005-07-24 11:25:02 +04:00
|
|
|
#endif
|
2002-11-01 19:36:27 +03:00
|
|
|
#include <errno.h>
|
|
|
|
#include <paths.h>
|
|
|
|
#include <sys/param.h>
|
|
|
|
|
2004-02-14 09:33:29 +03:00
|
|
|
#define Float32 KLUDGE_Float32
|
|
|
|
#define Float64 KLUDGE_Float64
|
2002-11-01 19:36:27 +03:00
|
|
|
#include <IOKit/IOKitLib.h>
|
|
|
|
#include <IOKit/IOBSD.h>
|
|
|
|
#include <IOKit/storage/IOCDMedia.h>
|
|
|
|
#include <IOKit/storage/IOMedia.h>
|
|
|
|
#include <IOKit/storage/IOCDTypes.h>
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
2004-05-30 23:20:53 +04:00
|
|
|
#undef Float32
|
|
|
|
#undef Float64
|
2002-11-01 19:36:27 +03:00
|
|
|
|
|
|
|
// These definitions were taken from mount_cd9660.c
|
|
|
|
// There are some similar definitions in IOCDTypes.h
|
|
|
|
// however there seems to be some dissagreement in
|
|
|
|
// the definition of CDTOC.length
|
|
|
|
struct _CDMSF {
|
|
|
|
u_char minute;
|
|
|
|
u_char second;
|
|
|
|
u_char frame;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define MSF_TO_LBA(msf) \
|
|
|
|
(((((msf).minute * 60UL) + (msf).second) * 75UL) + (msf).frame - 150)
|
|
|
|
|
|
|
|
struct _CDTOC_Desc {
|
|
|
|
u_char session;
|
|
|
|
u_char ctrl_adr; /* typed to be machine and compiler independent */
|
|
|
|
u_char tno;
|
|
|
|
u_char point;
|
|
|
|
struct _CDMSF address;
|
|
|
|
u_char zero;
|
|
|
|
struct _CDMSF p;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _CDTOC {
|
|
|
|
u_short length; /* in native cpu endian */
|
|
|
|
u_char first_session;
|
|
|
|
u_char last_session;
|
|
|
|
struct _CDTOC_Desc trackdesc[1];
|
|
|
|
};
|
|
|
|
|
|
|
|
static kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator, mach_port_t *masterPort );
|
|
|
|
static kern_return_t GetDeviceFilePath( io_iterator_t mediaIterator, char *deviceFilePath, CFIndex maxPathSize );
|
|
|
|
//int OpenDrive( const char *deviceFilePath );
|
|
|
|
static struct _CDTOC * ReadTOC( const char * devpath );
|
|
|
|
|
|
|
|
static char CDDevicePath[ MAXPATHLEN ];
|
|
|
|
|
|
|
|
#define BX_CD_FRAMESIZE 2048
|
2005-10-27 21:01:11 +04:00
|
|
|
#define CD_FRAMESIZE 2048
|
2002-11-01 19:36:27 +03:00
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#elif defined(WIN32)
|
2002-10-04 01:07:04 +04:00
|
|
|
// windows.h included by bochs.h
|
2001-04-10 05:04:59 +04:00
|
|
|
#include <winioctl.h>
|
2001-06-25 06:18:16 +04:00
|
|
|
#include "aspi-win32.h"
|
|
|
|
#include "scsidefs.h"
|
|
|
|
|
|
|
|
DWORD (*GetASPI32SupportInfo)(void);
|
|
|
|
DWORD (*SendASPI32Command)(LPSRB);
|
|
|
|
BOOL (*GetASPI32Buffer)(PASPI32BUFF);
|
|
|
|
BOOL (*FreeASPI32Buffer)(PASPI32BUFF);
|
|
|
|
BOOL (*TranslateASPI32Address)(PDWORD,PDWORD);
|
|
|
|
DWORD (*GetASPI32DLLVersion)(void);
|
|
|
|
|
|
|
|
|
2004-08-22 20:23:35 +04:00
|
|
|
static OSVERSIONINFO osinfo;
|
|
|
|
static BOOL isWindowsXP;
|
2002-10-03 15:59:37 +04:00
|
|
|
static BOOL bHaveDev;
|
|
|
|
static UINT cdromCount = 0;
|
|
|
|
static HINSTANCE hASPI = NULL;
|
2001-06-25 06:18:16 +04:00
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
#define BX_CD_FRAMESIZE 2048
|
2005-10-27 21:01:11 +04:00
|
|
|
#define CD_FRAMESIZE 2048
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2004-08-23 13:39:45 +04:00
|
|
|
// READ_TOC_EX structure(s) and #defines
|
|
|
|
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_TOC 0x00
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_SESSION 0x01
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_FULL_TOC 0x02
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_PMA 0x03
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_ATIP 0x04
|
|
|
|
#define CDROM_READ_TOC_EX_FORMAT_CDTEXT 0x05
|
|
|
|
|
|
|
|
#define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM
|
|
|
|
#define IOCTL_CDROM_READ_TOC_EX CTL_CODE(IOCTL_CDROM_BASE, 0x0015, METHOD_BUFFERED, FILE_READ_ACCESS)
|
2005-11-12 01:52:57 +03:00
|
|
|
#ifndef IOCTL_DISK_GET_LENGTH_INFO
|
2005-11-02 00:43:47 +03:00
|
|
|
#define IOCTL_DISK_GET_LENGTH_INFO CTL_CODE(IOCTL_DISK_BASE, 0x0017, METHOD_BUFFERED, FILE_READ_ACCESS)
|
2005-11-12 01:52:57 +03:00
|
|
|
#endif
|
2004-08-23 13:39:45 +04:00
|
|
|
|
|
|
|
typedef struct _CDROM_READ_TOC_EX {
|
|
|
|
UCHAR Format : 4;
|
|
|
|
UCHAR Reserved1 : 3; // future expansion
|
|
|
|
UCHAR Msf : 1;
|
|
|
|
UCHAR SessionTrack;
|
|
|
|
UCHAR Reserved2; // future expansion
|
|
|
|
UCHAR Reserved3; // future expansion
|
|
|
|
} CDROM_READ_TOC_EX, *PCDROM_READ_TOC_EX;
|
|
|
|
|
|
|
|
typedef struct _TRACK_DATA {
|
|
|
|
UCHAR Reserved;
|
|
|
|
UCHAR Control : 4;
|
|
|
|
UCHAR Adr : 4;
|
|
|
|
UCHAR TrackNumber;
|
|
|
|
UCHAR Reserved1;
|
|
|
|
UCHAR Address[4];
|
|
|
|
} TRACK_DATA, *PTRACK_DATA;
|
|
|
|
|
|
|
|
typedef struct _CDROM_TOC_SESSION_DATA {
|
|
|
|
// Header
|
|
|
|
UCHAR Length[2]; // add two bytes for this field
|
|
|
|
UCHAR FirstCompleteSession;
|
|
|
|
UCHAR LastCompleteSession;
|
|
|
|
// One track, representing the first track
|
|
|
|
// of the last finished session
|
|
|
|
TRACK_DATA TrackData[1];
|
|
|
|
} CDROM_TOC_SESSION_DATA, *PCDROM_TOC_SESSION_DATA;
|
|
|
|
|
|
|
|
// End READ_TOC_EX structure(s) and #defines
|
|
|
|
|
2003-01-12 18:04:52 +03:00
|
|
|
#else // all others (Irix, Tru64)
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#define BX_CD_FRAMESIZE 2048
|
|
|
|
#define CD_FRAMESIZE 2048
|
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-06-19 01:11:46 +04:00
|
|
|
#include <stdio.h>
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-11-01 19:36:27 +03:00
|
|
|
#ifdef __APPLE__
|
|
|
|
static kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator,
|
|
|
|
mach_port_t *masterPort )
|
|
|
|
{
|
|
|
|
kern_return_t kernResult;
|
|
|
|
CFMutableDictionaryRef classesToMatch;
|
|
|
|
kernResult = IOMasterPort( bootstrap_port, masterPort );
|
|
|
|
if ( kernResult != KERN_SUCCESS )
|
|
|
|
{
|
|
|
|
fprintf ( stderr, "IOMasterPort returned %d\n", kernResult );
|
|
|
|
return kernResult;
|
|
|
|
}
|
|
|
|
// CD media are instances of class kIOCDMediaClass.
|
|
|
|
classesToMatch = IOServiceMatching( kIOCDMediaClass );
|
|
|
|
if ( classesToMatch == NULL )
|
|
|
|
fprintf ( stderr, "IOServiceMatching returned a NULL dictionary.\n" );
|
|
|
|
else
|
|
|
|
{
|
2003-12-09 02:49:48 +03:00
|
|
|
// Each IOMedia object has a property with key kIOMediaEjectableKey
|
2002-11-01 19:36:27 +03:00
|
|
|
// which is true if the media is indeed ejectable. So add property
|
|
|
|
// to CFDictionary for matching.
|
|
|
|
CFDictionarySetValue( classesToMatch,
|
2003-12-09 02:49:48 +03:00
|
|
|
CFSTR( kIOMediaEjectableKey ), kCFBooleanTrue );
|
2002-11-01 19:36:27 +03:00
|
|
|
}
|
|
|
|
kernResult = IOServiceGetMatchingServices( *masterPort,
|
|
|
|
classesToMatch, mediaIterator );
|
|
|
|
if ( (kernResult != KERN_SUCCESS) || (*mediaIterator == NULL) )
|
|
|
|
fprintf( stderr, "No ejectable CD media found.\n kernResult = %d\n", kernResult );
|
|
|
|
|
|
|
|
return kernResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static kern_return_t GetDeviceFilePath( io_iterator_t mediaIterator,
|
|
|
|
char *deviceFilePath, CFIndex maxPathSize )
|
|
|
|
{
|
|
|
|
io_object_t nextMedia;
|
|
|
|
kern_return_t kernResult = KERN_FAILURE;
|
|
|
|
nextMedia = IOIteratorNext( mediaIterator );
|
|
|
|
if ( nextMedia == NULL )
|
|
|
|
{
|
|
|
|
*deviceFilePath = '\0';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CFTypeRef deviceFilePathAsCFString;
|
|
|
|
deviceFilePathAsCFString = IORegistryEntryCreateCFProperty(
|
2003-12-09 02:49:48 +03:00
|
|
|
nextMedia, CFSTR( kIOBSDNameKey ),
|
2002-11-01 19:36:27 +03:00
|
|
|
kCFAllocatorDefault, 0 );
|
|
|
|
*deviceFilePath = '\0';
|
|
|
|
if ( deviceFilePathAsCFString )
|
|
|
|
{
|
|
|
|
size_t devPathLength = strlen( _PATH_DEV );
|
|
|
|
strcpy( deviceFilePath, _PATH_DEV );
|
|
|
|
if ( CFStringGetCString( (const __CFString *) deviceFilePathAsCFString,
|
|
|
|
deviceFilePath + devPathLength,
|
|
|
|
maxPathSize - devPathLength,
|
|
|
|
kCFStringEncodingASCII ) )
|
|
|
|
{
|
|
|
|
// fprintf( stderr, "BSD path: %s\n", deviceFilePath );
|
|
|
|
kernResult = KERN_SUCCESS;
|
|
|
|
}
|
|
|
|
CFRelease( deviceFilePathAsCFString );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IOObjectRelease( nextMedia );
|
|
|
|
return kernResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int OpenDrive( const char *deviceFilePath )
|
|
|
|
{
|
|
|
|
|
|
|
|
int fileDescriptor;
|
|
|
|
|
|
|
|
fileDescriptor = open( deviceFilePath, O_RDONLY );
|
|
|
|
if ( fileDescriptor == -1 )
|
|
|
|
fprintf( stderr, "Error %d opening device %s.\n", errno, deviceFilePath );
|
|
|
|
return fileDescriptor;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct _CDTOC * ReadTOC( const char * devpath ) {
|
|
|
|
|
|
|
|
struct _CDTOC * toc_p = NULL;
|
|
|
|
io_iterator_t iterator = 0;
|
|
|
|
io_registry_entry_t service = 0;
|
|
|
|
CFDictionaryRef properties = 0;
|
|
|
|
CFDataRef data = 0;
|
|
|
|
mach_port_t port = 0;
|
|
|
|
char * devname;
|
|
|
|
|
|
|
|
if (( devname = strrchr( devpath, '/' )) != NULL ) {
|
|
|
|
++devname;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
devname = (char *) devpath;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( IOMasterPort(bootstrap_port, &port ) != KERN_SUCCESS ) {
|
|
|
|
fprintf( stderr, "IOMasterPort failed\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( IOServiceGetMatchingServices( port, IOBSDNameMatching( port, 0, devname ),
|
|
|
|
&iterator ) != KERN_SUCCESS ) {
|
|
|
|
fprintf( stderr, "IOServiceGetMatchingServices failed\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
service = IOIteratorNext( iterator );
|
|
|
|
|
|
|
|
IOObjectRelease( iterator );
|
|
|
|
|
|
|
|
iterator = 0;
|
|
|
|
|
|
|
|
while ( service && !IOObjectConformsTo( service, "IOCDMedia" )) {
|
|
|
|
if ( IORegistryEntryGetParentIterator( service, kIOServicePlane,
|
|
|
|
&iterator ) != KERN_SUCCESS ) {
|
|
|
|
fprintf( stderr, "IORegistryEntryGetParentIterator failed\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
IOObjectRelease( service );
|
|
|
|
service = IOIteratorNext( iterator );
|
|
|
|
IOObjectRelease( iterator );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( service == NULL ) {
|
|
|
|
fprintf( stderr, "CD media not found\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( IORegistryEntryCreateCFProperties( service, (__CFDictionary **) &properties,
|
|
|
|
kCFAllocatorDefault,
|
|
|
|
kNilOptions ) != KERN_SUCCESS ) {
|
|
|
|
fprintf( stderr, "IORegistryEntryGetParentIterator failed\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
2003-12-09 02:49:48 +03:00
|
|
|
data = (CFDataRef) CFDictionaryGetValue( properties, CFSTR(kIOCDMediaTOCKey) );
|
2002-11-01 19:36:27 +03:00
|
|
|
if ( data == NULL ) {
|
|
|
|
fprintf( stderr, "CFDictionaryGetValue failed\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
CFRange range;
|
|
|
|
CFIndex buflen;
|
|
|
|
|
|
|
|
buflen = CFDataGetLength( data ) + 1;
|
|
|
|
range = CFRangeMake( 0, buflen );
|
|
|
|
toc_p = (struct _CDTOC *) malloc( buflen );
|
|
|
|
if ( toc_p == NULL ) {
|
|
|
|
fprintf( stderr, "Out of memory\n" );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
CFDataGetBytes( data, range, (unsigned char *) toc_p );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
fprintf( stderr, "Table of contents\n length %d first %d last %d\n",
|
|
|
|
toc_p->length, toc_p->first_session, toc_p->last_session );
|
|
|
|
*/
|
|
|
|
|
|
|
|
CFRelease( properties );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
|
|
|
|
if ( service ) {
|
|
|
|
IOObjectRelease( service );
|
|
|
|
}
|
|
|
|
|
|
|
|
return toc_p;
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2001-06-25 06:18:16 +04:00
|
|
|
#ifdef WIN32
|
|
|
|
|
2002-09-26 13:00:52 +04:00
|
|
|
bool ReadCDSector(unsigned int hid, unsigned int tid, unsigned int lun, unsigned long frame, unsigned char *buf, int bufsize)
|
2001-06-25 06:18:16 +04:00
|
|
|
{
|
|
|
|
HANDLE hEventSRB;
|
|
|
|
SRB_ExecSCSICmd srb;
|
|
|
|
DWORD dwStatus;
|
|
|
|
|
|
|
|
hEventSRB = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
|
|
|
|
memset(&srb,0,sizeof(SRB_ExecSCSICmd));
|
|
|
|
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
|
|
|
srb.SRB_HaId = hid;
|
|
|
|
srb.SRB_Target = tid;
|
|
|
|
srb.SRB_Lun = lun;
|
|
|
|
srb.SRB_Flags = SRB_DIR_IN | SRB_EVENT_NOTIFY;
|
|
|
|
srb.SRB_SenseLen = SENSE_LEN;
|
|
|
|
srb.SRB_PostProc = hEventSRB;
|
|
|
|
srb.SRB_BufPointer = buf;
|
|
|
|
srb.SRB_BufLen = bufsize;
|
|
|
|
srb.SRB_CDBLen = 10;
|
|
|
|
srb.CDBByte[0] = SCSI_READ10;
|
2002-09-26 13:00:52 +04:00
|
|
|
srb.CDBByte[2] = (unsigned char) (frame>>24);
|
|
|
|
srb.CDBByte[3] = (unsigned char) (frame>>16);
|
|
|
|
srb.CDBByte[4] = (unsigned char) (frame>>8);
|
|
|
|
srb.CDBByte[5] = (unsigned char) (frame);
|
2001-06-25 06:18:16 +04:00
|
|
|
srb.CDBByte[7] = 0;
|
|
|
|
srb.CDBByte[8] = 1; /* read 1 frames */
|
|
|
|
|
|
|
|
ResetEvent(hEventSRB);
|
|
|
|
dwStatus = SendASPI32Command((SRB *)&srb);
|
|
|
|
if(dwStatus == SS_PENDING) {
|
|
|
|
WaitForSingleObject(hEventSRB, 100000);
|
|
|
|
}
|
|
|
|
CloseHandle(hEventSRB);
|
2002-03-02 14:31:18 +03:00
|
|
|
return (srb.SRB_TargStat == STATUS_GOOD);
|
2001-06-25 06:18:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int GetCDCapacity(unsigned int hid, unsigned int tid, unsigned int lun)
|
|
|
|
{
|
|
|
|
HANDLE hEventSRB;
|
|
|
|
SRB_ExecSCSICmd srb;
|
|
|
|
DWORD dwStatus;
|
2002-07-31 09:21:46 +04:00
|
|
|
unsigned char buf[8];
|
2001-06-25 06:18:16 +04:00
|
|
|
|
|
|
|
hEventSRB = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
|
|
|
|
memset(&buf, 0, sizeof(buf));
|
|
|
|
memset(&srb,0,sizeof(SRB_ExecSCSICmd));
|
|
|
|
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
|
|
|
srb.SRB_HaId = hid;
|
|
|
|
srb.SRB_Target = tid;
|
|
|
|
srb.SRB_Lun = lun;
|
|
|
|
srb.SRB_Flags = SRB_DIR_IN | SRB_EVENT_NOTIFY;
|
|
|
|
srb.SRB_SenseLen = SENSE_LEN;
|
|
|
|
srb.SRB_PostProc = hEventSRB;
|
|
|
|
srb.SRB_BufPointer = (unsigned char *)buf;
|
|
|
|
srb.SRB_BufLen = 8;
|
|
|
|
srb.SRB_CDBLen = 10;
|
|
|
|
srb.CDBByte[0] = SCSI_READCDCAP;
|
|
|
|
srb.CDBByte[2] = 0;
|
|
|
|
srb.CDBByte[3] = 0;
|
|
|
|
srb.CDBByte[4] = 0;
|
|
|
|
srb.CDBByte[5] = 0;
|
|
|
|
srb.CDBByte[8] = 0;
|
|
|
|
|
|
|
|
ResetEvent(hEventSRB);
|
|
|
|
dwStatus = SendASPI32Command((SRB *)&srb);
|
|
|
|
if(dwStatus == SS_PENDING) {
|
|
|
|
WaitForSingleObject(hEventSRB, 100000);
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(hEventSRB);
|
|
|
|
return ((buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]) * ((buf[4] << 24) + (buf[5] << 16) + (buf[6] << 8) + buf[7]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
cdrom_interface::cdrom_interface(char *dev)
|
|
|
|
{
|
2001-06-27 23:16:01 +04:00
|
|
|
put("CD");
|
merge in BRANCH-io-cleanup.
To see the commit logs for this use either cvsweb or
cvs update -r BRANCH-io-cleanup and then 'cvs log' the various files.
In general this provides a generic interface for logging.
logfunctions:: is a class that is inherited by some classes, and also
. allocated as a standalone global called 'genlog'. All logging uses
. one of the ::info(), ::error(), ::ldebug(), ::panic() methods of this
. class through 'BX_INFO(), BX_ERROR(), BX_DEBUG(), BX_PANIC()' macros
. respectively.
.
. An example usage:
. BX_INFO(("Hello, World!\n"));
iofunctions:: is a class that is allocated once by default, and assigned
as the iofunction of each logfunctions instance. It is this class that
maintains the file descriptor and other output related code, at this
point using vfprintf(). At some future point, someone may choose to
write a gui 'console' for bochs to which messages would be redirected
simply by assigning a different iofunction class to the various logfunctions
objects.
More cleanup is coming, but this works for now. If you want to see alot
of debugging output, in main.cc, change onoff[LOGLEV_DEBUG]=0 to =1.
Comments, bugs, flames, to me: todd@fries.net
2001-05-15 18:49:57 +04:00
|
|
|
settype(CDLOG);
|
2001-04-10 05:04:59 +04:00
|
|
|
fd = -1; // File descriptor not yet allocated
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
if (dev == NULL) {
|
2001-04-10 05:04:59 +04:00
|
|
|
path = NULL;
|
2005-10-27 21:01:11 +04:00
|
|
|
} else {
|
2001-04-10 05:04:59 +04:00
|
|
|
path = strdup(dev);
|
merge in BRANCH-io-cleanup.
To see the commit logs for this use either cvsweb or
cvs update -r BRANCH-io-cleanup and then 'cvs log' the various files.
In general this provides a generic interface for logging.
logfunctions:: is a class that is inherited by some classes, and also
. allocated as a standalone global called 'genlog'. All logging uses
. one of the ::info(), ::error(), ::ldebug(), ::panic() methods of this
. class through 'BX_INFO(), BX_ERROR(), BX_DEBUG(), BX_PANIC()' macros
. respectively.
.
. An example usage:
. BX_INFO(("Hello, World!\n"));
iofunctions:: is a class that is allocated once by default, and assigned
as the iofunction of each logfunctions instance. It is this class that
maintains the file descriptor and other output related code, at this
point using vfprintf(). At some future point, someone may choose to
write a gui 'console' for bochs to which messages would be redirected
simply by assigning a different iofunction class to the various logfunctions
objects.
More cleanup is coming, but this works for now. If you want to see alot
of debugging output, in main.cc, change onoff[LOGLEV_DEBUG]=0 to =1.
Comments, bugs, flames, to me: todd@fries.net
2001-05-15 18:49:57 +04:00
|
|
|
}
|
2001-06-19 01:11:46 +04:00
|
|
|
using_file=0;
|
2004-08-22 20:23:35 +04:00
|
|
|
#ifdef WIN32
|
|
|
|
bUseASPI = FALSE;
|
|
|
|
osinfo.dwOSVersionInfoSize = sizeof(osinfo);
|
|
|
|
GetVersionEx(&osinfo);
|
|
|
|
isWindowsXP = (osinfo.dwMajorVersion >= 5) && (osinfo.dwMinorVersion >= 1);
|
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2002-02-01 19:46:27 +03:00
|
|
|
|
2001-06-28 00:11:10 +04:00
|
|
|
void
|
|
|
|
cdrom_interface::init(void) {
|
2006-03-26 04:38:58 +04:00
|
|
|
BX_DEBUG(("Init $Id: cdrom.cc,v 1.89 2006-03-26 00:38:57 vruppert Exp $"));
|
2001-10-06 21:32:58 +04:00
|
|
|
BX_INFO(("file = '%s'",path));
|
2001-06-28 00:11:10 +04:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
cdrom_interface::~cdrom_interface(void)
|
|
|
|
{
|
2003-04-25 04:32:58 +04:00
|
|
|
#ifdef WIN32
|
|
|
|
#else
|
2005-10-27 21:01:11 +04:00
|
|
|
if (fd >= 0)
|
|
|
|
close(fd);
|
2003-04-25 04:32:58 +04:00
|
|
|
#endif
|
2005-10-27 21:01:11 +04:00
|
|
|
if (path)
|
|
|
|
free(path);
|
|
|
|
BX_DEBUG(("Exit"));
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
2002-10-25 15:44:41 +04:00
|
|
|
bx_bool
|
2002-02-01 19:46:27 +03:00
|
|
|
cdrom_interface::insert_cdrom(char *dev)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
unsigned char buffer[BX_CD_FRAMESIZE];
|
2005-10-27 21:01:11 +04:00
|
|
|
#ifndef WIN32
|
2001-04-10 05:04:59 +04:00
|
|
|
ssize_t ret;
|
2005-10-27 21:01:11 +04:00
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
// Load CD-ROM. Returns 0 if CD is not ready.
|
2002-02-01 19:46:27 +03:00
|
|
|
if (dev != NULL) path = strdup(dev);
|
2001-06-23 07:23:41 +04:00
|
|
|
BX_INFO (("load cdrom with path=%s", path));
|
2001-04-10 05:04:59 +04:00
|
|
|
#ifdef WIN32
|
2004-08-22 20:23:35 +04:00
|
|
|
char drive[256];
|
|
|
|
if ( (path[1] == ':') && (strlen(path) == 2) )
|
|
|
|
{
|
|
|
|
if(osinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) {
|
2004-08-23 13:39:45 +04:00
|
|
|
// Use direct device access under windows NT/2k/XP
|
2004-08-22 20:23:35 +04:00
|
|
|
|
|
|
|
// With all the backslashes it's hard to see, but to open D: drive
|
|
|
|
// the name would be: \\.\d:
|
|
|
|
sprintf(drive, "\\\\.\\%s", path);
|
|
|
|
BX_INFO (("Using direct access for cdrom."));
|
|
|
|
// This trick only works for Win2k and WinNT, so warn the user of that.
|
|
|
|
} else {
|
|
|
|
BX_INFO(("Using ASPI for cdrom. Drive letters are unused yet."));
|
|
|
|
bUseASPI = TRUE;
|
2001-06-19 01:11:46 +04:00
|
|
|
}
|
2005-11-01 22:10:24 +03:00
|
|
|
using_file = 0;
|
2004-08-22 20:23:35 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
strcpy(drive,path);
|
|
|
|
using_file = 1;
|
|
|
|
BX_INFO (("Opening image file as a cd"));
|
|
|
|
}
|
|
|
|
if(bUseASPI) {
|
|
|
|
DWORD d;
|
|
|
|
UINT cdr, cnt, max;
|
|
|
|
UINT i, j, k;
|
|
|
|
SRB_HAInquiry sh;
|
|
|
|
SRB_GDEVBlock sd;
|
|
|
|
if (!hASPI) {
|
|
|
|
hASPI = LoadLibrary("WNASPI32.DLL");
|
|
|
|
if (hASPI) {
|
|
|
|
SendASPI32Command = (DWORD(*)(LPSRB))GetProcAddress( hASPI, "SendASPI32Command" );
|
|
|
|
GetASPI32DLLVersion = (DWORD(*)(void))GetProcAddress( hASPI, "GetASPI32DLLVersion" );
|
|
|
|
GetASPI32SupportInfo = (DWORD(*)(void))GetProcAddress( hASPI, "GetASPI32SupportInfo" );
|
|
|
|
d = GetASPI32DLLVersion();
|
|
|
|
BX_INFO(("WNASPI32.DLL version %d.%02d initialized", d & 0xff, (d >> 8) & 0xff));
|
|
|
|
} else {
|
|
|
|
BX_PANIC(("Could not load ASPI drivers, so cdrom access will fail"));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2004-08-22 20:23:35 +04:00
|
|
|
}
|
2001-06-19 01:11:46 +04:00
|
|
|
}
|
2004-08-22 20:23:35 +04:00
|
|
|
cdr = 0;
|
|
|
|
bHaveDev = FALSE;
|
|
|
|
d = GetASPI32SupportInfo();
|
|
|
|
cnt = LOBYTE(LOWORD(d));
|
|
|
|
for(i = 0; i < cnt; i++) {
|
|
|
|
memset(&sh, 0, sizeof(sh));
|
|
|
|
sh.SRB_Cmd = SC_HA_INQUIRY;
|
|
|
|
sh.SRB_HaId = i;
|
|
|
|
SendASPI32Command((LPSRB)&sh);
|
|
|
|
if(sh.SRB_Status != SS_COMP)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
max = (int)sh.HA_Unique[3];
|
|
|
|
for(j = 0; j < max; j++) {
|
|
|
|
for(k = 0; k < 8; k++) {
|
|
|
|
memset(&sd, 0, sizeof(sd));
|
|
|
|
sd.SRB_Cmd = SC_GET_DEV_TYPE;
|
|
|
|
sd.SRB_HaId = i;
|
|
|
|
sd.SRB_Target = j;
|
|
|
|
sd.SRB_Lun = k;
|
|
|
|
SendASPI32Command((LPSRB)&sd);
|
|
|
|
if(sd.SRB_Status == SS_COMP) {
|
|
|
|
if(sd.SRB_DeviceType == DTYPE_CDROM) {
|
|
|
|
cdr++;
|
|
|
|
if(cdr > cdromCount) {
|
|
|
|
hid = i;
|
|
|
|
tid = j;
|
|
|
|
lun = k;
|
|
|
|
cdromCount++;
|
|
|
|
bHaveDev = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(bHaveDev) break;
|
|
|
|
}
|
|
|
|
if(bHaveDev) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fd=1;
|
|
|
|
} else {
|
|
|
|
hFile=CreateFile((char *)&drive, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
|
|
|
|
if (hFile !=(void *)0xFFFFFFFF)
|
2004-08-23 13:39:45 +04:00
|
|
|
fd=1;
|
2004-08-30 14:47:09 +04:00
|
|
|
if (!using_file) {
|
|
|
|
DWORD lpBytesReturned;
|
|
|
|
DeviceIoControl(hFile, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &lpBytesReturned, NULL);
|
|
|
|
}
|
2004-08-22 20:23:35 +04:00
|
|
|
}
|
2002-11-01 19:36:27 +03:00
|
|
|
#elif defined(__APPLE__)
|
|
|
|
if(strcmp(path, "drive") == 0)
|
|
|
|
{
|
|
|
|
mach_port_t masterPort = NULL;
|
|
|
|
io_iterator_t mediaIterator;
|
|
|
|
kern_return_t kernResult;
|
|
|
|
|
|
|
|
BX_INFO(( "Insert CDROM" ));
|
|
|
|
|
|
|
|
kernResult = FindEjectableCDMedia( &mediaIterator, &masterPort );
|
|
|
|
if ( kernResult != KERN_SUCCESS ) {
|
|
|
|
BX_INFO (("Unable to find CDROM"));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2002-11-01 19:36:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
kernResult = GetDeviceFilePath( mediaIterator, CDDevicePath, sizeof( CDDevicePath ) );
|
|
|
|
if ( kernResult != KERN_SUCCESS ) {
|
|
|
|
BX_INFO (("Unable to get CDROM device file path" ));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2002-11-01 19:36:27 +03:00
|
|
|
}
|
2005-10-27 21:01:11 +04:00
|
|
|
|
2002-11-01 19:36:27 +03:00
|
|
|
// Here a cdrom was found so see if we can read from it.
|
|
|
|
// At this point a failure will result in panic.
|
|
|
|
if ( strlen( CDDevicePath ) ) {
|
|
|
|
fd = open(CDDevicePath, O_RDONLY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fd = open(path, O_RDONLY);
|
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
#else
|
2001-10-06 21:32:58 +04:00
|
|
|
// all platforms except win32
|
2001-06-25 06:18:16 +04:00
|
|
|
fd = open(path, O_RDONLY);
|
2001-06-19 01:11:46 +04:00
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
if (fd < 0) {
|
2001-10-07 07:34:54 +04:00
|
|
|
BX_ERROR(( "open cd failed for %s: %s", path, strerror(errno)));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2003-01-30 23:44:32 +03:00
|
|
|
}
|
2005-10-27 21:01:11 +04:00
|
|
|
#ifndef WIN32
|
2001-10-07 07:34:54 +04:00
|
|
|
// do fstat to determine if it's a file or a device, then set using_file.
|
2002-04-10 09:38:34 +04:00
|
|
|
struct stat stat_buf;
|
2001-10-07 07:34:54 +04:00
|
|
|
ret = fstat (fd, &stat_buf);
|
|
|
|
if (ret) {
|
|
|
|
BX_PANIC (("fstat cdrom file returned error: %s", strerror (errno)));
|
|
|
|
}
|
|
|
|
if (S_ISREG (stat_buf.st_mode)) {
|
|
|
|
using_file = 1;
|
|
|
|
BX_INFO (("Opening image file %s as a cd.", path));
|
|
|
|
} else {
|
|
|
|
using_file = 0;
|
|
|
|
BX_INFO (("Using direct access for cdrom."));
|
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
2005-10-27 21:01:11 +04:00
|
|
|
|
|
|
|
// I just see if I can read a sector to verify that a
|
|
|
|
// CD is in the drive and readable.
|
2005-11-02 23:26:24 +03:00
|
|
|
return read_block(buffer, 0, 2048);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
bx_bool
|
2003-08-19 04:37:03 +04:00
|
|
|
cdrom_interface::start_cdrom()
|
|
|
|
{
|
|
|
|
// Spin up the cdrom drive.
|
|
|
|
|
|
|
|
if (fd >= 0) {
|
2004-09-11 19:39:53 +04:00
|
|
|
#if defined(__NetBSD__) || defined(__NetBSD_kernel__)
|
2003-08-19 04:37:03 +04:00
|
|
|
if (ioctl (fd, CDIOCSTART) < 0)
|
|
|
|
BX_DEBUG(( "start_cdrom: start returns error: %s", strerror (errno) ));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2003-08-19 04:37:03 +04:00
|
|
|
#else
|
|
|
|
BX_INFO(("start_cdrom: your OS is not supported yet."));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0; // OS not supported yet, return 0 always.
|
2003-08-19 04:37:03 +04:00
|
|
|
#endif
|
2005-10-27 21:01:11 +04:00
|
|
|
}
|
|
|
|
return 0;
|
2003-08-19 04:37:03 +04:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
void
|
|
|
|
cdrom_interface::eject_cdrom()
|
|
|
|
{
|
|
|
|
// Logically eject the CD. I suppose we could stick in
|
|
|
|
// some ioctl() calls to really eject the CD as well.
|
|
|
|
|
|
|
|
if (fd >= 0) {
|
2004-09-11 19:39:53 +04:00
|
|
|
#if (defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__))
|
2001-04-10 05:04:59 +04:00
|
|
|
(void) ioctl (fd, CDIOCALLOW);
|
|
|
|
if (ioctl (fd, CDIOCEJECT) < 0)
|
2005-10-27 21:01:11 +04:00
|
|
|
BX_DEBUG(( "eject_cdrom: eject returns error." ));
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WIN32
|
2005-10-27 21:01:11 +04:00
|
|
|
if (using_file == 0)
|
|
|
|
{
|
|
|
|
if(bUseASPI) {
|
|
|
|
} else {
|
|
|
|
DWORD lpBytesReturned;
|
|
|
|
DeviceIoControl(hFile, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &lpBytesReturned, NULL);
|
|
|
|
}
|
|
|
|
}
|
2003-04-25 04:32:58 +04:00
|
|
|
#else // WIN32
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-03-02 14:31:18 +03:00
|
|
|
#if __linux__
|
2005-10-27 21:01:11 +04:00
|
|
|
if (!using_file)
|
|
|
|
ioctl (fd, CDROMEJECT, NULL);
|
2002-03-02 14:31:18 +03:00
|
|
|
#endif
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
close(fd);
|
2003-04-25 04:32:58 +04:00
|
|
|
#endif // WIN32
|
2001-04-10 05:04:59 +04:00
|
|
|
fd = -1;
|
2005-10-27 21:01:11 +04:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-10-25 15:44:41 +04:00
|
|
|
bx_bool
|
2005-06-26 14:54:49 +04:00
|
|
|
cdrom_interface::read_toc(Bit8u* buf, int* length, bx_bool msf, int start_track, int format)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2005-01-19 21:21:40 +03:00
|
|
|
unsigned i;
|
2005-10-27 21:01:11 +04:00
|
|
|
// Read CD TOC. Returns 0 if start track is out of bounds.
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
if (fd < 0) {
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: file not open."));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2004-08-23 13:39:45 +04:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2005-11-01 22:10:24 +03:00
|
|
|
// This is a hack and works okay if there's one rom track only
|
2002-11-01 19:36:27 +03:00
|
|
|
#if defined(WIN32)
|
2005-11-01 22:10:24 +03:00
|
|
|
if (!isWindowsXP || using_file) {
|
2002-07-31 09:21:46 +04:00
|
|
|
#else
|
2005-11-01 22:10:24 +03:00
|
|
|
if (using_file || (format != 0)) {
|
2002-07-31 09:21:46 +04:00
|
|
|
#endif
|
2004-08-23 13:39:45 +04:00
|
|
|
Bit32u blocks;
|
|
|
|
int len = 4;
|
2002-07-30 10:25:57 +04:00
|
|
|
|
2004-08-23 13:39:45 +04:00
|
|
|
switch (format) {
|
|
|
|
case 0:
|
|
|
|
// From atapi specs : start track can be 0-63, AA
|
|
|
|
if ((start_track > 1) && (start_track != 0xaa))
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2002-07-30 10:25:57 +04:00
|
|
|
|
2004-08-23 13:39:45 +04:00
|
|
|
buf[2] = 1;
|
|
|
|
buf[3] = 1;
|
|
|
|
|
|
|
|
if (start_track <= 1) {
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
buf[len++] = 0x14; // ADR, control
|
|
|
|
buf[len++] = 1; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = 0; // minute
|
|
|
|
buf[len++] = 2; // second
|
|
|
|
buf[len++] = 0; // frame
|
|
|
|
} else {
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
2005-11-01 22:10:24 +03:00
|
|
|
buf[len++] = 0; // logical sector 0
|
2004-08-23 13:39:45 +04:00
|
|
|
}
|
|
|
|
}
|
2002-07-30 10:25:57 +04:00
|
|
|
|
2004-08-23 13:39:45 +04:00
|
|
|
// Lead out track
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
buf[len++] = 0x16; // ADR, control
|
|
|
|
buf[len++] = 0xaa; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
blocks = capacity();
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
2005-06-26 14:54:49 +04:00
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) / 60); // minute
|
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) % 60); // second
|
|
|
|
buf[len++] = (Bit8u)((blocks + 150) % 75); // frame;
|
2004-08-23 13:39:45 +04:00
|
|
|
} else {
|
|
|
|
buf[len++] = (blocks >> 24) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 16) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 8) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
buf[0] = ((len-2) >> 8) & 0xff;
|
|
|
|
buf[1] = (len-2) & 0xff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
// multi session stuff - emulate a single session only
|
|
|
|
buf[0] = 0;
|
|
|
|
buf[1] = 0x0a;
|
|
|
|
buf[2] = 1;
|
|
|
|
buf[3] = 1;
|
2005-01-19 21:21:40 +03:00
|
|
|
for (i = 0; i < 8; i++)
|
2004-08-23 13:39:45 +04:00
|
|
|
buf[4+i] = 0;
|
|
|
|
len = 12;
|
|
|
|
break;
|
2002-07-30 10:25:57 +04:00
|
|
|
|
2005-11-01 22:10:24 +03:00
|
|
|
case 2:
|
|
|
|
// raw toc - emulate a single session only (ported from qemu)
|
|
|
|
buf[2] = 1;
|
|
|
|
buf[3] = 1;
|
|
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
buf[len++] = 1;
|
|
|
|
buf[len++] = 0x14;
|
|
|
|
buf[len++] = 0;
|
|
|
|
if (i < 3) {
|
|
|
|
buf[len++] = 0xa0 + i;
|
|
|
|
} else {
|
|
|
|
buf[len++] = 1;
|
|
|
|
}
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
if (i < 2) {
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 1;
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
} else if (i == 2) {
|
|
|
|
blocks = capacity();
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) / 60); // minute
|
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) % 60); // second
|
|
|
|
buf[len++] = (Bit8u)((blocks + 150) % 75); // frame;
|
|
|
|
} else {
|
|
|
|
buf[len++] = (blocks >> 24) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 16) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 8) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
buf[len++] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf[0] = ((len-2) >> 8) & 0xff;
|
|
|
|
buf[1] = (len-2) & 0xff;
|
|
|
|
break;
|
|
|
|
|
2004-08-23 13:39:45 +04:00
|
|
|
default:
|
|
|
|
BX_PANIC(("cdrom: read_toc: unknown format"));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2004-08-23 13:39:45 +04:00
|
|
|
}
|
2002-07-30 10:25:57 +04:00
|
|
|
|
|
|
|
*length = len;
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2001-11-11 17:41:53 +03:00
|
|
|
}
|
|
|
|
// all these implementations below are the platform-dependent code required
|
|
|
|
// to read the TOC from a physical cdrom.
|
2001-04-10 05:04:59 +04:00
|
|
|
#ifdef WIN32
|
2004-08-23 13:39:45 +04:00
|
|
|
if (isWindowsXP)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2004-08-23 13:39:45 +04:00
|
|
|
// This only works with WinXP
|
|
|
|
CDROM_READ_TOC_EX input;
|
|
|
|
memset(&input, 0, sizeof(input));
|
|
|
|
input.Format = format;
|
|
|
|
input.Msf = msf;
|
|
|
|
input.SessionTrack = start_track;
|
|
|
|
|
|
|
|
// We have to allocate a chunk of memory to make sure it is aligned on a sector base.
|
|
|
|
UCHAR *data = (UCHAR *) VirtualAlloc(NULL, 2048*2, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
unsigned long iBytesReturned;
|
|
|
|
DeviceIoControl(hFile, IOCTL_CDROM_READ_TOC_EX, &input, sizeof(input), data, 804, &iBytesReturned, NULL);
|
|
|
|
// now copy it to the users buffer and free our buffer
|
2005-11-02 23:26:24 +03:00
|
|
|
*length = data[1] + (data[0] << 8) + 2;
|
|
|
|
memcpy(buf, data, *length);
|
2004-08-23 13:39:45 +04:00
|
|
|
VirtualFree(data, 0, MEM_RELEASE);
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2005-06-07 23:26:21 +04:00
|
|
|
} else {
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2001-04-10 06:15:31 +04:00
|
|
|
#elif __linux__ || defined(__sun)
|
2001-11-11 17:41:53 +03:00
|
|
|
{
|
2001-04-10 05:04:59 +04:00
|
|
|
struct cdrom_tochdr tochdr;
|
|
|
|
if (ioctl(fd, CDROMREADTOCHDR, &tochdr))
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCHDR failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-07-29 20:42:01 +04:00
|
|
|
if ((start_track > tochdr.cdth_trk1) && (start_track != 0xaa))
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
buf[2] = tochdr.cdth_trk0;
|
|
|
|
buf[3] = tochdr.cdth_trk1;
|
|
|
|
|
|
|
|
if (start_track < tochdr.cdth_trk0)
|
|
|
|
start_track = tochdr.cdth_trk0;
|
|
|
|
|
|
|
|
int len = 4;
|
|
|
|
for (int i = start_track; i <= tochdr.cdth_trk1; i++) {
|
|
|
|
struct cdrom_tocentry tocentry;
|
|
|
|
tocentry.cdte_format = (msf) ? CDROM_MSF : CDROM_LBA;
|
|
|
|
tocentry.cdte_track = i;
|
|
|
|
if (ioctl(fd, CDROMREADTOCENTRY, &tocentry))
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCENTRY failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = 0; // Reserved
|
2001-06-19 01:14:00 +04:00
|
|
|
buf[len++] = (tocentry.cdte_adr << 4) | tocentry.cdte_ctrl ; // ADR, control
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = i; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.minute;
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.second;
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.frame;
|
|
|
|
} else {
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 24) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 16) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 8) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lead out track
|
|
|
|
struct cdrom_tocentry tocentry;
|
|
|
|
tocentry.cdte_format = (msf) ? CDROM_MSF : CDROM_LBA;
|
2001-06-19 01:11:46 +04:00
|
|
|
#ifdef CDROM_LEADOUT
|
2001-04-10 06:15:31 +04:00
|
|
|
tocentry.cdte_track = CDROM_LEADOUT;
|
|
|
|
#else
|
2001-04-10 05:04:59 +04:00
|
|
|
tocentry.cdte_track = 0xaa;
|
2001-04-10 06:15:31 +04:00
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
if (ioctl(fd, CDROMREADTOCENTRY, &tocentry))
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCENTRY lead-out failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = 0; // Reserved
|
2001-06-19 01:14:00 +04:00
|
|
|
buf[len++] = (tocentry.cdte_adr << 4) | tocentry.cdte_ctrl ; // ADR, control
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = 0xaa; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.minute;
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.second;
|
|
|
|
buf[len++] = tocentry.cdte_addr.msf.frame;
|
|
|
|
} else {
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 24) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 16) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 8) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.cdte_addr.lba) >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf[0] = ((len-2) >> 8) & 0xff;
|
|
|
|
buf[1] = (len-2) & 0xff;
|
|
|
|
|
|
|
|
*length = len;
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2004-09-11 19:39:53 +04:00
|
|
|
#elif (defined(__NetBSD__) || defined(__NetBSD_kernel__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__))
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
struct ioc_toc_header h;
|
|
|
|
struct ioc_read_toc_entry t;
|
|
|
|
|
|
|
|
if (ioctl (fd, CDIOREADTOCHEADER, &h) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCHDR failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-07-29 20:42:01 +04:00
|
|
|
if ((start_track > h.ending_track) && (start_track != 0xaa))
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
buf[2] = h.starting_track;
|
|
|
|
buf[3] = h.ending_track;
|
|
|
|
|
|
|
|
if (start_track < h.starting_track)
|
|
|
|
start_track = h.starting_track;
|
2001-06-19 01:11:46 +04:00
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
int len = 4;
|
|
|
|
for (int i = start_track; i <= h.ending_track; i++) {
|
|
|
|
struct cd_toc_entry tocentry;
|
|
|
|
t.address_format = (msf) ? CD_MSF_FORMAT : CD_LBA_FORMAT;
|
|
|
|
t.starting_track = i;
|
|
|
|
t.data_len = sizeof(tocentry);
|
|
|
|
t.data = &tocentry;
|
|
|
|
|
2003-01-11 01:59:44 +03:00
|
|
|
if (ioctl (fd, CDIOREADTOCENTRYS, &t) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCENTRY failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
buf[len++] = 0; // Reserved
|
2001-06-19 01:14:00 +04:00
|
|
|
buf[len++] = (tocentry.addr_type << 4) | tocentry.control ; // ADR, control
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = i; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = tocentry.addr.msf.minute;
|
|
|
|
buf[len++] = tocentry.addr.msf.second;
|
|
|
|
buf[len++] = tocentry.addr.msf.frame;
|
|
|
|
} else {
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 24) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 16) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 8) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lead out track
|
|
|
|
struct cd_toc_entry tocentry;
|
|
|
|
t.address_format = (msf) ? CD_MSF_FORMAT : CD_LBA_FORMAT;
|
|
|
|
t.starting_track = 0xaa;
|
|
|
|
t.data_len = sizeof(tocentry);
|
|
|
|
t.data = &tocentry;
|
|
|
|
|
2003-01-11 01:59:44 +03:00
|
|
|
if (ioctl (fd, CDIOREADTOCENTRYS, &t) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: read_toc: READTOCENTRY lead-out failed."));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
buf[len++] = 0; // Reserved
|
2001-06-19 01:14:00 +04:00
|
|
|
buf[len++] = (tocentry.addr_type << 4) | tocentry.control ; // ADR, control
|
2001-04-10 05:04:59 +04:00
|
|
|
buf[len++] = 0xaa; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = tocentry.addr.msf.minute;
|
|
|
|
buf[len++] = tocentry.addr.msf.second;
|
|
|
|
buf[len++] = tocentry.addr.msf.frame;
|
|
|
|
} else {
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 24) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 16) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 8) & 0xff;
|
|
|
|
buf[len++] = (((unsigned)tocentry.addr.lba) >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf[0] = ((len-2) >> 8) & 0xff;
|
|
|
|
buf[1] = (len-2) & 0xff;
|
|
|
|
|
|
|
|
*length = len;
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2002-11-01 19:36:27 +03:00
|
|
|
#elif defined(__APPLE__)
|
2005-10-27 21:01:11 +04:00
|
|
|
// Read CD TOC. Returns 0 if start track is out of bounds.
|
2002-11-01 19:36:27 +03:00
|
|
|
|
|
|
|
#if 1
|
|
|
|
{
|
|
|
|
struct _CDTOC * toc = ReadTOC( CDDevicePath );
|
|
|
|
|
|
|
|
if ((start_track > toc->last_session) && (start_track != 0xaa))
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2002-11-01 19:36:27 +03:00
|
|
|
|
|
|
|
buf[2] = toc->first_session;
|
|
|
|
buf[3] = toc->last_session;
|
|
|
|
|
|
|
|
if (start_track < toc->first_session)
|
|
|
|
start_track = toc->first_session;
|
|
|
|
|
|
|
|
int len = 4;
|
|
|
|
for (int i = start_track; i <= toc->last_session; i++) {
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
buf[len++] = toc->trackdesc[i].ctrl_adr ; // ADR, control
|
|
|
|
buf[len++] = i; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
|
|
|
buf[len++] = toc->trackdesc[i].address.minute;
|
|
|
|
buf[len++] = toc->trackdesc[i].address.second;
|
|
|
|
buf[len++] = toc->trackdesc[i].address.frame;
|
|
|
|
} else {
|
|
|
|
unsigned lba = (unsigned)(MSF_TO_LBA(toc->trackdesc[i].address));
|
|
|
|
buf[len++] = (lba >> 24) & 0xff;
|
|
|
|
buf[len++] = (lba >> 16) & 0xff;
|
|
|
|
buf[len++] = (lba >> 8) & 0xff;
|
|
|
|
buf[len++] = (lba >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lead out track
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
buf[len++] = 0x16; // ADR, control
|
|
|
|
buf[len++] = 0xaa; // Track number
|
|
|
|
buf[len++] = 0; // Reserved
|
|
|
|
|
2005-06-26 14:54:49 +04:00
|
|
|
Bit32u blocks = capacity();
|
2002-11-01 19:36:27 +03:00
|
|
|
|
|
|
|
// Start address
|
|
|
|
if (msf) {
|
|
|
|
buf[len++] = 0; // reserved
|
2005-06-26 14:54:49 +04:00
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) / 60); // minute
|
|
|
|
buf[len++] = (Bit8u)(((blocks + 150) / 75) % 60); // second
|
|
|
|
buf[len++] = (Bit8u)((blocks + 150) % 75); // frame;
|
2002-11-01 19:36:27 +03:00
|
|
|
} else {
|
|
|
|
buf[len++] = (blocks >> 24) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 16) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 8) & 0xff;
|
|
|
|
buf[len++] = (blocks >> 0) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf[0] = ((len-2) >> 8) & 0xff;
|
|
|
|
buf[1] = (len-2) & 0xff;
|
|
|
|
|
|
|
|
*length = len;
|
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return 1;
|
2002-11-01 19:36:27 +03:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
BX_INFO(( "Read TOC - Not Implemented" ));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0;
|
2002-11-01 19:36:27 +03:00
|
|
|
#endif
|
2001-04-10 05:04:59 +04:00
|
|
|
#else
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_INFO(("read_toc: your OS is not supported yet."));
|
2005-10-27 21:01:11 +04:00
|
|
|
return 0; // OS not supported yet, return 0 always.
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-26 14:54:49 +04:00
|
|
|
Bit32u
|
2001-04-10 05:04:59 +04:00
|
|
|
cdrom_interface::capacity()
|
|
|
|
{
|
|
|
|
// Return CD-ROM capacity. I believe you want to return
|
2002-07-31 09:21:46 +04:00
|
|
|
// the number of blocks of capacity the actual media has.
|
2001-10-07 07:34:54 +04:00
|
|
|
|
|
|
|
#if !defined WIN32
|
|
|
|
// win32 has its own way of doing this
|
|
|
|
if (using_file) {
|
|
|
|
// return length of the image file
|
|
|
|
struct stat stat_buf;
|
|
|
|
int ret = fstat (fd, &stat_buf);
|
|
|
|
if (ret) {
|
|
|
|
BX_PANIC (("fstat on cdrom image returned err: %s", strerror(errno)));
|
|
|
|
}
|
|
|
|
if ((stat_buf.st_size % 2048) != 0) {
|
|
|
|
BX_ERROR (("expected cdrom image to be a multiple of 2048 bytes"));
|
|
|
|
}
|
2006-03-26 04:38:58 +04:00
|
|
|
return (stat_buf.st_size / 2048);
|
2001-10-07 07:34:54 +04:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2001-12-10 21:37:39 +03:00
|
|
|
#ifdef __BEOS__
|
2005-10-27 21:01:11 +04:00
|
|
|
return GetNumDeviceBlocks(fd, BX_CD_FRAMESIZE);
|
2001-12-10 21:37:39 +03:00
|
|
|
#elif defined(__sun)
|
2001-04-10 06:15:31 +04:00
|
|
|
{
|
|
|
|
struct stat buf = {0};
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-04-10 06:15:31 +04:00
|
|
|
if (fd < 0) {
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: capacity: file not open."));
|
2001-04-10 06:15:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if( fstat(fd, &buf) != 0 )
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: capacity: stat() failed."));
|
2001-04-10 06:15:31 +04:00
|
|
|
|
|
|
|
return(buf.st_size);
|
2001-06-19 01:11:46 +04:00
|
|
|
}
|
2004-09-11 19:39:53 +04:00
|
|
|
#elif (defined(__NetBSD__) || defined(__NetBSD_kernel__) || defined(__OpenBSD__))
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
// We just read the disklabel, imagine that...
|
|
|
|
struct disklabel lp;
|
|
|
|
|
|
|
|
if (fd < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: capacity: file not open."));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
if (ioctl(fd, DIOCGDINFO, &lp) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: ioctl(DIOCGDINFO) failed"));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_DEBUG(( "capacity: %u", lp.d_secperunit ));
|
2001-04-10 05:04:59 +04:00
|
|
|
return(lp.d_secperunit);
|
2001-06-26 12:12:21 +04:00
|
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
|
|
{
|
|
|
|
// Read the TOC to get the data size, since BLKGETSIZE doesn't work on
|
|
|
|
// non-ATAPI drives. This is based on Keith Jones code below.
|
|
|
|
// <splite@purdue.edu> 21 June 2001
|
|
|
|
|
2002-05-31 13:56:58 +04:00
|
|
|
int i, dtrk_lba, num_sectors;
|
|
|
|
int dtrk = 0;
|
2001-06-26 12:12:21 +04:00
|
|
|
struct cdrom_tochdr td;
|
|
|
|
struct cdrom_tocentry te;
|
|
|
|
|
|
|
|
if (fd < 0)
|
|
|
|
BX_PANIC(("cdrom: capacity: file not open."));
|
|
|
|
|
|
|
|
if (ioctl(fd, CDROMREADTOCHDR, &td) < 0)
|
|
|
|
BX_PANIC(("cdrom: ioctl(CDROMREADTOCHDR) failed"));
|
|
|
|
|
|
|
|
num_sectors = -1;
|
|
|
|
dtrk_lba = -1;
|
|
|
|
|
|
|
|
for (i = td.cdth_trk0; i <= td.cdth_trk1; i++) {
|
|
|
|
te.cdte_track = i;
|
|
|
|
te.cdte_format = CDROM_LBA;
|
|
|
|
if (ioctl(fd, CDROMREADTOCENTRY, &te) < 0)
|
|
|
|
BX_PANIC(("cdrom: ioctl(CDROMREADTOCENTRY) failed"));
|
|
|
|
|
|
|
|
if (dtrk_lba != -1) {
|
|
|
|
num_sectors = te.cdte_addr.lba - dtrk_lba;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (te.cdte_ctrl & CDROM_DATA_TRACK) {
|
|
|
|
dtrk = i;
|
|
|
|
dtrk_lba = te.cdte_addr.lba;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_sectors < 0) {
|
|
|
|
if (dtrk_lba != -1) {
|
|
|
|
te.cdte_track = CDROM_LEADOUT;
|
|
|
|
te.cdte_format = CDROM_LBA;
|
|
|
|
if (ioctl(fd, CDROMREADTOCENTRY, &te) < 0)
|
|
|
|
BX_PANIC(("cdrom: ioctl(CDROMREADTOCENTRY) failed"));
|
|
|
|
num_sectors = te.cdte_addr.lba - dtrk_lba;
|
|
|
|
} else
|
|
|
|
BX_PANIC(("cdrom: no data track found"));
|
|
|
|
}
|
|
|
|
|
|
|
|
BX_INFO(("cdrom: Data track %d, length %d", dtrk, num_sectors));
|
|
|
|
|
|
|
|
return(num_sectors);
|
|
|
|
|
2001-05-16 21:39:07 +04:00
|
|
|
}
|
2004-09-11 19:39:53 +04:00
|
|
|
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
2001-05-16 21:39:07 +04:00
|
|
|
{
|
2003-01-11 01:59:44 +03:00
|
|
|
// Read the TOC to get the size of the data track.
|
|
|
|
// Keith Jones <freebsd.dev@blueyonder.co.uk>, 16 January 2000
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
#define MAX_TRACKS 100
|
|
|
|
|
|
|
|
int i, num_tracks, num_sectors;
|
|
|
|
struct ioc_toc_header td;
|
|
|
|
struct ioc_read_toc_entry rte;
|
|
|
|
struct cd_toc_entry toc_buffer[MAX_TRACKS + 1];
|
|
|
|
|
|
|
|
if (fd < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: capacity: file not open."));
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
if (ioctl(fd, CDIOREADTOCHEADER, &td) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: ioctl(CDIOREADTOCHEADER) failed"));
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
num_tracks = (td.ending_track - td.starting_track) + 1;
|
|
|
|
if (num_tracks > MAX_TRACKS)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: TOC is too large"));
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
rte.address_format = CD_LBA_FORMAT;
|
|
|
|
rte.starting_track = td.starting_track;
|
|
|
|
rte.data_len = (num_tracks + 1) * sizeof(struct cd_toc_entry);
|
|
|
|
rte.data = toc_buffer;
|
|
|
|
if (ioctl(fd, CDIOREADTOCENTRYS, &rte) < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: ioctl(CDIOREADTOCENTRYS) failed"));
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
num_sectors = -1;
|
|
|
|
for (i = 0; i < num_tracks; i++) {
|
2005-10-27 21:01:11 +04:00
|
|
|
if (rte.data[i].control & 4) { /* data track */
|
2001-05-16 21:39:07 +04:00
|
|
|
num_sectors = ntohl(rte.data[i + 1].addr.lba)
|
|
|
|
- ntohl(rte.data[i].addr.lba);
|
2001-10-07 07:34:54 +04:00
|
|
|
BX_INFO(( "cdrom: Data track %d, length %d",
|
2001-05-30 22:56:02 +04:00
|
|
|
rte.data[i].track, num_sectors));
|
2001-05-16 21:39:07 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_sectors < 0)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("cdrom: no data track found"));
|
2001-05-16 21:39:07 +04:00
|
|
|
|
|
|
|
return(num_sectors);
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
#elif defined WIN32
|
|
|
|
{
|
2005-11-01 22:10:24 +03:00
|
|
|
if (bUseASPI) {
|
|
|
|
return ((GetCDCapacity(hid, tid, lun) / 2352) + 1);
|
|
|
|
} else if (using_file) {
|
2005-05-04 22:19:49 +04:00
|
|
|
ULARGE_INTEGER FileSize;
|
|
|
|
FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart);
|
2006-03-26 04:38:58 +04:00
|
|
|
return (Bit32u)(FileSize.QuadPart / 2048);
|
2005-05-04 22:19:49 +04:00
|
|
|
} else { /* direct device access */
|
2005-11-02 00:43:47 +03:00
|
|
|
if (isWindowsXP) {
|
|
|
|
LARGE_INTEGER length;
|
|
|
|
DWORD iBytesReturned;
|
|
|
|
DeviceIoControl(hFile, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &length, sizeof(length), &iBytesReturned, NULL);
|
|
|
|
return (Bit32u)(length.QuadPart / 2048);
|
|
|
|
} else {
|
|
|
|
ULARGE_INTEGER FreeBytesForCaller;
|
|
|
|
ULARGE_INTEGER TotalNumOfBytes;
|
|
|
|
ULARGE_INTEGER TotalFreeBytes;
|
|
|
|
GetDiskFreeSpaceEx( path, &FreeBytesForCaller, &TotalNumOfBytes, &TotalFreeBytes);
|
|
|
|
return (Bit32u)(TotalNumOfBytes.QuadPart / 2048);
|
2005-11-02 19:44:00 +03:00
|
|
|
}
|
2005-05-04 22:19:49 +04:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2002-11-01 19:36:27 +03:00
|
|
|
#elif defined __APPLE__
|
|
|
|
// Find the size of the first data track on the cd. This has produced
|
|
|
|
// the same results as the linux version on every cd I have tried, about
|
|
|
|
// 5. The differences here seem to be that the entries in the TOC when
|
|
|
|
// retrieved from the IOKit interface appear in a reversed order when
|
|
|
|
// compared with the linux READTOCENTRY ioctl.
|
|
|
|
{
|
|
|
|
// Return CD-ROM capacity. I believe you want to return
|
|
|
|
// the number of bytes of capacity the actual media has.
|
|
|
|
|
|
|
|
BX_INFO(( "Capacity" ));
|
|
|
|
|
|
|
|
struct _CDTOC * toc = ReadTOC( CDDevicePath );
|
|
|
|
|
|
|
|
if ( toc == NULL ) {
|
|
|
|
BX_PANIC(( "capacity: Failed to read toc" ));
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t toc_entries = ( toc->length - 2 ) / sizeof( struct _CDTOC_Desc );
|
|
|
|
|
|
|
|
BX_DEBUG(( "reading %d toc entries\n", toc_entries ));
|
|
|
|
|
|
|
|
int start_sector = -1;
|
|
|
|
int data_track = -1;
|
|
|
|
|
|
|
|
// Iterate through the list backward. Pick the first data track and
|
|
|
|
// get the address of the immediately previous (or following depending
|
|
|
|
// on how you look at it). The difference in the sector numbers
|
|
|
|
// is returned as the sized of the data track.
|
|
|
|
for ( int i=toc_entries - 1; i>=0; i-- ) {
|
|
|
|
BX_DEBUG(( "session %d ctl_adr %d tno %d point %d lba %d z %d p lba %d\n",
|
|
|
|
(int)toc->trackdesc[i].session,
|
|
|
|
(int)toc->trackdesc[i].ctrl_adr,
|
|
|
|
(int)toc->trackdesc[i].tno,
|
|
|
|
(int)toc->trackdesc[i].point,
|
|
|
|
MSF_TO_LBA( toc->trackdesc[i].address ),
|
|
|
|
(int)toc->trackdesc[i].zero,
|
|
|
|
MSF_TO_LBA(toc->trackdesc[i].p )));
|
|
|
|
|
|
|
|
if ( start_sector != -1 ) {
|
|
|
|
start_sector = MSF_TO_LBA(toc->trackdesc[i].p) - start_sector;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2004-11-05 01:41:24 +03:00
|
|
|
if ((toc->trackdesc[i].ctrl_adr >> 4) != 1) continue;
|
2002-11-01 19:36:27 +03:00
|
|
|
|
|
|
|
if ( toc->trackdesc[i].ctrl_adr & 0x04 ) {
|
|
|
|
data_track = toc->trackdesc[i].point;
|
|
|
|
start_sector = MSF_TO_LBA(toc->trackdesc[i].p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free( toc );
|
|
|
|
|
|
|
|
if ( start_sector == -1 ) {
|
|
|
|
start_sector = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
BX_INFO(("first data track %d data size is %d", data_track, start_sector));
|
|
|
|
|
|
|
|
return start_sector;
|
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
#else
|
2001-10-06 21:32:58 +04:00
|
|
|
BX_ERROR(( "capacity: your OS is not supported yet." ));
|
2001-04-10 05:04:59 +04:00
|
|
|
return(0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2005-11-02 23:26:24 +03:00
|
|
|
bx_bool BX_CPP_AttrRegparmN(3)
|
|
|
|
cdrom_interface::read_block(Bit8u* buf, int lba, int blocksize)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
// Read a single block from the CD
|
|
|
|
|
2003-11-30 23:54:42 +03:00
|
|
|
#ifdef WIN32
|
|
|
|
LARGE_INTEGER pos;
|
|
|
|
#else
|
2001-04-10 05:04:59 +04:00
|
|
|
off_t pos;
|
2003-11-30 23:54:42 +03:00
|
|
|
#endif
|
2005-10-27 21:01:11 +04:00
|
|
|
ssize_t n = 0;
|
|
|
|
Bit8u try_count = 3;
|
2005-11-02 23:26:24 +03:00
|
|
|
Bit8u* buf1;
|
|
|
|
|
|
|
|
if (blocksize == 2352) {
|
|
|
|
memset(buf, 0, 2352);
|
|
|
|
memset(buf+1, 0xff, 10);
|
|
|
|
int raw_block = lba + 150;
|
|
|
|
buf[12] = (raw_block / 75) / 60;
|
|
|
|
buf[13] = (raw_block / 75) % 60;
|
|
|
|
buf[14] = (raw_block % 75);
|
|
|
|
buf[15] = 0x01;
|
|
|
|
buf1 = buf + 16;
|
|
|
|
} else {
|
|
|
|
buf1 = buf;
|
|
|
|
}
|
2005-10-27 21:01:11 +04:00
|
|
|
do {
|
2001-04-10 05:04:59 +04:00
|
|
|
#ifdef WIN32
|
2005-10-27 21:01:11 +04:00
|
|
|
if(bUseASPI) {
|
2005-11-02 23:26:24 +03:00
|
|
|
ReadCDSector(hid, tid, lun, lba, buf1, BX_CD_FRAMESIZE);
|
2005-10-27 21:01:11 +04:00
|
|
|
n = BX_CD_FRAMESIZE;
|
|
|
|
} else {
|
|
|
|
pos.QuadPart = (LONGLONG)lba*BX_CD_FRAMESIZE;
|
|
|
|
pos.LowPart = SetFilePointer(hFile, pos.LowPart, &pos.HighPart, SEEK_SET);
|
|
|
|
if ((pos.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) {
|
|
|
|
BX_PANIC(("cdrom: read_block: SetFilePointer returned error."));
|
|
|
|
} else {
|
2005-11-02 23:26:24 +03:00
|
|
|
ReadFile(hFile, (void *) buf1, BX_CD_FRAMESIZE, (unsigned long *) &n, NULL);
|
2005-10-27 21:01:11 +04:00
|
|
|
}
|
|
|
|
}
|
2002-11-01 19:36:27 +03:00
|
|
|
#elif defined(__APPLE__)
|
|
|
|
#define CD_SEEK_DISTANCE kCDSectorSizeWhole
|
2005-10-27 21:01:11 +04:00
|
|
|
if(using_file)
|
|
|
|
{
|
|
|
|
pos = lseek(fd, lba*BX_CD_FRAMESIZE, SEEK_SET);
|
|
|
|
if (pos < 0) {
|
|
|
|
BX_PANIC(("cdrom: read_block: lseek returned error."));
|
|
|
|
} else {
|
2005-11-02 23:26:24 +03:00
|
|
|
n = read(fd, buf1, BX_CD_FRAMESIZE);
|
2005-10-27 21:01:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This seek will leave us 16 bytes from the start of the data
|
|
|
|
// hence the magic number.
|
|
|
|
pos = lseek(fd, lba*CD_SEEK_DISTANCE + 16, SEEK_SET);
|
|
|
|
if (pos < 0) {
|
|
|
|
BX_PANIC(("cdrom: read_block: lseek returned error."));
|
|
|
|
} else {
|
2005-11-02 23:26:24 +03:00
|
|
|
n = read(fd, buf1, CD_FRAMESIZE);
|
2005-10-27 21:01:11 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2002-11-01 19:36:27 +03:00
|
|
|
pos = lseek(fd, lba*BX_CD_FRAMESIZE, SEEK_SET);
|
|
|
|
if (pos < 0) {
|
|
|
|
BX_PANIC(("cdrom: read_block: lseek returned error."));
|
2005-10-27 21:01:11 +04:00
|
|
|
} else {
|
2005-11-02 23:26:24 +03:00
|
|
|
n = read(fd, (char*) buf1, BX_CD_FRAMESIZE);
|
2002-11-01 19:36:27 +03:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
2005-10-27 21:01:11 +04:00
|
|
|
} while ((n != BX_CD_FRAMESIZE) && (--try_count > 0));
|
2001-06-19 01:11:46 +04:00
|
|
|
|
2005-10-27 21:01:11 +04:00
|
|
|
return (n == BX_CD_FRAMESIZE);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2001-06-19 01:11:46 +04:00
|
|
|
|
2005-12-27 16:21:25 +03:00
|
|
|
void cdrom_interface::seek(int lba)
|
|
|
|
{
|
|
|
|
unsigned char buffer[BX_CD_FRAMESIZE];
|
|
|
|
|
|
|
|
read_block(buffer, lba, BX_CD_FRAMESIZE);
|
|
|
|
}
|
|
|
|
|
2002-11-19 08:47:45 +03:00
|
|
|
#endif /* if BX_SUPPORT_CDROM */
|
2006-03-07 21:16:41 +03:00
|
|
|
|
|
|
|
#endif
|