chd 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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/chd.c
ADDED
@@ -0,0 +1,1008 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/io.h>
|
3
|
+
#include <libchdr/chd.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <stdio.h>
|
6
|
+
#include <string.h>
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Document-class: CHD
|
10
|
+
*
|
11
|
+
* Accessing CHD MAME file.
|
12
|
+
*/
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Document-class: CHD::Error
|
16
|
+
*
|
17
|
+
* Generic error raised by CHD.
|
18
|
+
*/
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Document-class: CHD::NotSupportedError
|
22
|
+
*
|
23
|
+
* An operation is not supported.
|
24
|
+
*
|
25
|
+
* It is mapped to the following libchdr error: `CHDERR_NOT_SUPPORTED`.
|
26
|
+
*/
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Document-class: CHD::IOError
|
30
|
+
*
|
31
|
+
* Error that happens when reading/writing data from/to the CHD file.
|
32
|
+
*
|
33
|
+
* It is mapped to the following libchdr errors:
|
34
|
+
* `CHDERR_READ_ERROR`, `CHDERR_WRITE_ERROR`, `CHDERR_CODEC_ERROR`,
|
35
|
+
* `CHDERR_HUNK_OUT_OF_RANGE`, `CHDERR_DECOMPRESSION_ERROR`,
|
36
|
+
* `CHDERR_COMPRESSION_ERROR`.
|
37
|
+
*/
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Document-class: CHD::DataError
|
41
|
+
*
|
42
|
+
* Some retrieved data are not valid.
|
43
|
+
*
|
44
|
+
* It is mapped to the following libchdr errors:
|
45
|
+
* `CHDERR_INVALID_FILE`, `CHDERR_INVALID_DATA`.
|
46
|
+
*/
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Document-class: CHD::NotFoundError
|
50
|
+
*
|
51
|
+
* The requested file doesn't exist.
|
52
|
+
*
|
53
|
+
* It is mapped to the following libchdr error:
|
54
|
+
* `CHDERR_FILE_NOT_FOUND`.
|
55
|
+
*/
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Document-class: CHD::NotWritableError
|
59
|
+
*
|
60
|
+
* The requested file is not writable.
|
61
|
+
*
|
62
|
+
* It is mapped to the following libchdr error:
|
63
|
+
* `CHDERR_FILE_NOT_WRITABLE`.
|
64
|
+
*/
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Document-class: CHD::UnsupportedError
|
68
|
+
*
|
69
|
+
* The file version/format is not supported.
|
70
|
+
*
|
71
|
+
* It is mapped to the following libchdr errors:
|
72
|
+
* `CHDERR_UNSUPPORTED_VERSION`, `CHDERR_UNSUPPORTED_FORMAT`.
|
73
|
+
*/
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Document-class: CHD::ParentRequiredError
|
77
|
+
*
|
78
|
+
* The a parent file is required by this CHD, as it only contains
|
79
|
+
* partial data.
|
80
|
+
*
|
81
|
+
* It is mapped to the following libchdr error:
|
82
|
+
* `CHDERR_REQUIRES_PARENT`.
|
83
|
+
*/
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Document-class: CHD::ParentInvalidError
|
87
|
+
*
|
88
|
+
* The provided parent file doesn't match the expected one
|
89
|
+
* (digest hash differs).
|
90
|
+
*
|
91
|
+
* It is mapped to the following libchdr error:
|
92
|
+
* `CHDERR_INVALID_PARENT`.
|
93
|
+
*/
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
#ifndef ARRAY_SIZE
|
98
|
+
#define ARRAY_SIZE(arr) \
|
99
|
+
(sizeof(arr) / sizeof((arr)[0]) \
|
100
|
+
+ sizeof(typeof(int[1 - 2 * \
|
101
|
+
!!__builtin_types_compatible_p(typeof(arr), \
|
102
|
+
typeof(&arr[0]))])) * 0)
|
103
|
+
#endif
|
104
|
+
|
105
|
+
#ifndef CHD_METATADATA_BUFFER_MAXSIZE
|
106
|
+
#define CHD_METATADATA_BUFFER_MAXSIZE 256
|
107
|
+
#endif
|
108
|
+
|
109
|
+
|
110
|
+
#if SIZEOF_INT == SIZEOF_INT32_T
|
111
|
+
#if SIZEOF_INT == SIZEOF_VALUE
|
112
|
+
#define VALUE_TO_UINT32(value) RB_NUM2UINT(value)
|
113
|
+
#else
|
114
|
+
#define VALUE_TO_UINT32(value) RB_FIX2UINT(value)
|
115
|
+
#endif
|
116
|
+
#elif SIZEOF_LONG == SIZEOF_INT32_T
|
117
|
+
#if SIZEOF_LONG == SIZEOF_VALUE
|
118
|
+
#define VALUE_TO_UINT32(value) RB_NUM2ULONG(value)
|
119
|
+
#else
|
120
|
+
#define VALUE_TO_UINT32(value) RB_FIX2ULONG(value)
|
121
|
+
#endif
|
122
|
+
#else
|
123
|
+
#error "unable to establish conversion from VALUE to uint32_t"
|
124
|
+
#endif
|
125
|
+
|
126
|
+
#define chd_rb_get_typeddata(chd, obj) \
|
127
|
+
TypedData_Get_Struct(obj, struct chd_rb_data, &chd_data_type, chd)
|
128
|
+
|
129
|
+
|
130
|
+
struct chd_rb_data {
|
131
|
+
#define CHD_RB_DATA_INITIALIZED 0x01
|
132
|
+
#define CHD_RB_DATA_OPENED 0x02
|
133
|
+
#define CHD_RB_DATA_PRECACHED 0x04
|
134
|
+
int flags;
|
135
|
+
chd_file *file;
|
136
|
+
const chd_header *header;
|
137
|
+
uint8_t *cached_hunk;
|
138
|
+
int cached_hunkidx;
|
139
|
+
int units_per_hunk;
|
140
|
+
struct {
|
141
|
+
VALUE header;
|
142
|
+
} value;
|
143
|
+
};
|
144
|
+
|
145
|
+
static void chd_rb_data_type_free(void *data) {
|
146
|
+
struct chd_rb_data *chd = data;
|
147
|
+
if (chd->file) {
|
148
|
+
chd_close(chd->file);
|
149
|
+
}
|
150
|
+
if (chd->cached_hunk) {
|
151
|
+
free(chd->cached_hunk);
|
152
|
+
}
|
153
|
+
free(data);
|
154
|
+
}
|
155
|
+
static size_t chd_rb_data_type_size(const void *data) {
|
156
|
+
const struct chd_rb_data *chd = data;
|
157
|
+
size_t size = sizeof(struct chd_rb_data);
|
158
|
+
|
159
|
+
if (chd->cached_hunk)
|
160
|
+
size += chd->header->hunkbytes;
|
161
|
+
|
162
|
+
return size;
|
163
|
+
}
|
164
|
+
static void chd_rb_data_type_mark(void *data) {
|
165
|
+
struct chd_rb_data *chd = data;
|
166
|
+
rb_gc_mark_locations((VALUE *)(&chd->value),
|
167
|
+
(VALUE *)(((char*)(&chd->value) + sizeof(chd->value))));
|
168
|
+
}
|
169
|
+
|
170
|
+
static rb_data_type_t chd_data_type = {
|
171
|
+
.wrap_struct_name = "chd",
|
172
|
+
.function = { .dmark = chd_rb_data_type_mark,
|
173
|
+
.dfree = chd_rb_data_type_free,
|
174
|
+
.dsize = chd_rb_data_type_size, },
|
175
|
+
.data = NULL,
|
176
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
177
|
+
};
|
178
|
+
|
179
|
+
|
180
|
+
static VALUE cCHD = Qundef;
|
181
|
+
static VALUE eCHDError = Qundef;
|
182
|
+
static VALUE eCHDNotSupportedError = Qundef;
|
183
|
+
static VALUE eCHDIOError = Qundef;
|
184
|
+
static VALUE eCHDDataError = Qundef;
|
185
|
+
static VALUE eCHDNotFoundError = Qundef;
|
186
|
+
static VALUE eCHDNotWritableError = Qundef;
|
187
|
+
static VALUE eCHDUnsupportedError = Qundef;
|
188
|
+
static VALUE eCHDParentRequiredError = Qundef;
|
189
|
+
static VALUE eCHDParentInvalidError = Qundef;
|
190
|
+
|
191
|
+
static ID id_parent;
|
192
|
+
static ID id_version;
|
193
|
+
static ID id_compression;
|
194
|
+
static ID id_md5;
|
195
|
+
static ID id_sha1;
|
196
|
+
static ID id_sha1_raw;
|
197
|
+
static ID id_hunk_bytes;
|
198
|
+
static ID id_hunk_count;
|
199
|
+
static ID id_unit_bytes;
|
200
|
+
static ID id_unit_count;
|
201
|
+
static ID id_logical_bytes;
|
202
|
+
|
203
|
+
|
204
|
+
static VALUE chd_m_close(VALUE self);
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
static VALUE
|
209
|
+
chd_rb_alloc(VALUE klass)
|
210
|
+
{
|
211
|
+
struct chd_rb_data *chd;
|
212
|
+
VALUE obj = TypedData_Make_Struct(cCHD, struct chd_rb_data,
|
213
|
+
&chd_data_type, chd);
|
214
|
+
chd->value.header = Qnil;
|
215
|
+
return obj;
|
216
|
+
}
|
217
|
+
|
218
|
+
|
219
|
+
static void
|
220
|
+
chd_rb_ensure_initialized(struct chd_rb_data *chd)
|
221
|
+
{
|
222
|
+
if (! (chd->flags & CHD_RB_DATA_INITIALIZED)) {
|
223
|
+
rb_bug("uninitialized instance");
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
static void
|
228
|
+
chd_rb_ensure_opened(struct chd_rb_data *chd)
|
229
|
+
{
|
230
|
+
if (! (chd->flags & CHD_RB_DATA_OPENED)) {
|
231
|
+
rb_raise(eCHDError, "closed");
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
static void
|
236
|
+
chd_rb_raise_if_error(chd_error err) {
|
237
|
+
switch(err) {
|
238
|
+
/* No error */
|
239
|
+
case CHDERR_NONE:
|
240
|
+
return;
|
241
|
+
|
242
|
+
/* Out of memory */
|
243
|
+
case CHDERR_OUT_OF_MEMORY:
|
244
|
+
rb_raise(rb_eNoMemError, "out of memory (libchdr)");
|
245
|
+
|
246
|
+
/* Argument Error */
|
247
|
+
case CHDERR_INVALID_PARAMETER:
|
248
|
+
rb_raise(rb_eArgError, "invalid parameter");
|
249
|
+
|
250
|
+
/* Invalid */
|
251
|
+
case CHDERR_INVALID_FILE:
|
252
|
+
case CHDERR_INVALID_DATA:
|
253
|
+
rb_raise(eCHDDataError, "invalid data");
|
254
|
+
|
255
|
+
/* File */
|
256
|
+
case CHDERR_FILE_NOT_FOUND:
|
257
|
+
rb_raise(eCHDNotFoundError, "file not found");
|
258
|
+
case CHDERR_FILE_NOT_WRITEABLE:
|
259
|
+
rb_raise(eCHDNotWritableError, "CHD is read-only");
|
260
|
+
|
261
|
+
/* Unsupported */
|
262
|
+
case CHDERR_UNSUPPORTED_VERSION:
|
263
|
+
case CHDERR_UNSUPPORTED_FORMAT:
|
264
|
+
rb_raise(eCHDUnsupportedError, "%s", chd_error_string(err));
|
265
|
+
|
266
|
+
/* Parent required */
|
267
|
+
case CHDERR_REQUIRES_PARENT:
|
268
|
+
rb_raise(eCHDParentRequiredError, "parent CHD is required");
|
269
|
+
case CHDERR_INVALID_PARENT:
|
270
|
+
rb_raise(eCHDParentInvalidError, "invalid parent (checksum mismatch)");
|
271
|
+
|
272
|
+
/* I/O */
|
273
|
+
case CHDERR_READ_ERROR:
|
274
|
+
case CHDERR_WRITE_ERROR:
|
275
|
+
case CHDERR_CODEC_ERROR:
|
276
|
+
case CHDERR_HUNK_OUT_OF_RANGE:
|
277
|
+
case CHDERR_DECOMPRESSION_ERROR:
|
278
|
+
case CHDERR_COMPRESSION_ERROR:
|
279
|
+
rb_raise(eCHDIOError, "%s", chd_error_string(err));
|
280
|
+
|
281
|
+
/* Not Supported */
|
282
|
+
case CHDERR_NOT_SUPPORTED:
|
283
|
+
rb_raise(eCHDNotSupportedError, "%s", chd_error_string(err));
|
284
|
+
|
285
|
+
/* Should be hidden to user */
|
286
|
+
case CHDERR_METADATA_NOT_FOUND:
|
287
|
+
rb_bug("the <%s> should have been hidden", chd_error_string(err));
|
288
|
+
|
289
|
+
/* Not used in libchdr code (as of 2022-01-01) */
|
290
|
+
case CHDERR_INVALID_METADATA:
|
291
|
+
case CHDERR_INVALID_METADATA_SIZE:
|
292
|
+
case CHDERR_INVALID_STATE:
|
293
|
+
case CHDERR_CANT_CREATE_FILE:
|
294
|
+
case CHDERR_CANT_VERIFY:
|
295
|
+
case CHDERR_VERIFY_INCOMPLETE:
|
296
|
+
case CHDERR_OPERATION_PENDING:
|
297
|
+
case CHDERR_NO_ASYNC_OPERATION:
|
298
|
+
case CHDERR_NO_INTERFACE:
|
299
|
+
rb_bug("the <%s> is not handled", chd_error_string(err));
|
300
|
+
|
301
|
+
/* Not defined in libchdr code (as of 2022-01-01) */
|
302
|
+
default:
|
303
|
+
rb_bug("the <%s> is unknown", chd_error_string(err));
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
|
308
|
+
static VALUE
|
309
|
+
chd_rb_header(const chd_header *header) {
|
310
|
+
#define get_chd_hash(buffer, bytes) \
|
311
|
+
rb_str_freeze(rb_str_new((char *)(buffer), bytes))
|
312
|
+
|
313
|
+
VALUE hdr = rb_hash_new();
|
314
|
+
VALUE parent = rb_hash_new();
|
315
|
+
|
316
|
+
rb_hash_aset(hdr, ID2SYM(id_version), ULONG2NUM(header->version));
|
317
|
+
rb_hash_aset(hdr, ID2SYM(id_hunk_bytes), ULONG2NUM(header->hunkbytes));
|
318
|
+
rb_hash_aset(hdr, ID2SYM(id_hunk_count), ULONG2NUM(header->totalhunks));
|
319
|
+
rb_hash_aset(hdr, ID2SYM(id_unit_bytes), ULONG2NUM(header->unitbytes));
|
320
|
+
rb_hash_aset(hdr, ID2SYM(id_unit_count), ULONG2NUM(header->unitcount));
|
321
|
+
rb_hash_aset(hdr, ID2SYM(id_logical_bytes),ULONG2NUM(header->logicalbytes));
|
322
|
+
|
323
|
+
if (header->version >= 3) {
|
324
|
+
rb_hash_aset(hdr, ID2SYM(id_sha1),
|
325
|
+
get_chd_hash(header->sha1, CHD_SHA1_BYTES));
|
326
|
+
if (header->flags & CHDFLAGS_HAS_PARENT) {
|
327
|
+
rb_hash_aset(parent, ID2SYM(id_sha1),
|
328
|
+
get_chd_hash(header->parentsha1, CHD_SHA1_BYTES));
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
if (header->version <= 3) {
|
333
|
+
rb_hash_aset(hdr, ID2SYM(id_md5),
|
334
|
+
get_chd_hash(header->md5, CHD_MD5_BYTES));
|
335
|
+
if (header->flags & CHDFLAGS_HAS_PARENT) {
|
336
|
+
rb_hash_aset(parent, ID2SYM(id_md5),
|
337
|
+
get_chd_hash(header->parentmd5, CHD_MD5_BYTES));
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
if (header->version >= 4) {
|
342
|
+
rb_hash_aset(hdr, ID2SYM(id_sha1_raw),
|
343
|
+
get_chd_hash(header->rawsha1, CHD_SHA1_BYTES));
|
344
|
+
}
|
345
|
+
|
346
|
+
if (header->version >= 5) {
|
347
|
+
VALUE compression = rb_ary_new();
|
348
|
+
for (int i = 0 ; i < ARRAY_SIZE(header->compression) ; i++) {
|
349
|
+
if (header->compression[i] == CHD_CODEC_NONE)
|
350
|
+
continue;
|
351
|
+
|
352
|
+
char str[sizeof(uint32_t)];
|
353
|
+
rb_integer_pack(ULONG2NUM(header->compression[i]),
|
354
|
+
str, 1, sizeof(uint32_t),
|
355
|
+
0, INTEGER_PACK_BIG_ENDIAN);
|
356
|
+
rb_ary_push(compression, rb_str_new(str, sizeof(str)));
|
357
|
+
}
|
358
|
+
rb_hash_aset(hdr, ID2SYM(id_compression), rb_ary_freeze(compression));
|
359
|
+
}
|
360
|
+
|
361
|
+
if (! RHASH_EMPTY_P(parent)) {
|
362
|
+
rb_hash_aset(hdr, ID2SYM(id_parent), rb_hash_freeze(parent));
|
363
|
+
}
|
364
|
+
|
365
|
+
return rb_hash_freeze(hdr);
|
366
|
+
#undef get_chd_hash
|
367
|
+
}
|
368
|
+
|
369
|
+
|
370
|
+
/**
|
371
|
+
* (see CHD#initialize)
|
372
|
+
*/
|
373
|
+
static VALUE
|
374
|
+
chd_s_new(int argc, VALUE *argv, VALUE klass)
|
375
|
+
{
|
376
|
+
return rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS);
|
377
|
+
}
|
378
|
+
|
379
|
+
|
380
|
+
/**
|
381
|
+
* Open a CHD file.
|
382
|
+
*
|
383
|
+
* With no associated block {CHD.open} is synonym for {CHD.new}.
|
384
|
+
* If the optional code block is given, it will be passed the opened CHD file
|
385
|
+
* as an argument and the CHD object will automatically be closed when
|
386
|
+
* the block terminates. The value of the block will be returned.
|
387
|
+
*
|
388
|
+
* @note Only the read-only mode ({RDONLY}) is currently supported.
|
389
|
+
*
|
390
|
+
* @overload open(file, mode=RDONLY, parent: nil)
|
391
|
+
* @param file [String, IO] path-string or open IO on the CHD file
|
392
|
+
* @param mode [Integer] opening mode ({RDONLY} or {RDWR})
|
393
|
+
* @param parent [String, IO] path-string or open IO on the CHD parent file.
|
394
|
+
*
|
395
|
+
* @yield [chd] the opened CHD file
|
396
|
+
*
|
397
|
+
* @example
|
398
|
+
* CHD.open('file.chd') do |chd|
|
399
|
+
* puts chd.metadata
|
400
|
+
* end
|
401
|
+
*/
|
402
|
+
static VALUE
|
403
|
+
chd_s_open(int argc, VALUE *argv, VALUE klass)
|
404
|
+
{
|
405
|
+
VALUE chd;
|
406
|
+
|
407
|
+
chd = rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS);
|
408
|
+
|
409
|
+
if (rb_block_given_p()) {
|
410
|
+
return rb_ensure(rb_yield, chd, chd_m_close, chd);
|
411
|
+
}
|
412
|
+
|
413
|
+
return chd;
|
414
|
+
}
|
415
|
+
|
416
|
+
|
417
|
+
/**
|
418
|
+
* Retrieve the header from a CHD file.
|
419
|
+
*
|
420
|
+
* For a detailed description of the header, see {#header}.
|
421
|
+
*
|
422
|
+
* @param filename [String] path to CHD file
|
423
|
+
*
|
424
|
+
* @return [Hash{Symbol => Object}]
|
425
|
+
*/
|
426
|
+
static VALUE
|
427
|
+
chd_s_header(VALUE klass, VALUE filename)
|
428
|
+
{
|
429
|
+
chd_header header;
|
430
|
+
chd_error err;
|
431
|
+
|
432
|
+
err = chd_read_header(StringValueCStr(filename), &header);
|
433
|
+
chd_rb_raise_if_error(err);
|
434
|
+
|
435
|
+
return chd_rb_header(&header);
|
436
|
+
}
|
437
|
+
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Create a new access to a CHD file.
|
441
|
+
*
|
442
|
+
* @note Only the read-only mode ({RDONLY}) is currently supported.
|
443
|
+
*
|
444
|
+
* @overload initialize(file, mode=RDONLY, parent: nil)
|
445
|
+
* @param file [String, IO] path-string or open IO on the CHD file
|
446
|
+
* @param mode [Integer] opening mode ({RDONLY} or {RDWR})
|
447
|
+
* @param parent [String, IO] path-string or open IO on the CHD parent file.
|
448
|
+
*
|
449
|
+
* @return [CHD]
|
450
|
+
*/
|
451
|
+
static VALUE
|
452
|
+
chd_m_initialize(int argc, VALUE *argv, VALUE self)
|
453
|
+
{
|
454
|
+
VALUE file, mode, opts;
|
455
|
+
ID kwargs_id[1] = { id_parent };
|
456
|
+
VALUE kwargs [1];
|
457
|
+
|
458
|
+
// Retrieve typed data
|
459
|
+
struct chd_rb_data *chd;
|
460
|
+
chd_rb_get_typeddata(chd, self);
|
461
|
+
|
462
|
+
// Sanity check
|
463
|
+
if (chd->flags & CHD_RB_DATA_INITIALIZED) {
|
464
|
+
rb_warn("%"PRIsVALUE" refusing to initialize same instance twice",
|
465
|
+
rb_obj_as_string(cCHD));
|
466
|
+
return Qnil;
|
467
|
+
}
|
468
|
+
|
469
|
+
// Retrieve arguments
|
470
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "11:",
|
471
|
+
&file, &mode, &opts);
|
472
|
+
rb_get_kwargs(opts, kwargs_id, 0, 2, kwargs);
|
473
|
+
|
474
|
+
// If mode not specified, default to read-only
|
475
|
+
if (NIL_P(mode)) {
|
476
|
+
mode = INT2FIX(CHD_OPEN_READ);
|
477
|
+
}
|
478
|
+
|
479
|
+
// If given retrieve parent chd file
|
480
|
+
chd_file *parent = NULL;
|
481
|
+
if ((kwargs[0] != Qundef) && (kwargs[0] != Qnil)) {
|
482
|
+
if (! RTEST(rb_obj_is_kind_of(kwargs[0], cCHD))) {
|
483
|
+
rb_raise(rb_eArgError, "parent must be a kind of %"PRIsVALUE,
|
484
|
+
rb_obj_as_string(cCHD));
|
485
|
+
}
|
486
|
+
struct chd_rb_data *chd_parent;
|
487
|
+
chd_rb_get_typeddata(chd_parent, self);
|
488
|
+
parent = chd_parent->file;
|
489
|
+
}
|
490
|
+
|
491
|
+
// Open CHD
|
492
|
+
chd_error err = CHDERR_NONE;
|
493
|
+
if (RTEST(rb_obj_is_kind_of(file, rb_cIO))) {
|
494
|
+
rb_io_t *fptr;
|
495
|
+
GetOpenFile(file, fptr);
|
496
|
+
|
497
|
+
err = chd_open_file(rb_io_stdio_file(fptr),
|
498
|
+
FIX2INT(mode), parent, &chd->file);
|
499
|
+
} else {
|
500
|
+
err = chd_open(StringValueCStr(file),
|
501
|
+
FIX2INT(mode), parent, &chd->file);
|
502
|
+
}
|
503
|
+
chd_rb_raise_if_error(err);
|
504
|
+
|
505
|
+
// Retrieve header and hunkbytes
|
506
|
+
chd->header = chd_get_header(chd->file);
|
507
|
+
chd->units_per_hunk = chd->header->hunkbytes / chd->header->unitbytes;
|
508
|
+
if (chd->header->hunkbytes % chd->header->unitbytes) {
|
509
|
+
chd_close(chd->file);
|
510
|
+
rb_raise(eCHDDataError, "CHD hunk is not a multiple of unit");
|
511
|
+
}
|
512
|
+
|
513
|
+
// Allocate cache
|
514
|
+
chd->cached_hunk = malloc(chd->header->hunkbytes);
|
515
|
+
chd->cached_hunkidx = -1;
|
516
|
+
if (chd->cached_hunk == NULL) {
|
517
|
+
chd_close(chd->file);
|
518
|
+
rb_raise(rb_eNoMemError, "out of memory (hunk cache)");
|
519
|
+
}
|
520
|
+
|
521
|
+
// Mark as initialized and opened
|
522
|
+
chd->flags = CHD_RB_DATA_INITIALIZED | CHD_RB_DATA_OPENED;
|
523
|
+
|
524
|
+
return Qnil;
|
525
|
+
}
|
526
|
+
|
527
|
+
|
528
|
+
/**
|
529
|
+
* Pre-cache the whole CHD in memory.
|
530
|
+
*
|
531
|
+
* @note
|
532
|
+
* * It is not necessary to enable pre-cache just to improved
|
533
|
+
* consecutive partial-read as the current hunk is always cached.
|
534
|
+
* * Once enabled, there is no way to remove the cache.
|
535
|
+
*
|
536
|
+
* @return [self]
|
537
|
+
*/
|
538
|
+
static VALUE
|
539
|
+
chd_m_precache(VALUE self) {
|
540
|
+
// Retrieve typed data
|
541
|
+
struct chd_rb_data *chd;
|
542
|
+
chd_rb_get_typeddata(chd, self);
|
543
|
+
chd_rb_ensure_initialized(chd);
|
544
|
+
chd_rb_ensure_opened(chd);
|
545
|
+
|
546
|
+
chd_error err = chd_precache(chd->file);
|
547
|
+
chd_rb_raise_if_error(err);
|
548
|
+
chd->flags |= CHD_RB_DATA_PRECACHED;
|
549
|
+
|
550
|
+
return self;
|
551
|
+
}
|
552
|
+
|
553
|
+
|
554
|
+
/**
|
555
|
+
* Has the CHD been pre-cached?
|
556
|
+
*/
|
557
|
+
static VALUE
|
558
|
+
chd_m_precached_p(VALUE self) {
|
559
|
+
// Retrieve typed data
|
560
|
+
struct chd_rb_data *chd;
|
561
|
+
chd_rb_get_typeddata(chd, self);
|
562
|
+
chd_rb_ensure_initialized(chd);
|
563
|
+
chd_rb_ensure_opened(chd);
|
564
|
+
|
565
|
+
return (chd->flags & CHD_RB_DATA_PRECACHED) ? Qtrue : Qfalse;
|
566
|
+
}
|
567
|
+
|
568
|
+
|
569
|
+
/**
|
570
|
+
* Return the CHD header.
|
571
|
+
*
|
572
|
+
* The header contains information about:
|
573
|
+
* * version
|
574
|
+
* * compression used
|
575
|
+
* * digest (sha1 or md5) for the file and the parent
|
576
|
+
* * hunk and unit (size and count)
|
577
|
+
*
|
578
|
+
* @return [Hash{Symbol => Object}]
|
579
|
+
*/
|
580
|
+
static VALUE
|
581
|
+
chd_m_header(VALUE self) {
|
582
|
+
// Retrieve typed data
|
583
|
+
struct chd_rb_data *chd;
|
584
|
+
chd_rb_get_typeddata(chd, self);
|
585
|
+
chd_rb_ensure_initialized(chd);
|
586
|
+
chd_rb_ensure_opened(chd);
|
587
|
+
|
588
|
+
if (NIL_P(chd->value.header)) {
|
589
|
+
chd->value.header = rb_obj_freeze(chd_rb_header(chd->header));
|
590
|
+
}
|
591
|
+
|
592
|
+
return chd->value.header;
|
593
|
+
}
|
594
|
+
|
595
|
+
|
596
|
+
/**
|
597
|
+
* Retrieve a single metadata.
|
598
|
+
*
|
599
|
+
* If specified the metadata tag is a 4-character symbol, some commonly used
|
600
|
+
* are `:GDDD`, `:IDNT`, `:'KEY '`, `:'CIS '`, `:CHTR`, `:CHT2`, `:CHGD`,
|
601
|
+
* `:AVAV`, `:AVLD`. Note the use of single-quote to include a white-space
|
602
|
+
* in some of the tag.
|
603
|
+
*
|
604
|
+
* @overload get_metadata(index=0, tag=nil)
|
605
|
+
* @param index [Integer] index from which to lookup for metadata
|
606
|
+
* @param tag [Symbol, nil] tag of the metadata to lookup
|
607
|
+
* (using nil as a wildcard)
|
608
|
+
*
|
609
|
+
* @return [Array(String, Integer, Symbol)]
|
610
|
+
* @return [nil] if do metadata found
|
611
|
+
*/
|
612
|
+
static VALUE
|
613
|
+
chd_m_get_metadata(int argc, VALUE *argv, VALUE self)
|
614
|
+
{
|
615
|
+
VALUE tag, index;
|
616
|
+
|
617
|
+
// Retrieve arguments
|
618
|
+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "02",
|
619
|
+
&index, &tag);
|
620
|
+
|
621
|
+
if (! NIL_P(index)) {
|
622
|
+
rb_check_type(index, T_FIXNUM);
|
623
|
+
}
|
624
|
+
|
625
|
+
if (! NIL_P(tag)) {
|
626
|
+
rb_warn("%"PRIsVALUE" refusing to initialize same instance twice",
|
627
|
+
rb_obj_as_string(tag));
|
628
|
+
rb_check_type(tag, T_SYMBOL);
|
629
|
+
tag = rb_sym2str(tag);
|
630
|
+
if (RSTRING_LEN(tag) != 4) {
|
631
|
+
rb_raise(rb_eArgError, "tag must be a 4-char symbol");
|
632
|
+
}
|
633
|
+
}
|
634
|
+
|
635
|
+
// Retrieve typed data
|
636
|
+
struct chd_rb_data *chd;
|
637
|
+
chd_rb_get_typeddata(chd, self);
|
638
|
+
chd_rb_ensure_initialized(chd);
|
639
|
+
chd_rb_ensure_opened(chd);
|
640
|
+
|
641
|
+
// Perform query
|
642
|
+
char buffer[CHD_METATADATA_BUFFER_MAXSIZE] = { 0 };
|
643
|
+
uint32_t buflen = sizeof(buffer);
|
644
|
+
uint32_t resultlen = 0;
|
645
|
+
uint32_t resulttag = 0;
|
646
|
+
uint8_t resultflags = 0;
|
647
|
+
uint32_t searchindex = 0;
|
648
|
+
uint32_t searchtag = CHDMETATAG_WILDCARD;
|
649
|
+
|
650
|
+
if (! NIL_P(index)) {
|
651
|
+
searchindex = FIX2INT(index);
|
652
|
+
}
|
653
|
+
|
654
|
+
if (! NIL_P(tag)) {
|
655
|
+
char *tag_str = RSTRING_PTR(tag);
|
656
|
+
char *searchtag_str = (char *)&searchtag;
|
657
|
+
searchtag_str[0] = tag_str[3];
|
658
|
+
searchtag_str[1] = tag_str[2];
|
659
|
+
searchtag_str[2] = tag_str[1];
|
660
|
+
searchtag_str[3] = tag_str[0];
|
661
|
+
}
|
662
|
+
|
663
|
+
chd_error err;
|
664
|
+
err = chd_get_metadata(chd->file,
|
665
|
+
searchtag, searchindex,
|
666
|
+
buffer, buflen,
|
667
|
+
&resultlen, &resulttag, &resultflags);
|
668
|
+
|
669
|
+
// return nil on not found, otherwise raise exception
|
670
|
+
if (err == CHDERR_METADATA_NOT_FOUND)
|
671
|
+
return Qnil;
|
672
|
+
chd_rb_raise_if_error(err);
|
673
|
+
|
674
|
+
// Sanity check on buffer length
|
675
|
+
if (resultlen > buflen) {
|
676
|
+
rb_bug("decoding metadata buffer size (%d) is too small (got: %d)",
|
677
|
+
(int)sizeof(buffer), resultlen);
|
678
|
+
}
|
679
|
+
|
680
|
+
// Assume it's ascii 8-bit text encoded, remove last null-char
|
681
|
+
if ((resultlen > 0) &&
|
682
|
+
(buffer[resultlen-1] == '\0') &&
|
683
|
+
(strchr(buffer, '\0') == &buffer[resultlen-1])) {
|
684
|
+
resultlen -= 1;
|
685
|
+
}
|
686
|
+
|
687
|
+
// Returns result
|
688
|
+
char str[sizeof(uint32_t)];
|
689
|
+
rb_integer_pack(ULONG2NUM(resulttag), str, 1, sizeof(uint32_t), 0,
|
690
|
+
INTEGER_PACK_BIG_ENDIAN);
|
691
|
+
|
692
|
+
VALUE res[] = { rb_str_new(buffer, resultlen),
|
693
|
+
INT2FIX(resultflags),
|
694
|
+
rb_to_symbol(rb_str_new(str, sizeof(str)))
|
695
|
+
};
|
696
|
+
|
697
|
+
return rb_ary_new_from_values(ARRAY_SIZE(res), res);
|
698
|
+
}
|
699
|
+
|
700
|
+
|
701
|
+
/**
|
702
|
+
* Retrieve all the metadata.
|
703
|
+
*
|
704
|
+
* @return [Array<Array(String, Integer, Symbol)>]
|
705
|
+
*/
|
706
|
+
static VALUE
|
707
|
+
chd_m_metadata(VALUE self) {
|
708
|
+
VALUE list = rb_ary_new();
|
709
|
+
|
710
|
+
for (int i = 0 ; ; i++) {
|
711
|
+
VALUE md = chd_m_get_metadata(1, (VALUE []) { INT2FIX(i) }, self);
|
712
|
+
if (NIL_P(md))
|
713
|
+
break;
|
714
|
+
rb_ary_push(list, md);
|
715
|
+
}
|
716
|
+
return list;
|
717
|
+
}
|
718
|
+
|
719
|
+
|
720
|
+
/**
|
721
|
+
* Read a CHD hunk.
|
722
|
+
*
|
723
|
+
* @param idx [Integer] hunk index (start at 0)
|
724
|
+
*
|
725
|
+
* @raise [RangeError] if the requested hunk doesn't exists
|
726
|
+
*
|
727
|
+
* @return [String]
|
728
|
+
*/
|
729
|
+
static VALUE
|
730
|
+
chd_m_read_hunk(VALUE self, VALUE idx) {
|
731
|
+
// Retrieve typed data
|
732
|
+
struct chd_rb_data *chd;
|
733
|
+
chd_rb_get_typeddata(chd, self);
|
734
|
+
chd_rb_ensure_initialized(chd);
|
735
|
+
chd_rb_ensure_opened(chd);
|
736
|
+
|
737
|
+
uint32_t hunkidx = VALUE_TO_UINT32(idx);
|
738
|
+
if ((hunkidx < 0) || (hunkidx >= chd->header->totalhunks)) {
|
739
|
+
rb_raise(rb_eRangeError, "hunk index (%d) is out of range (%d..%d)",
|
740
|
+
hunkidx, 0, chd->header->totalhunks - 1);
|
741
|
+
}
|
742
|
+
|
743
|
+
VALUE strdata = rb_str_buf_new(chd->header->hunkbytes);
|
744
|
+
char *buffer = RSTRING_PTR(strdata);
|
745
|
+
|
746
|
+
chd_error err = chd_read(chd->file, hunkidx, buffer);
|
747
|
+
chd_rb_raise_if_error(err);
|
748
|
+
|
749
|
+
rb_str_set_len(strdata, chd->header->hunkbytes);
|
750
|
+
return strdata;
|
751
|
+
}
|
752
|
+
|
753
|
+
|
754
|
+
/**
|
755
|
+
* Read a CHD unit.
|
756
|
+
*
|
757
|
+
* @param idx [Integer] unit index (start at 0)
|
758
|
+
*
|
759
|
+
* @raise [RangeError] if the requested unit doesn't exists
|
760
|
+
*
|
761
|
+
* @return [String]
|
762
|
+
*/
|
763
|
+
static VALUE
|
764
|
+
chd_m_read_unit(VALUE self, VALUE idx) {
|
765
|
+
// Retrieve typed data
|
766
|
+
struct chd_rb_data *chd;
|
767
|
+
chd_rb_get_typeddata(chd, self);
|
768
|
+
chd_rb_ensure_initialized(chd);
|
769
|
+
chd_rb_ensure_opened(chd);
|
770
|
+
|
771
|
+
const uint32_t hunkbytes = chd->header->hunkbytes;
|
772
|
+
const uint32_t unitbytes = chd->header->unitbytes;
|
773
|
+
const uint32_t unitidx = VALUE_TO_UINT32(idx);
|
774
|
+
const uint32_t hunkidx = unitidx / chd->units_per_hunk;
|
775
|
+
const size_t offset = (unitidx % chd->units_per_hunk) * unitbytes;
|
776
|
+
|
777
|
+
if (hunkidx != chd->cached_hunkidx) {
|
778
|
+
chd_error err = chd_read(chd->file, hunkidx, chd->cached_hunk);
|
779
|
+
chd->cached_hunkidx = (err == CHDERR_NONE) ? hunkidx : -1;
|
780
|
+
chd_rb_raise_if_error(err);
|
781
|
+
}
|
782
|
+
|
783
|
+
return rb_str_new((char *) &chd->cached_hunk[offset], unitbytes);
|
784
|
+
}
|
785
|
+
|
786
|
+
|
787
|
+
/**
|
788
|
+
* Read bytes of data.
|
789
|
+
*
|
790
|
+
* @param offset [Integer] offset from which reading bytes start
|
791
|
+
* @param size [Integer] number of bytes to read
|
792
|
+
*
|
793
|
+
* @raise [IOError] if the requested data is not available
|
794
|
+
*
|
795
|
+
* @return [String]
|
796
|
+
*/
|
797
|
+
static VALUE
|
798
|
+
chd_m_read_bytes(VALUE self, VALUE offset, VALUE size) {
|
799
|
+
// Retrieve typed data
|
800
|
+
struct chd_rb_data *chd;
|
801
|
+
chd_rb_get_typeddata(chd, self);
|
802
|
+
chd_rb_ensure_initialized(chd);
|
803
|
+
chd_rb_ensure_opened(chd);
|
804
|
+
|
805
|
+
const uint32_t _offset = VALUE_TO_UINT32(offset);
|
806
|
+
const uint32_t _size = VALUE_TO_UINT32(size);
|
807
|
+
const uint32_t hunkbytes = chd->header->hunkbytes;
|
808
|
+
const uint32_t hunkidx_first = _offset / hunkbytes;
|
809
|
+
const uint32_t hunkidx_last = (_offset + _size - 1) / hunkbytes;
|
810
|
+
const VALUE strdata = rb_str_buf_new(_size);
|
811
|
+
char *buffer = RSTRING_PTR(strdata);
|
812
|
+
|
813
|
+
for (uint32_t hunkidx = hunkidx_first; hunkidx <= hunkidx_last; hunkidx++) {
|
814
|
+
uint32_t startoffs = (hunkidx == hunkidx_first)
|
815
|
+
? (_offset % hunkbytes)
|
816
|
+
: 0;
|
817
|
+
uint32_t endoffs = (hunkidx == hunkidx_last)
|
818
|
+
? ((_offset + _size - 1) % hunkbytes)
|
819
|
+
: (hunkbytes - 1);
|
820
|
+
size_t chunksize = endoffs + 1 - startoffs;
|
821
|
+
|
822
|
+
// if it's a full block, just read directly from disk
|
823
|
+
// (unless it's the cached hunk)
|
824
|
+
if ((startoffs == 0 ) &&
|
825
|
+
(endoffs == (hunkbytes - 1) ) &&
|
826
|
+
(hunkidx != chd->cached_hunkidx)) {
|
827
|
+
chd_error err = chd_read(chd->file, hunkidx, buffer);
|
828
|
+
chd_rb_raise_if_error(err);
|
829
|
+
}
|
830
|
+
// otherwise, read from the cache
|
831
|
+
// (and fill the cache if necessary)
|
832
|
+
else {
|
833
|
+
if (hunkidx != chd->cached_hunkidx) {
|
834
|
+
chd_error err = chd_read(chd->file, hunkidx, chd->cached_hunk);
|
835
|
+
chd->cached_hunkidx = (err == CHDERR_NONE) ? hunkidx : -1;
|
836
|
+
chd_rb_raise_if_error(err);
|
837
|
+
}
|
838
|
+
memcpy(buffer, &chd->cached_hunk[startoffs], chunksize);
|
839
|
+
}
|
840
|
+
|
841
|
+
buffer += chunksize;
|
842
|
+
}
|
843
|
+
|
844
|
+
rb_str_set_len(strdata, _size);
|
845
|
+
return strdata;
|
846
|
+
}
|
847
|
+
|
848
|
+
|
849
|
+
/**
|
850
|
+
* Close the file.
|
851
|
+
*
|
852
|
+
* @note Once closed, further operation on this object will result
|
853
|
+
* in an {Error} exception, except for {#close} that will be a no-op.
|
854
|
+
*
|
855
|
+
* @return [nil]
|
856
|
+
*/
|
857
|
+
static VALUE
|
858
|
+
chd_m_close(VALUE self) {
|
859
|
+
// Retrieve typed data
|
860
|
+
struct chd_rb_data *chd;
|
861
|
+
chd_rb_get_typeddata(chd, self);
|
862
|
+
chd_rb_ensure_initialized(chd);
|
863
|
+
|
864
|
+
// If opened
|
865
|
+
if (chd->flags & CHD_RB_DATA_OPENED) {
|
866
|
+
chd_close(chd->file);
|
867
|
+
chd->file = NULL;
|
868
|
+
chd->header = NULL;
|
869
|
+
chd->value.header = Qnil;
|
870
|
+
chd->flags &= ~(CHD_RB_DATA_OPENED | CHD_RB_DATA_PRECACHED);
|
871
|
+
}
|
872
|
+
|
873
|
+
return Qnil;
|
874
|
+
}
|
875
|
+
|
876
|
+
|
877
|
+
/**
|
878
|
+
* Is the file closed?
|
879
|
+
*/
|
880
|
+
static VALUE
|
881
|
+
chd_m_closed_p(VALUE self) {
|
882
|
+
// Retrieve typed data
|
883
|
+
struct chd_rb_data *chd;
|
884
|
+
chd_rb_get_typeddata(chd, self);
|
885
|
+
chd_rb_ensure_initialized(chd);
|
886
|
+
|
887
|
+
return (chd->flags & CHD_RB_DATA_OPENED) ? Qfalse : Qtrue;
|
888
|
+
}
|
889
|
+
|
890
|
+
|
891
|
+
/**
|
892
|
+
* Returns version number.
|
893
|
+
*
|
894
|
+
* @return [String]
|
895
|
+
*/
|
896
|
+
static VALUE
|
897
|
+
chd_m_version(VALUE self) {
|
898
|
+
return rb_hash_lookup(chd_m_header(self), ID2SYM(id_version));
|
899
|
+
}
|
900
|
+
|
901
|
+
/**
|
902
|
+
* Number of bytes in a hunk.
|
903
|
+
*
|
904
|
+
* @return [Integer]
|
905
|
+
*/
|
906
|
+
static VALUE
|
907
|
+
chd_m_hunk_bytes(VALUE self) {
|
908
|
+
return rb_hash_lookup(chd_m_header(self), ID2SYM(id_hunk_bytes));
|
909
|
+
}
|
910
|
+
|
911
|
+
/**
|
912
|
+
* Number of hunks.
|
913
|
+
*
|
914
|
+
* @return [Integer]
|
915
|
+
*/
|
916
|
+
static VALUE
|
917
|
+
chd_m_hunk_count(VALUE self) {
|
918
|
+
return rb_hash_lookup(chd_m_header(self), ID2SYM(id_hunk_count));
|
919
|
+
}
|
920
|
+
|
921
|
+
/**
|
922
|
+
* Number of bytes in a unit
|
923
|
+
*
|
924
|
+
* @return [Integer]
|
925
|
+
*/
|
926
|
+
static VALUE
|
927
|
+
chd_m_unit_bytes(VALUE self) {
|
928
|
+
return rb_hash_lookup(chd_m_header(self), ID2SYM(id_unit_bytes));
|
929
|
+
}
|
930
|
+
|
931
|
+
/**
|
932
|
+
* Number of units.
|
933
|
+
*
|
934
|
+
* @return [Integer]
|
935
|
+
*/
|
936
|
+
static VALUE
|
937
|
+
chd_m_unit_count(VALUE self) {
|
938
|
+
return rb_hash_lookup(chd_m_header(self), ID2SYM(id_unit_count));
|
939
|
+
}
|
940
|
+
|
941
|
+
void Init_core(void) {
|
942
|
+
/* Main classes */
|
943
|
+
cCHD = rb_define_class("CHD", rb_cObject);
|
944
|
+
eCHDError = rb_define_class_under(cCHD, "Error", rb_eStandardError);
|
945
|
+
|
946
|
+
/* Sub errors */
|
947
|
+
eCHDIOError = rb_define_class_under(cCHD,
|
948
|
+
"IOError", eCHDError);
|
949
|
+
eCHDNotSupportedError = rb_define_class_under(cCHD,
|
950
|
+
"NotSupportedError", eCHDError);
|
951
|
+
eCHDDataError = rb_define_class_under(cCHD,
|
952
|
+
"DataError", eCHDError);
|
953
|
+
eCHDNotFoundError = rb_define_class_under(cCHD,
|
954
|
+
"NotFoundError", eCHDError);
|
955
|
+
eCHDNotWritableError = rb_define_class_under(cCHD,
|
956
|
+
"NotWritableError", eCHDError);
|
957
|
+
eCHDUnsupportedError = rb_define_class_under(cCHD,
|
958
|
+
"UnsupportedError", eCHDError);
|
959
|
+
eCHDParentRequiredError = rb_define_class_under(cCHD,
|
960
|
+
"ParentRequiredError", eCHDError);
|
961
|
+
eCHDParentInvalidError = rb_define_class_under(cCHD,
|
962
|
+
"ParentInvalidError", eCHDError);
|
963
|
+
|
964
|
+
/* ID */
|
965
|
+
id_parent = rb_intern("parent");
|
966
|
+
id_version = rb_intern("version");
|
967
|
+
id_compression = rb_intern("compression");
|
968
|
+
id_md5 = rb_intern("md5");
|
969
|
+
id_sha1 = rb_intern("sha1");
|
970
|
+
id_sha1_raw = rb_intern("sha1_raw");
|
971
|
+
id_hunk_bytes = rb_intern("hunk_bytes");
|
972
|
+
id_hunk_count = rb_intern("hunk_count");
|
973
|
+
id_unit_bytes = rb_intern("unit_bytes");
|
974
|
+
id_unit_count = rb_intern("unit_count");
|
975
|
+
id_logical_bytes = rb_intern("logical_bytes");
|
976
|
+
|
977
|
+
/* Constants */
|
978
|
+
/* 1: Read-only mode for opening CHD file. */
|
979
|
+
rb_define_const(cCHD, "RDONLY", INT2FIX(CHD_OPEN_READ));
|
980
|
+
/* 2: Read-write mode for opening CHD file. */
|
981
|
+
rb_define_const(cCHD, "RDWR", INT2FIX(CHD_OPEN_READWRITE));
|
982
|
+
/* 0x01: Indicates that data is checksumed */
|
983
|
+
rb_define_const(cCHD, "METADATA_FLAG_CHECKSUM", INT2FIX(CHD_MDFLAGS_CHECKSUM));
|
984
|
+
|
985
|
+
/* Definitions */
|
986
|
+
rb_define_alloc_func(cCHD, chd_rb_alloc);
|
987
|
+
rb_define_singleton_method(cCHD, "new", chd_s_new, -1);
|
988
|
+
rb_define_singleton_method(cCHD, "header", chd_s_header, 1);
|
989
|
+
rb_define_singleton_method(cCHD, "open", chd_s_open, -1);
|
990
|
+
rb_define_method(cCHD, "initialize", chd_m_initialize, -1);
|
991
|
+
rb_define_method(cCHD, "precache", chd_m_precache, 0);
|
992
|
+
rb_define_method(cCHD, "precached?", chd_m_precached_p, 0);
|
993
|
+
rb_define_method(cCHD, "header", chd_m_header, 0);
|
994
|
+
rb_define_method(cCHD, "get_metadata", chd_m_get_metadata, -1);
|
995
|
+
rb_define_method(cCHD, "metadata", chd_m_metadata, 0);
|
996
|
+
rb_define_method(cCHD, "read_hunk", chd_m_read_hunk, 1);
|
997
|
+
rb_define_method(cCHD, "read_unit", chd_m_read_unit, 1);
|
998
|
+
rb_define_method(cCHD, "read_bytes", chd_m_read_bytes, 2);
|
999
|
+
rb_define_method(cCHD, "close", chd_m_close, 0);
|
1000
|
+
rb_define_method(cCHD, "closed?", chd_m_closed_p, 0);
|
1001
|
+
rb_define_method(cCHD, "version", chd_m_version, 0);
|
1002
|
+
rb_define_method(cCHD, "hunk_bytes", chd_m_hunk_bytes, 0);
|
1003
|
+
rb_define_method(cCHD, "hunk_count", chd_m_hunk_count, 0);
|
1004
|
+
rb_define_method(cCHD, "unit_bytes", chd_m_unit_bytes, 0);
|
1005
|
+
rb_define_method(cCHD, "unit_count", chd_m_unit_count, 0);
|
1006
|
+
}
|
1007
|
+
|
1008
|
+
|