chd 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +30 -0
- data/chd.gemspec +29 -0
- data/ext/chd.c +1008 -0
- data/ext/extconf.rb +60 -0
- data/lib/chd/cd.rb +272 -0
- data/lib/chd/metadata.rb +196 -0
- data/lib/chd/version.rb +4 -0
- data/lib/chd.rb +21 -0
- data/libchdr/CMakeLists.txt +104 -0
- data/libchdr/LICENSE.txt +24 -0
- data/libchdr/README.md +7 -0
- data/libchdr/deps/lzma-19.00/CMakeLists.txt +33 -0
- data/libchdr/deps/lzma-19.00/LICENSE +3 -0
- data/libchdr/deps/lzma-19.00/include/7zTypes.h +375 -0
- data/libchdr/deps/lzma-19.00/include/Alloc.h +51 -0
- data/libchdr/deps/lzma-19.00/include/Bra.h +64 -0
- data/libchdr/deps/lzma-19.00/include/Compiler.h +33 -0
- data/libchdr/deps/lzma-19.00/include/CpuArch.h +336 -0
- data/libchdr/deps/lzma-19.00/include/Delta.h +19 -0
- data/libchdr/deps/lzma-19.00/include/LzFind.h +121 -0
- data/libchdr/deps/lzma-19.00/include/LzHash.h +57 -0
- data/libchdr/deps/lzma-19.00/include/Lzma86.h +111 -0
- data/libchdr/deps/lzma-19.00/include/LzmaDec.h +234 -0
- data/libchdr/deps/lzma-19.00/include/LzmaEnc.h +76 -0
- data/libchdr/deps/lzma-19.00/include/LzmaLib.h +131 -0
- data/libchdr/deps/lzma-19.00/include/Precomp.h +10 -0
- data/libchdr/deps/lzma-19.00/include/Sort.h +18 -0
- data/libchdr/deps/lzma-19.00/lzma-history.txt +446 -0
- data/libchdr/deps/lzma-19.00/lzma.txt +328 -0
- data/libchdr/deps/lzma-19.00/lzma.vcxproj +543 -0
- data/libchdr/deps/lzma-19.00/lzma.vcxproj.filters +17 -0
- data/libchdr/deps/lzma-19.00/src/Alloc.c +455 -0
- data/libchdr/deps/lzma-19.00/src/Bra86.c +82 -0
- data/libchdr/deps/lzma-19.00/src/BraIA64.c +53 -0
- data/libchdr/deps/lzma-19.00/src/CpuArch.c +218 -0
- data/libchdr/deps/lzma-19.00/src/Delta.c +64 -0
- data/libchdr/deps/lzma-19.00/src/LzFind.c +1127 -0
- data/libchdr/deps/lzma-19.00/src/Lzma86Dec.c +54 -0
- data/libchdr/deps/lzma-19.00/src/LzmaDec.c +1185 -0
- data/libchdr/deps/lzma-19.00/src/LzmaEnc.c +1330 -0
- data/libchdr/deps/lzma-19.00/src/Sort.c +141 -0
- data/libchdr/deps/zlib-1.2.11/CMakeLists.txt +29 -0
- data/libchdr/deps/zlib-1.2.11/ChangeLog +1515 -0
- data/libchdr/deps/zlib-1.2.11/FAQ +368 -0
- data/libchdr/deps/zlib-1.2.11/INDEX +68 -0
- data/libchdr/deps/zlib-1.2.11/Makefile +5 -0
- data/libchdr/deps/zlib-1.2.11/Makefile.in +410 -0
- data/libchdr/deps/zlib-1.2.11/README +115 -0
- data/libchdr/deps/zlib-1.2.11/adler32.c +186 -0
- data/libchdr/deps/zlib-1.2.11/compress.c +86 -0
- data/libchdr/deps/zlib-1.2.11/configure +921 -0
- data/libchdr/deps/zlib-1.2.11/crc32.c +442 -0
- data/libchdr/deps/zlib-1.2.11/crc32.h +441 -0
- data/libchdr/deps/zlib-1.2.11/deflate.c +2163 -0
- data/libchdr/deps/zlib-1.2.11/deflate.h +349 -0
- data/libchdr/deps/zlib-1.2.11/doc/algorithm.txt +209 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1950.txt +619 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1951.txt +955 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1952.txt +675 -0
- data/libchdr/deps/zlib-1.2.11/doc/txtvsbin.txt +107 -0
- data/libchdr/deps/zlib-1.2.11/gzclose.c +25 -0
- data/libchdr/deps/zlib-1.2.11/gzguts.h +218 -0
- data/libchdr/deps/zlib-1.2.11/gzlib.c +637 -0
- data/libchdr/deps/zlib-1.2.11/gzread.c +654 -0
- data/libchdr/deps/zlib-1.2.11/gzwrite.c +665 -0
- data/libchdr/deps/zlib-1.2.11/infback.c +640 -0
- data/libchdr/deps/zlib-1.2.11/inffast.c +323 -0
- data/libchdr/deps/zlib-1.2.11/inffast.h +11 -0
- data/libchdr/deps/zlib-1.2.11/inffixed.h +94 -0
- data/libchdr/deps/zlib-1.2.11/inflate.c +1561 -0
- data/libchdr/deps/zlib-1.2.11/inflate.h +125 -0
- data/libchdr/deps/zlib-1.2.11/inftrees.c +304 -0
- data/libchdr/deps/zlib-1.2.11/inftrees.h +62 -0
- data/libchdr/deps/zlib-1.2.11/make_vms.com +867 -0
- data/libchdr/deps/zlib-1.2.11/treebuild.xml +116 -0
- data/libchdr/deps/zlib-1.2.11/trees.c +1203 -0
- data/libchdr/deps/zlib-1.2.11/trees.h +128 -0
- data/libchdr/deps/zlib-1.2.11/uncompr.c +93 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h +534 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h.cmakein +536 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h.in +534 -0
- data/libchdr/deps/zlib-1.2.11/zlib.3 +149 -0
- data/libchdr/deps/zlib-1.2.11/zlib.3.pdf +0 -0
- data/libchdr/deps/zlib-1.2.11/zlib.h +1912 -0
- data/libchdr/deps/zlib-1.2.11/zlib.map +94 -0
- data/libchdr/deps/zlib-1.2.11/zlib.pc.cmakein +13 -0
- data/libchdr/deps/zlib-1.2.11/zlib.pc.in +13 -0
- data/libchdr/deps/zlib-1.2.11/zlib2ansi +152 -0
- data/libchdr/deps/zlib-1.2.11/zutil.c +325 -0
- data/libchdr/deps/zlib-1.2.11/zutil.h +271 -0
- data/libchdr/include/dr_libs/dr_flac.h +12280 -0
- data/libchdr/include/libchdr/bitstream.h +43 -0
- data/libchdr/include/libchdr/cdrom.h +110 -0
- data/libchdr/include/libchdr/chd.h +427 -0
- data/libchdr/include/libchdr/chdconfig.h +10 -0
- data/libchdr/include/libchdr/coretypes.h +60 -0
- data/libchdr/include/libchdr/flac.h +50 -0
- data/libchdr/include/libchdr/huffman.h +90 -0
- data/libchdr/pkg-config.pc.in +10 -0
- data/libchdr/src/libchdr_bitstream.c +125 -0
- data/libchdr/src/libchdr_cdrom.c +415 -0
- data/libchdr/src/libchdr_chd.c +2744 -0
- data/libchdr/src/libchdr_flac.c +302 -0
- data/libchdr/src/libchdr_huffman.c +545 -0
- data/libchdr/src/link.T +5 -0
- data/libchdr/tests/CMakeLists.txt +2 -0
- data/libchdr/tests/benchmark.c +52 -0
- metadata +183 -0
data/ext/extconf.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
ROOT = File.join(__dir__, '..')
|
|
5
|
+
|
|
6
|
+
def with_success(&block)
|
|
7
|
+
state = [ $CFLAGS, $LDFLAGS, $libs ]
|
|
8
|
+
block.call.tap {|success|
|
|
9
|
+
$CFLAGS, $LDFLAGS, $libs = state unless success
|
|
10
|
+
}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
build = Set[]
|
|
15
|
+
|
|
16
|
+
cmake = find_executable('cmake')
|
|
17
|
+
|
|
18
|
+
unless pkg_config('libchdr').tap {|found|
|
|
19
|
+
puts "found pkg-config for libchdr" if found
|
|
20
|
+
} ||
|
|
21
|
+
with_success do
|
|
22
|
+
find_library('chdr', 'chd_open') & find_header('libchdr/chd.h')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
$CFLAGS += " -I " + File.join(ROOT, 'libchdr', 'include')
|
|
26
|
+
|
|
27
|
+
$libs += [ 'deps/zlib-1.2.11/libzlib.a',
|
|
28
|
+
'deps/lzma-19.00/liblzma.a',
|
|
29
|
+
'CMakeFiles/chdr.dir/src/libchdr_chd.c.o',
|
|
30
|
+
'CMakeFiles/chdr.dir/src/libchdr_flac.c.o',
|
|
31
|
+
'CMakeFiles/chdr.dir/src/libchdr_bitstream.c.o',
|
|
32
|
+
'CMakeFiles/chdr.dir/src/libchdr_huffman.c.o',
|
|
33
|
+
'CMakeFiles/chdr.dir/src/libchdr_cdrom.c.o',
|
|
34
|
+
].map {|l|
|
|
35
|
+
' ' + File.join(ROOT, 'build', l)
|
|
36
|
+
}.join
|
|
37
|
+
|
|
38
|
+
# $libs += [ 'libchdr-static.a', 'liblzma.a', 'libzlib.a' ].map {|l|
|
|
39
|
+
# ' ' + File.join(ROOT, 'stage', 'lib', l)
|
|
40
|
+
# }.join(' ')
|
|
41
|
+
|
|
42
|
+
build.add(:libchdr)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
create_makefile('chd/core')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if build.include?(:libchdr)
|
|
49
|
+
puts "Building bundled libchdr"
|
|
50
|
+
|
|
51
|
+
Dir.chdir(ROOT) do
|
|
52
|
+
unless cmake &&
|
|
53
|
+
system(cmake, '-DINSTALL_STATIC_LIBS=on',
|
|
54
|
+
'-S', 'libchdr', '-B', 'build') &&
|
|
55
|
+
system(cmake, '--build', 'build')
|
|
56
|
+
abort "failed building libchdr"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
data/lib/chd/cd.rb
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'digest'
|
|
3
|
+
|
|
4
|
+
class CHD
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Access a CD-ROM / GD-ROM in Mame CHD format
|
|
8
|
+
#
|
|
9
|
+
class CD
|
|
10
|
+
# Maximum number of tracks in a CD-ROM
|
|
11
|
+
MAX_TRACKS = 99
|
|
12
|
+
|
|
13
|
+
# Maximum sector size
|
|
14
|
+
MAX_SECTOR_DATASIZE = 2352
|
|
15
|
+
|
|
16
|
+
# Maximum subcode size
|
|
17
|
+
MAX_SUBCODE_DATASIZE = 96
|
|
18
|
+
|
|
19
|
+
# Maximum frame size
|
|
20
|
+
FRAME_SIZE = MAX_SUBCODE_DATASIZE + MAX_SECTOR_DATASIZE
|
|
21
|
+
|
|
22
|
+
# @!visibility private
|
|
23
|
+
TRACK_PADDING = 4
|
|
24
|
+
|
|
25
|
+
# Various sector data size according to track type
|
|
26
|
+
TRACK_TYPE_DATASIZE = {
|
|
27
|
+
:MODE1 => 2048,
|
|
28
|
+
:MODE1_RAW => 2352,
|
|
29
|
+
:MODE2 => 2336,
|
|
30
|
+
:MODE2_FORM1 => 2048,
|
|
31
|
+
:MODE2_FORM2 => 2324,
|
|
32
|
+
:MODE2_FORM_MIX => 2336,
|
|
33
|
+
:MODE2_RAW => 2352,
|
|
34
|
+
:AUDIO => 2352,
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# Various subcode data size according to track type
|
|
38
|
+
TRACK_SUBTYPE_DATASIZE = {
|
|
39
|
+
:NONE => 0,
|
|
40
|
+
:NORMAL => 96,
|
|
41
|
+
:RAW => 96,
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
SYNCBYTES = [ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
47
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0x00
|
|
48
|
+
].pack('C*').freeze
|
|
49
|
+
|
|
50
|
+
public
|
|
51
|
+
|
|
52
|
+
# Read the TOC, and returns it's information in a parsed form.
|
|
53
|
+
#
|
|
54
|
+
# @param chd [CHD] a chd opened file
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<Hash{Symbol => Object}>] Table of Content
|
|
57
|
+
# @return [nil] if the CHD file is not of a CD-ROM / GD-ROM type
|
|
58
|
+
#
|
|
59
|
+
def self.read_toc(chd)
|
|
60
|
+
return nil if chd.hunk_bytes % FRAME_SIZE != 0 ||
|
|
61
|
+
chd.unit_bytes != FRAME_SIZE
|
|
62
|
+
|
|
63
|
+
flags = Set.new
|
|
64
|
+
tracks = []
|
|
65
|
+
while (idx = tracks.size) < MAX_TRACKS do
|
|
66
|
+
if md = chd.get_metadata(idx, Metadata::CDROM_TRACK, )
|
|
67
|
+
elsif md = chd.get_metadata(idx, Metadata::CDROM_TRACK_PREGAP)
|
|
68
|
+
elsif md = chd.get_metadata(idx, Metadata::GDROM_OLD, )
|
|
69
|
+
raise NotSupported, "upgrade your CHD to a more recent version"
|
|
70
|
+
elsif md = chd.get_metadata(idx, Metadata::GDROM_TRACK, )
|
|
71
|
+
flags << :GDROM
|
|
72
|
+
else
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
tracks << Metadata.parse(*md)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if ! tracks.empty?
|
|
79
|
+
unless tracks.each_with_index.all? {|trackinfo, index|
|
|
80
|
+
trackinfo[:track] == index + 1
|
|
81
|
+
}
|
|
82
|
+
raise ParsingError, "unordered tracks"
|
|
83
|
+
end
|
|
84
|
+
[ tracks, flags ]
|
|
85
|
+
elsif chd.get_metadata(0, Metadata::CDROM_OLD)
|
|
86
|
+
raise NotSupported, "upgrade your CHD to a more recent version"
|
|
87
|
+
else
|
|
88
|
+
raise NotFoundError, "provided CHD is not a CD-ROM"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def initialize(chd)
|
|
94
|
+
@chd = chd
|
|
95
|
+
@toc, @flags = CD.read_toc(chd)
|
|
96
|
+
|
|
97
|
+
# Build mapping
|
|
98
|
+
chdofs = physofs = logofs = 0
|
|
99
|
+
@mapping = @toc.map {|trackinfo|
|
|
100
|
+
{ :physframeofs => physofs,
|
|
101
|
+
:chdframeofs => chdofs,
|
|
102
|
+
:logframeofs => trackinfo[:pregap] + logofs,
|
|
103
|
+
:logframes => trackinfo[:frames] - trackinfo[:pregap],
|
|
104
|
+
}.tap {
|
|
105
|
+
logofs += trackinfo[:pregap] if trackinfo[:pgdatasize].zero?
|
|
106
|
+
logofs += trackinfo[:frames] + trackinfo[:postgap]
|
|
107
|
+
physofs += trackinfo[:frames]
|
|
108
|
+
chdofs += trackinfo[:frames] + trackinfo[:extraframes]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
@mapping << { :physframeofs => physofs,
|
|
112
|
+
:logframeofs => logofs,
|
|
113
|
+
:chdframeofs => chdofs,
|
|
114
|
+
:logframes => 0,
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Table of Content
|
|
120
|
+
#
|
|
121
|
+
# @return [Array<Hash{Symbol => Object}>]
|
|
122
|
+
#
|
|
123
|
+
attr_reader :toc
|
|
124
|
+
|
|
125
|
+
# Get the frame number that the track starts at.
|
|
126
|
+
#
|
|
127
|
+
# @param track [Integer] track number (start at 1)
|
|
128
|
+
#
|
|
129
|
+
# @return [Integer] frame number
|
|
130
|
+
#
|
|
131
|
+
def track_start(track, phys = false)
|
|
132
|
+
frame_ofs_type = phys ? :physframeofs : :logframeofs
|
|
133
|
+
|
|
134
|
+
# handle lead-out specially
|
|
135
|
+
if track == 0xAA
|
|
136
|
+
@mapping.last[frame_ofs_type]
|
|
137
|
+
elsif ! (1 .. @toc.size).include?(track)
|
|
138
|
+
raise RangeError, "track must be in 1..#{@toc.size}"
|
|
139
|
+
else
|
|
140
|
+
@mappging.dig(track - 1, frame_ofs_type)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Read one or more sectors from a CD-ROM
|
|
145
|
+
#
|
|
146
|
+
# @param lbasector [Integer] sector number
|
|
147
|
+
# @param datatype [Symbol] type of data
|
|
148
|
+
#
|
|
149
|
+
# @return [String]
|
|
150
|
+
#
|
|
151
|
+
def read_sector(lbasector, datatype = nil, phys = false)
|
|
152
|
+
# Compute CHD sector and track index
|
|
153
|
+
frame_ofs_type = phys ? :physframeofs : :logframeofs
|
|
154
|
+
chdsector = lbasector
|
|
155
|
+
trackidx = 0
|
|
156
|
+
@mapping.each_cons(2).with_index do |(cur, nxt), idx|
|
|
157
|
+
if lbasector < nxt[frame_ofs_type]
|
|
158
|
+
chdsector = lbasector - cur[frame_ofs_type] + cur[:chdframeofs]
|
|
159
|
+
trackidx = idx
|
|
160
|
+
break
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
trackinfo = @toc[trackidx];
|
|
166
|
+
tracktype = trackinfo[:trktype]
|
|
167
|
+
|
|
168
|
+
offset, length, header =
|
|
169
|
+
# return same type or don't care
|
|
170
|
+
if (datatype == tracktype) || datatype.nil?
|
|
171
|
+
[ 0, trackinfo[:datasize] ]
|
|
172
|
+
|
|
173
|
+
# return 2048 bytes of MODE1 data
|
|
174
|
+
# from a 2352 byte MODE1 RAW sector
|
|
175
|
+
elsif (datatype == :MODE1 ) &&
|
|
176
|
+
(tracktype == :MODE1_RAW)
|
|
177
|
+
[ 16, 2048 ]
|
|
178
|
+
|
|
179
|
+
# return 2352 byte MODE1 RAW sector
|
|
180
|
+
# from 2048 bytes of MODE1 data
|
|
181
|
+
elsif (datatype == :MODE1_RAW) &&
|
|
182
|
+
(tracktype == :MODE1 )
|
|
183
|
+
warn "promotion of MODE1/FORM1 sector to MODE1 RAW is incomplete"
|
|
184
|
+
m, sf = lba.divmod(60 * 75);
|
|
185
|
+
s, f = sf.divmod(75)
|
|
186
|
+
hdr = SYNCBYTES + [
|
|
187
|
+
((m / 10) << 4) | ((m % 10) << 0), # M
|
|
188
|
+
((s / 10) << 4) | ((s % 10) << 0), # S
|
|
189
|
+
((f / 10) << 4) | ((f % 10) << 0), # F
|
|
190
|
+
1 # MODE1
|
|
191
|
+
].pack('C*') # MSF + MODE1
|
|
192
|
+
|
|
193
|
+
[ 0, 2048, hdr ]
|
|
194
|
+
|
|
195
|
+
# return 2048 bytes of MODE1 data
|
|
196
|
+
# from a MODE2 FORM1 or RAW sector
|
|
197
|
+
elsif (datatype == :MODE1 ) &&
|
|
198
|
+
((tracktype == :MODE2_FORM1) || (tracktype == :MODE2_RAW ))
|
|
199
|
+
[ 24, 2048 ]
|
|
200
|
+
|
|
201
|
+
# return 2048 bytes of MODE1 data
|
|
202
|
+
# from a MODE2 FORM2 or XA sector
|
|
203
|
+
elsif (datatype == :MODE1 ) &&
|
|
204
|
+
(tracktype == :MODE2_FORM_MIX)
|
|
205
|
+
[ 8, 2048 ]
|
|
206
|
+
|
|
207
|
+
# return MODE2 2336 byte data
|
|
208
|
+
# from a 2352 byte MODE1 or MODE2 RAW sector (skip the header)
|
|
209
|
+
elsif (datatype == :MODE2) &&
|
|
210
|
+
((tracktype == :MODE1_RAW) || (tracktype == :MODE2_RAW))
|
|
211
|
+
[ 16, 2336 ]
|
|
212
|
+
|
|
213
|
+
# Not supported
|
|
214
|
+
else
|
|
215
|
+
raise NotSupported,
|
|
216
|
+
"conversion from type %s to type %s not supported" % [
|
|
217
|
+
tracktype, datatype ]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Read data
|
|
221
|
+
unless phys
|
|
222
|
+
if ! trackinfo[:pgdatasize].zero?
|
|
223
|
+
# chdman (phys=true) relies on chdframeofs to point
|
|
224
|
+
# to index 0 instead of index 1 for extractcd.
|
|
225
|
+
# Actually playing CDs requires it to point
|
|
226
|
+
# to index 1 instead of index 0,
|
|
227
|
+
# so adjust the offset when phys=false.
|
|
228
|
+
chdsector += trackinfo[:pregap]
|
|
229
|
+
elsif lbasector < trackinfo[:logframeofs]
|
|
230
|
+
# if this is pregap info that isn't actually in the file,
|
|
231
|
+
# just return blank data
|
|
232
|
+
return '\0' * length
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
data = @chd.read_bytes(chdsector * FRAME_SIZE + offset, length)
|
|
237
|
+
data = header + data if header
|
|
238
|
+
data
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _enhance_toc(toc)
|
|
246
|
+
chd_offset = physical_offset = logical_offset = 0
|
|
247
|
+
|
|
248
|
+
toc.each do |trackinfo|
|
|
249
|
+
trackinfo[:logframeofs] = 0
|
|
250
|
+
|
|
251
|
+
if trackinfo[:pgdatasize].zero?
|
|
252
|
+
logical_offset += trackinfo[:pregap]
|
|
253
|
+
else
|
|
254
|
+
trackinfo[:logframeofs] = trackinfo[:pregap]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
trackinfo[:physframeofs] = physical_offset
|
|
258
|
+
trackinfo[:chdframeofs ] = chd_offset
|
|
259
|
+
trackinfo[:logframeofs ] += logical_offset
|
|
260
|
+
trackinfo[:logframes ] = trackinfo[:frames] - trackinfo[:pregap]
|
|
261
|
+
|
|
262
|
+
logical_offset += trackinfo[:frames] + trackinfo[:postgap]
|
|
263
|
+
physical_offset += trackinfo[:frames]
|
|
264
|
+
chd_offset += trackinfo[:frames] + trackinfo[:extraframes]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
toc
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
data/lib/chd/metadata.rb
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
class CHD
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Parsing of the metadata failed.
|
|
7
|
+
#
|
|
8
|
+
class ParsingError < Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
#
|
|
13
|
+
# | Type | Associated scanf string |
|
|
14
|
+
# |------|----------------------------------------------------------------------------------------|
|
|
15
|
+
# | GDDD | "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d" |
|
|
16
|
+
# | CHTR | "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d" |
|
|
17
|
+
# | CHT2 | "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d" |
|
|
18
|
+
# | CHGD | "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PAD:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d" |
|
|
19
|
+
# | AVAV | "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d" |
|
|
20
|
+
#
|
|
21
|
+
module Metadata
|
|
22
|
+
HARD_DISK = :'GDDD' # Hard disk
|
|
23
|
+
HARD_DISK_IDENT = :'IDNT' # Hard disk identity
|
|
24
|
+
HARD_DISK_KEY = :'KEY ' # Hard disk key
|
|
25
|
+
PCMCIA_CIS = :'CIS ' # PCMCIA CIS
|
|
26
|
+
CDROM_TRACK = :'CHTR' # CDROM Track
|
|
27
|
+
CDROM_TRACK_PREGAP = :'CHT2' # CDROM Track with pregap
|
|
28
|
+
GDROM_TRACK = :'CHGD' # GDROM Track (SEGA Genesis)
|
|
29
|
+
AV = :'AVAV' # Audio/Video
|
|
30
|
+
AV_LD = :'AVLD' # Audio/Video Laser Disk
|
|
31
|
+
CDROM_OLD = :'CHCD' # CDROM Track (old CHD version)
|
|
32
|
+
GDROM_OLD = :'CHGT' # GDROM Track (old CHD version)
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
HARD_DISK_REGEX = /\A CYLS: (?<cyls>%d) ,
|
|
37
|
+
HEADS: (?<heads>%d) ,
|
|
38
|
+
SECS: (?<secs>%d) ,
|
|
39
|
+
BPS: (?<bps>)%d
|
|
40
|
+
\z /x
|
|
41
|
+
CDROM_TRACK_REGEX = /\A TRACK: (?<track>\d+) \s+
|
|
42
|
+
TYPE: (?<trktype>\w+) \s+
|
|
43
|
+
SUBTYPE: (?<subtype>\w+) \s+
|
|
44
|
+
FRAMES: (?<frames>\d+)
|
|
45
|
+
\z /x
|
|
46
|
+
CDROM_TRACK_PREGAP_REGEX = /\A TRACK: (?<track>\d+) \s+
|
|
47
|
+
TYPE: (?<trktype>\w+) \s+
|
|
48
|
+
SUBTYPE: (?<subtype>\w+) \s+
|
|
49
|
+
FRAMES: (?<frames>\d+) \s+
|
|
50
|
+
PREGAP: (?<pregap>\d+) \s+
|
|
51
|
+
PGTYPE: V?(?<pgtype>\w+) \s+
|
|
52
|
+
PGSUB: (?<pgsub>\w+) \s+
|
|
53
|
+
POSTGAP: (?<postgap>\d+)
|
|
54
|
+
\z /x
|
|
55
|
+
GDROM_TRACK_REGEX = /\A TRACK: (?<track>\d+) \s+
|
|
56
|
+
TYPE: (?<trktype>\w+) \s+
|
|
57
|
+
SUBTYPE: (?<subtype>\w+) \s+
|
|
58
|
+
FRAMES: (?<frames>\d+) \s+
|
|
59
|
+
PAD: (?<padframes>\d+) \s+
|
|
60
|
+
PREGAP: (?<pregap>\d+) \s+
|
|
61
|
+
PGTYPE: V?(?<pgtype>\w+) \s+
|
|
62
|
+
PGSUB: (?<pgsub>\w+) \s+
|
|
63
|
+
POSTGAP: (?<postgap>\d+)
|
|
64
|
+
\z /x
|
|
65
|
+
AV_REGEX = /\A FPS: (?<fps>\d+\.\d+) \s+
|
|
66
|
+
WIDTH: (?<width>\d+) \s+
|
|
67
|
+
HEIGHT: (?<height>\d+) \s+
|
|
68
|
+
INTERLACED:(?<interlaced>\d+) \s+
|
|
69
|
+
CHANNELS: (?<channels>\d+) \s+
|
|
70
|
+
SAMPLERATE:(?<samplerate>\d+)
|
|
71
|
+
\z /x
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
CD_TRACK_TYPES = {
|
|
75
|
+
'MODE1' => :MODE1,
|
|
76
|
+
'MODE1/2048' => :MODE1,
|
|
77
|
+
'MODE1_RAW' => :MODE1_RAW,
|
|
78
|
+
'MODE1/2352' => :MODE1_RAW,
|
|
79
|
+
'MODE2' => :MODE2,
|
|
80
|
+
'MODE2/2336' => :MODE2,
|
|
81
|
+
'MODE2_FORM1' => :MODE2_FORM1,
|
|
82
|
+
'MODE2/2048' => :MODE2_FORM1,
|
|
83
|
+
'MODE2_FORM2' => :MODE2_FORM2,
|
|
84
|
+
'MODE2/2324' => :MODE2_FORM2,
|
|
85
|
+
'MODE2_FORM_MIX' => :MODE2_FORM_MIX,
|
|
86
|
+
'MODE2/2336' => :MODE2_FORM_MIX,
|
|
87
|
+
'MODE2_RAW' => :MODE2_RAW,
|
|
88
|
+
'MODE2/2352' => :MODE2_RAW,
|
|
89
|
+
'AUDIO' => :AUDIO,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
CD_TRACK_SUBTYPES = {
|
|
93
|
+
'NONE' => :NONE,
|
|
94
|
+
'RW' => :NORMAL,
|
|
95
|
+
'RW_RAW' => :RAW,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
public
|
|
101
|
+
|
|
102
|
+
# Parse metadata returned by {CHD#get_metadata}
|
|
103
|
+
#
|
|
104
|
+
# @param data [String]
|
|
105
|
+
# @param flags [Integer]
|
|
106
|
+
# @param type [Symbol]
|
|
107
|
+
#
|
|
108
|
+
# @return [Hash{Symbol => Object}]
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# chd = CHD.new('file.chd')
|
|
112
|
+
# puts CHD::Metadata.parse(*chd.get_metadata(0)).inspect
|
|
113
|
+
#
|
|
114
|
+
def self.parse(data, flags, type)
|
|
115
|
+
# Flags
|
|
116
|
+
unless (flags & ~(METADATA_FLAG_CHECKSUM)).zero?
|
|
117
|
+
raise ParsingError,
|
|
118
|
+
"unsupported flag (0x#{flags.to_s(16)}) (fill bug report)"
|
|
119
|
+
end
|
|
120
|
+
set_flags = Set.new
|
|
121
|
+
set_flags << :checksum unless (flags & METADATA_FLAG_CHECKSUM).zero?
|
|
122
|
+
|
|
123
|
+
# Data
|
|
124
|
+
case type
|
|
125
|
+
when CDROM_TRACK then parse_cdrom_track(data, CDROM_TRACK_REGEX)
|
|
126
|
+
when CDROM_TRACK_PREGAP then parse_cdrom_track(data, CDROM_TRACK_PREGAP_REGEX)
|
|
127
|
+
when GDROM_TRACK then parse_cdrom_track(data, GDROM_TRACK_REGEX)
|
|
128
|
+
when HARD_DISK then parse_hard_disk(data, HARD_DISK_REGEX)
|
|
129
|
+
else raise "not implemented yet"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def self.parse_cdrom_track(data, regex)
|
|
136
|
+
md = parse_using_regex(data, regex, :trktype => CD_TRACK_TYPES,
|
|
137
|
+
:subtype => CD_TRACK_SUBTYPES,
|
|
138
|
+
:pgtype => CD_TRACK_TYPES,
|
|
139
|
+
:pgsub => CD_TRACK_SUBTYPES)
|
|
140
|
+
dflt = { :track => nil,
|
|
141
|
+
:trktype => nil,
|
|
142
|
+
:subtype => nil,
|
|
143
|
+
:frames => nil,
|
|
144
|
+
:padframes => 0,
|
|
145
|
+
:pregap => 0,
|
|
146
|
+
:pgtype => :MODE1,
|
|
147
|
+
:pgsub => :NONE,
|
|
148
|
+
:postgap => 0,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
md = dflt.merge(md)
|
|
152
|
+
|
|
153
|
+
if (md[:track] < 0) || (md[:track] > CD::MAX_TRACKS)
|
|
154
|
+
raise ParsingError, "track number out of range"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
md.merge(:extraframes => CD::TRACK_PADDING -
|
|
158
|
+
md[:frames] % CD::TRACK_PADDING,
|
|
159
|
+
:datasize => CD::TRACK_TYPE_DATASIZE[ md[:trktype]],
|
|
160
|
+
:subsize => CD::TRACK_SUBTYPE_DATASIZE[md[:subtype]],
|
|
161
|
+
:pgdatasize => CD::TRACK_TYPE_DATASIZE[ md[:pgtype ]],
|
|
162
|
+
:pgsubsize => CD::TRACK_SUBTYPE_DATASIZE[md[:pgsub ]],
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def self.parse_hard_disk(data, regex)
|
|
167
|
+
parse_using_regex(data, regex)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def self.parse_using_regex(data, regex, mapping = nil, &block)
|
|
171
|
+
unless md = regex.match(data)&.named_captures
|
|
172
|
+
raise ParsingError, "failed to parse track"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
md.transform_keys!(&:to_sym)
|
|
176
|
+
md.transform_values! do |v|
|
|
177
|
+
case v
|
|
178
|
+
when /^\d+$/ then Integer(v)
|
|
179
|
+
when /^\d+\.\d+$/ then Float(v)
|
|
180
|
+
else v
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
(mapping || {}).each do |key, map|
|
|
185
|
+
md[key] = map.fetch(md[key]) if md.include?(key)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
block&.call(md)
|
|
189
|
+
|
|
190
|
+
md
|
|
191
|
+
rescue KeyError
|
|
192
|
+
raise ParsingError, "unable to decode track description"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
end
|
|
196
|
+
end
|
data/lib/chd/version.rb
ADDED
data/lib/chd.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
require 'chd/core'
|
|
3
|
+
require 'chd/metadata'
|
|
4
|
+
require 'chd/cd'
|
|
5
|
+
|
|
6
|
+
class CHD
|
|
7
|
+
|
|
8
|
+
# Returns a string representation of the number of frames using
|
|
9
|
+
# minutes:seconds:frames format.
|
|
10
|
+
#
|
|
11
|
+
# @param frames [Integer]
|
|
12
|
+
#
|
|
13
|
+
# @return [String]
|
|
14
|
+
#
|
|
15
|
+
def self.msf(frames)
|
|
16
|
+
"%02d:%02d:%02d" % [ frames / (75 * 60), (frames / 75) % 60, frames % 75 ]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.9)
|
|
2
|
+
|
|
3
|
+
project(chdr VERSION 0.1 LANGUAGES C)
|
|
4
|
+
|
|
5
|
+
if(CMAKE_PROJECT_NAME STREQUAL "chdr")
|
|
6
|
+
option(BUILD_SHARED_LIBS "Build libchdr also as a shared library" ON)
|
|
7
|
+
endif()
|
|
8
|
+
option(INSTALL_STATIC_LIBS "Install static libraries" OFF)
|
|
9
|
+
option(WITH_SYSTEM_ZLIB "Use system provided zlib library" OFF)
|
|
10
|
+
|
|
11
|
+
option(BUILD_LTO "Compile libchdr with link-time optimization if supported" OFF)
|
|
12
|
+
if(BUILD_LTO)
|
|
13
|
+
include(CheckIPOSupported)
|
|
14
|
+
check_ipo_supported(RESULT HAVE_IPO)
|
|
15
|
+
if(HAVE_IPO)
|
|
16
|
+
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
|
17
|
+
endif()
|
|
18
|
+
endif()
|
|
19
|
+
|
|
20
|
+
include(GNUInstallDirs)
|
|
21
|
+
|
|
22
|
+
#--------------------------------------------------
|
|
23
|
+
# dependencies
|
|
24
|
+
#--------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# lzma
|
|
28
|
+
add_subdirectory(deps/lzma-19.00 EXCLUDE_FROM_ALL)
|
|
29
|
+
list(APPEND CHDR_LIBS lzma)
|
|
30
|
+
list(APPEND CHDR_INCLUDES lzma)
|
|
31
|
+
|
|
32
|
+
# zlib
|
|
33
|
+
if (WITH_SYSTEM_ZLIB)
|
|
34
|
+
find_package(ZLIB REQUIRED)
|
|
35
|
+
list(APPEND PLATFORM_LIBS ZLIB::ZLIB)
|
|
36
|
+
else()
|
|
37
|
+
add_subdirectory(deps/zlib-1.2.11 EXCLUDE_FROM_ALL)
|
|
38
|
+
list(APPEND CHDR_LIBS zlib)
|
|
39
|
+
list(APPEND CHDR_INCLUDES zlib)
|
|
40
|
+
endif()
|
|
41
|
+
|
|
42
|
+
#--------------------------------------------------
|
|
43
|
+
# chdr
|
|
44
|
+
#--------------------------------------------------
|
|
45
|
+
|
|
46
|
+
set(CHDR_SOURCES
|
|
47
|
+
src/libchdr_bitstream.c
|
|
48
|
+
src/libchdr_cdrom.c
|
|
49
|
+
src/libchdr_chd.c
|
|
50
|
+
src/libchdr_flac.c
|
|
51
|
+
src/libchdr_huffman.c
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
list(APPEND CHDR_INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/include)
|
|
55
|
+
|
|
56
|
+
add_library(chdr-static STATIC ${CHDR_SOURCES})
|
|
57
|
+
target_include_directories(chdr-static PRIVATE ${CHDR_INCLUDES} PUBLIC include)
|
|
58
|
+
target_link_libraries(chdr-static PRIVATE ${CHDR_LIBS} ${PLATFORM_LIBS})
|
|
59
|
+
|
|
60
|
+
if(MSVC)
|
|
61
|
+
target_compile_definitions(chdr-static PRIVATE _CRT_SECURE_NO_WARNINGS)
|
|
62
|
+
endif()
|
|
63
|
+
|
|
64
|
+
if (INSTALL_STATIC_LIBS)
|
|
65
|
+
install(TARGETS chdr-static ${CHDR_LIBS}
|
|
66
|
+
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
67
|
+
)
|
|
68
|
+
endif()
|
|
69
|
+
|
|
70
|
+
if (BUILD_SHARED_LIBS)
|
|
71
|
+
add_library(chdr SHARED ${CHDR_SOURCES})
|
|
72
|
+
target_include_directories(chdr PRIVATE ${CHDR_INCLUDES} PUBLIC include)
|
|
73
|
+
target_link_libraries(chdr PRIVATE ${CHDR_LIBS} ${PLATFORM_LIBS})
|
|
74
|
+
|
|
75
|
+
if(MSVC)
|
|
76
|
+
target_compile_definitions(chdr PUBLIC "CHD_DLL")
|
|
77
|
+
target_compile_definitions(chdr PRIVATE "CHD_DLL_EXPORTS")
|
|
78
|
+
target_compile_definitions(chdr PRIVATE _CRT_SECURE_NO_WARNINGS)
|
|
79
|
+
elseif(APPLE)
|
|
80
|
+
target_link_libraries(chdr PRIVATE -Wl,-dead_strip -Wl,-exported_symbol,_chd_*)
|
|
81
|
+
else()
|
|
82
|
+
target_link_libraries(chdr PRIVATE -Wl,--version-script ${CMAKE_CURRENT_SOURCE_DIR}/src/link.T -Wl,--no-undefined)
|
|
83
|
+
endif()
|
|
84
|
+
|
|
85
|
+
set_target_properties(chdr PROPERTIES C_VISIBILITY_PRESET hidden)
|
|
86
|
+
set_target_properties(chdr PROPERTIES VISIBILITY_INLINES_HIDDEN 1)
|
|
87
|
+
set_target_properties(chdr PROPERTIES PUBLIC_HEADER "include/libchdr/bitstream.h;include/libchdr/cdrom.h;include/libchdr/chd.h;include/libchdr/chdconfig.h;include/libchdr/coretypes.h;include/libchdr/flac.h;include/libchdr/huffman.h")
|
|
88
|
+
set_target_properties(chdr PROPERTIES VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" SOVERSION ${PROJECT_VERSION_MAJOR})
|
|
89
|
+
|
|
90
|
+
if (CMAKE_BUILD_TYPE MATCHES Release)
|
|
91
|
+
#add_custom_command(TARGET chdr POST_BUILD COMMAND ${CMAKE_STRIP} libchdr.so)
|
|
92
|
+
endif (CMAKE_BUILD_TYPE MATCHES Release)
|
|
93
|
+
|
|
94
|
+
install(TARGETS chdr
|
|
95
|
+
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
96
|
+
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
97
|
+
PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libchdr"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
configure_file(pkg-config.pc.in ${CMAKE_BINARY_DIR}/libchdr.pc @ONLY)
|
|
101
|
+
install(FILES ${CMAKE_BINARY_DIR}/libchdr.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
|
102
|
+
endif()
|
|
103
|
+
|
|
104
|
+
add_subdirectory(tests)
|
data/libchdr/LICENSE.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright Romain Tisserand
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
* Redistributions of source code must retain the above copyright
|
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
* Neither the name of the <organization> nor the
|
|
12
|
+
names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/libchdr/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# libchdr
|
|
2
|
+
|
|
3
|
+
libchdr is a standalone library for reading MAME's CHDv1-v5 formats.
|
|
4
|
+
|
|
5
|
+
The code is based off of MAME's old C codebase which read up to CHDv4 with OS-dependent features removed, and CHDv5 support backported from MAME's current C++ codebase.
|
|
6
|
+
|
|
7
|
+
libchdr is licensed under the BSD 3-Clause (see [LICENSE.txt](LICENSE.txt)) and uses third party libraries that are each distributed under their own terms (see each library's license in [deps/](deps/)).
|