Class: CHD::CD

Inherits:
Object
  • Object
show all
Defined in:
lib/chd/cd.rb

Overview

Access a CD-ROM / GD-ROM in Mame CHD format

Constant Summary collapse

MAX_TRACKS =

Maximum number of tracks in a CD-ROM

99
MAX_SECTOR_DATASIZE =

Maximum sector size

2352
MAX_SUBCODE_DATASIZE =

Maximum subcode size

96
FRAME_SIZE =

Maximum frame size

MAX_SUBCODE_DATASIZE + MAX_SECTOR_DATASIZE
TRACK_TYPE_DATASIZE =

Various sector data size according to track type

{
     :MODE1          => 2048,
     :MODE1_RAW      => 2352,
     :MODE2          => 2336,
     :MODE2_FORM1    => 2048,
     :MODE2_FORM2    => 2324,
     :MODE2_FORM_MIX => 2336,
     :MODE2_RAW      => 2352,
     :AUDIO          => 2352,
}.freeze
TRACK_SUBTYPE_DATASIZE =

Various subcode data size according to track type

{
    :NONE            => 0,
    :NORMAL          => 96,
    :RAW             => 96,
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(chd) ⇒ CD

Returns a new instance of CD.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/chd/cd.rb', line 93

def initialize(chd)
    @chd         = chd
    @toc, @flags = CD.read_toc(chd)

    # Build mapping 
	chdofs = physofs = logofs = 0
    @mapping = @toc.map {|trackinfo|
        { :physframeofs => physofs,
   :chdframeofs  => chdofs,
   :logframeofs  => trackinfo[:pregap] + logofs,
   :logframes    => trackinfo[:frames] - trackinfo[:pregap],
        }.tap {
            logofs  += trackinfo[:pregap] if trackinfo[:pgdatasize].zero?
     logofs  += trackinfo[:frames] + trackinfo[:postgap]
     physofs += trackinfo[:frames]
            chdofs  += trackinfo[:frames] + trackinfo[:extraframes]
        }
    }
    @mapping <<  { :physframeofs => physofs,
                   :logframeofs  => logofs,
                   :chdframeofs  => chdofs,
                   :logframes    => 0,
                 }
end

Instance Attribute Details

#tocArray<Hash{Symbol => Object}> (readonly)

Table of Content

Returns:

  • (Array<Hash{Symbol => Object}>)


123
124
125
# File 'lib/chd/cd.rb', line 123

def toc
  @toc
end

Class Method Details

.read_toc(chd) ⇒ Array<Hash{Symbol => Object}>?

Read the TOC, and returns it's information in a parsed form.

Parameters:

  • chd (CHD)

    a chd opened file

Returns:

  • (Array<Hash{Symbol => Object}>)

    Table of Content

  • (nil)

    if the CHD file is not of a CD-ROM / GD-ROM type



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/chd/cd.rb', line 59

def self.read_toc(chd)
    return nil if chd.hunk_bytes %  FRAME_SIZE != 0 ||
                  chd.unit_bytes != FRAME_SIZE
    
    flags  = Set.new
    tracks = []
    while (idx = tracks.size) < MAX_TRACKS do
 if    md = chd.(idx, Metadata::CDROM_TRACK,      )
 elsif md = chd.(idx, Metadata::CDROM_TRACK_PREGAP)
 elsif md = chd.(idx, Metadata::GDROM_OLD,        )
            raise NotSupported, "upgrade your CHD to a more recent version"
 elsif md = chd.(idx, Metadata::GDROM_TRACK,      )
            flags << :GDROM
 else
     break            
 end
        tracks << Metadata.parse(*md)
    end
    
    if ! tracks.empty?
        unless tracks.each_with_index.all? {|trackinfo, index|
                   trackinfo[:track] == index + 1
               }
            raise ParsingError, "unordered tracks"
        end
        [ tracks, flags ]
    elsif chd.(0, Metadata::CDROM_OLD)
        raise NotSupported, "upgrade your CHD to a more recent version"
    else
        raise NotFoundError, "provided CHD is not a CD-ROM"
    end
end

Instance Method Details

#read_sector(lbasector, datatype = nil, phys = false) ⇒ String

Read one or more sectors from a CD-ROM

Parameters:

  • lbasector (Integer)

    sector number

  • datatype (Symbol) (defaults to: nil)

    type of data

Returns:

  • (String)


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/chd/cd.rb', line 151

def read_sector(lbasector, datatype = nil, phys = false)
    # Compute CHD sector and track index
    frame_ofs_type = phys ? :physframeofs : :logframeofs
    chdsector      = lbasector
    trackidx       = 0
    @mapping.each_cons(2).with_index do |(cur, nxt), idx|
        if lbasector < nxt[frame_ofs_type]
            chdsector = lbasector - cur[frame_ofs_type] + cur[:chdframeofs]
            trackidx = idx
            break
        end
    end


    trackinfo = @toc[trackidx];
	tracktype = trackinfo[:trktype]

	offset, length, header =
         # return same type or don't care
         if (datatype == tracktype) || datatype.nil?
             [ 0,  trackinfo[:datasize] ]
             
  # return 2048 bytes of MODE1 data
         #   from a 2352 byte MODE1 RAW sector
  elsif (datatype  == :MODE1    ) &&
               (tracktype == :MODE1_RAW)
      [ 16, 2048 ]
             
  # return 2352 byte MODE1 RAW sector
         #  from 2048 bytes of MODE1 data
  elsif (datatype  == :MODE1_RAW) &&
               (tracktype == :MODE1    )
      warn "promotion of MODE1/FORM1 sector to MODE1 RAW is incomplete"
             m, sf = lba.divmod(60 * 75);
             s, f  = sf.divmod(75)
             hdr   = SYNCBYTES + [
          ((m / 10) << 4) | ((m % 10) << 0), # M
          ((s / 10) << 4) | ((s % 10) << 0), # S
          ((f / 10) << 4) | ((f % 10) << 0), # F
                 1                                  # MODE1
             ].pack('C*') # MSF + MODE1
             
             [ 0, 2048, hdr ]
             
  # return 2048 bytes of MODE1 data
         #   from a MODE2 FORM1 or RAW sector
         elsif (datatype  == :MODE1      ) &&
              ((tracktype == :MODE2_FORM1) || (tracktype == :MODE2_RAW  ))
      [ 24, 2048 ]
             
  # return 2048 bytes of MODE1 data
         #   from a MODE2 FORM2 or XA sector
  elsif (datatype  == :MODE1         ) &&
               (tracktype == :MODE2_FORM_MIX)
      [  8, 2048 ]
             
         # return MODE2 2336 byte data
         #   from a 2352 byte MODE1 or MODE2 RAW sector (skip the header)
  elsif (datatype  == :MODE2) &&
              ((tracktype == :MODE1_RAW) || (tracktype == :MODE2_RAW))
      [ 16, 2336 ]
             
         # Not supported
         else
             raise NotSupported,
                   "conversion from type %s to type %s not supported" % [
                       tracktype, datatype ]
  end

    # Read data
	unless phys 
 if ! trackinfo[:pgdatasize].zero?
		# chdman (phys=true) relies on chdframeofs to point
            # to index 0 instead of index 1 for extractcd.
		# Actually playing CDs requires it to point
            # to index 1 instead of index 0,
            # so adjust the offset when phys=false.
		chdsector += trackinfo[:pregap]
        elsif  lbasector < trackinfo[:logframeofs]
     # if this is pregap info that isn't actually in the file,
            # just return blank data
            return '\0' * length
 end
	end

    data = @chd.read_bytes(chdsector * FRAME_SIZE + offset, length)
    data = header + data if header
    data
end

#track_start(track, phys = false) ⇒ Integer

Get the frame number that the track starts at.

Parameters:

  • track (Integer)

    track number (start at 1)

Returns:

  • (Integer)

    frame number



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/chd/cd.rb', line 131

def track_start(track, phys = false)
    frame_ofs_type = phys ? :physframeofs : :logframeofs
    
	# handle lead-out specially
	if track == 0xAA
 @mapping.last[frame_ofs_type]
    elsif ! (1 .. @toc.size).include?(track)
        raise RangeError, "track must be in 1..#{@toc.size}"
    else
        @mappging.dig(track - 1, frame_ofs_type)
    end
end