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.
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
+