chd 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +30 -0
  3. data/chd.gemspec +29 -0
  4. data/ext/chd.c +1008 -0
  5. data/ext/extconf.rb +60 -0
  6. data/lib/chd/cd.rb +272 -0
  7. data/lib/chd/metadata.rb +196 -0
  8. data/lib/chd/version.rb +4 -0
  9. data/lib/chd.rb +21 -0
  10. data/libchdr/CMakeLists.txt +104 -0
  11. data/libchdr/LICENSE.txt +24 -0
  12. data/libchdr/README.md +7 -0
  13. data/libchdr/deps/lzma-19.00/CMakeLists.txt +33 -0
  14. data/libchdr/deps/lzma-19.00/LICENSE +3 -0
  15. data/libchdr/deps/lzma-19.00/include/7zTypes.h +375 -0
  16. data/libchdr/deps/lzma-19.00/include/Alloc.h +51 -0
  17. data/libchdr/deps/lzma-19.00/include/Bra.h +64 -0
  18. data/libchdr/deps/lzma-19.00/include/Compiler.h +33 -0
  19. data/libchdr/deps/lzma-19.00/include/CpuArch.h +336 -0
  20. data/libchdr/deps/lzma-19.00/include/Delta.h +19 -0
  21. data/libchdr/deps/lzma-19.00/include/LzFind.h +121 -0
  22. data/libchdr/deps/lzma-19.00/include/LzHash.h +57 -0
  23. data/libchdr/deps/lzma-19.00/include/Lzma86.h +111 -0
  24. data/libchdr/deps/lzma-19.00/include/LzmaDec.h +234 -0
  25. data/libchdr/deps/lzma-19.00/include/LzmaEnc.h +76 -0
  26. data/libchdr/deps/lzma-19.00/include/LzmaLib.h +131 -0
  27. data/libchdr/deps/lzma-19.00/include/Precomp.h +10 -0
  28. data/libchdr/deps/lzma-19.00/include/Sort.h +18 -0
  29. data/libchdr/deps/lzma-19.00/lzma-history.txt +446 -0
  30. data/libchdr/deps/lzma-19.00/lzma.txt +328 -0
  31. data/libchdr/deps/lzma-19.00/lzma.vcxproj +543 -0
  32. data/libchdr/deps/lzma-19.00/lzma.vcxproj.filters +17 -0
  33. data/libchdr/deps/lzma-19.00/src/Alloc.c +455 -0
  34. data/libchdr/deps/lzma-19.00/src/Bra86.c +82 -0
  35. data/libchdr/deps/lzma-19.00/src/BraIA64.c +53 -0
  36. data/libchdr/deps/lzma-19.00/src/CpuArch.c +218 -0
  37. data/libchdr/deps/lzma-19.00/src/Delta.c +64 -0
  38. data/libchdr/deps/lzma-19.00/src/LzFind.c +1127 -0
  39. data/libchdr/deps/lzma-19.00/src/Lzma86Dec.c +54 -0
  40. data/libchdr/deps/lzma-19.00/src/LzmaDec.c +1185 -0
  41. data/libchdr/deps/lzma-19.00/src/LzmaEnc.c +1330 -0
  42. data/libchdr/deps/lzma-19.00/src/Sort.c +141 -0
  43. data/libchdr/deps/zlib-1.2.11/CMakeLists.txt +29 -0
  44. data/libchdr/deps/zlib-1.2.11/ChangeLog +1515 -0
  45. data/libchdr/deps/zlib-1.2.11/FAQ +368 -0
  46. data/libchdr/deps/zlib-1.2.11/INDEX +68 -0
  47. data/libchdr/deps/zlib-1.2.11/Makefile +5 -0
  48. data/libchdr/deps/zlib-1.2.11/Makefile.in +410 -0
  49. data/libchdr/deps/zlib-1.2.11/README +115 -0
  50. data/libchdr/deps/zlib-1.2.11/adler32.c +186 -0
  51. data/libchdr/deps/zlib-1.2.11/compress.c +86 -0
  52. data/libchdr/deps/zlib-1.2.11/configure +921 -0
  53. data/libchdr/deps/zlib-1.2.11/crc32.c +442 -0
  54. data/libchdr/deps/zlib-1.2.11/crc32.h +441 -0
  55. data/libchdr/deps/zlib-1.2.11/deflate.c +2163 -0
  56. data/libchdr/deps/zlib-1.2.11/deflate.h +349 -0
  57. data/libchdr/deps/zlib-1.2.11/doc/algorithm.txt +209 -0
  58. data/libchdr/deps/zlib-1.2.11/doc/rfc1950.txt +619 -0
  59. data/libchdr/deps/zlib-1.2.11/doc/rfc1951.txt +955 -0
  60. data/libchdr/deps/zlib-1.2.11/doc/rfc1952.txt +675 -0
  61. data/libchdr/deps/zlib-1.2.11/doc/txtvsbin.txt +107 -0
  62. data/libchdr/deps/zlib-1.2.11/gzclose.c +25 -0
  63. data/libchdr/deps/zlib-1.2.11/gzguts.h +218 -0
  64. data/libchdr/deps/zlib-1.2.11/gzlib.c +637 -0
  65. data/libchdr/deps/zlib-1.2.11/gzread.c +654 -0
  66. data/libchdr/deps/zlib-1.2.11/gzwrite.c +665 -0
  67. data/libchdr/deps/zlib-1.2.11/infback.c +640 -0
  68. data/libchdr/deps/zlib-1.2.11/inffast.c +323 -0
  69. data/libchdr/deps/zlib-1.2.11/inffast.h +11 -0
  70. data/libchdr/deps/zlib-1.2.11/inffixed.h +94 -0
  71. data/libchdr/deps/zlib-1.2.11/inflate.c +1561 -0
  72. data/libchdr/deps/zlib-1.2.11/inflate.h +125 -0
  73. data/libchdr/deps/zlib-1.2.11/inftrees.c +304 -0
  74. data/libchdr/deps/zlib-1.2.11/inftrees.h +62 -0
  75. data/libchdr/deps/zlib-1.2.11/make_vms.com +867 -0
  76. data/libchdr/deps/zlib-1.2.11/treebuild.xml +116 -0
  77. data/libchdr/deps/zlib-1.2.11/trees.c +1203 -0
  78. data/libchdr/deps/zlib-1.2.11/trees.h +128 -0
  79. data/libchdr/deps/zlib-1.2.11/uncompr.c +93 -0
  80. data/libchdr/deps/zlib-1.2.11/zconf.h +534 -0
  81. data/libchdr/deps/zlib-1.2.11/zconf.h.cmakein +536 -0
  82. data/libchdr/deps/zlib-1.2.11/zconf.h.in +534 -0
  83. data/libchdr/deps/zlib-1.2.11/zlib.3 +149 -0
  84. data/libchdr/deps/zlib-1.2.11/zlib.3.pdf +0 -0
  85. data/libchdr/deps/zlib-1.2.11/zlib.h +1912 -0
  86. data/libchdr/deps/zlib-1.2.11/zlib.map +94 -0
  87. data/libchdr/deps/zlib-1.2.11/zlib.pc.cmakein +13 -0
  88. data/libchdr/deps/zlib-1.2.11/zlib.pc.in +13 -0
  89. data/libchdr/deps/zlib-1.2.11/zlib2ansi +152 -0
  90. data/libchdr/deps/zlib-1.2.11/zutil.c +325 -0
  91. data/libchdr/deps/zlib-1.2.11/zutil.h +271 -0
  92. data/libchdr/include/dr_libs/dr_flac.h +12280 -0
  93. data/libchdr/include/libchdr/bitstream.h +43 -0
  94. data/libchdr/include/libchdr/cdrom.h +110 -0
  95. data/libchdr/include/libchdr/chd.h +427 -0
  96. data/libchdr/include/libchdr/chdconfig.h +10 -0
  97. data/libchdr/include/libchdr/coretypes.h +60 -0
  98. data/libchdr/include/libchdr/flac.h +50 -0
  99. data/libchdr/include/libchdr/huffman.h +90 -0
  100. data/libchdr/pkg-config.pc.in +10 -0
  101. data/libchdr/src/libchdr_bitstream.c +125 -0
  102. data/libchdr/src/libchdr_cdrom.c +415 -0
  103. data/libchdr/src/libchdr_chd.c +2744 -0
  104. data/libchdr/src/libchdr_flac.c +302 -0
  105. data/libchdr/src/libchdr_huffman.c +545 -0
  106. data/libchdr/src/link.T +5 -0
  107. data/libchdr/tests/CMakeLists.txt +2 -0
  108. data/libchdr/tests/benchmark.c +52 -0
  109. 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
+