chd 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +30 -0
- data/chd.gemspec +29 -0
- data/ext/chd.c +1008 -0
- data/ext/extconf.rb +60 -0
- data/lib/chd/cd.rb +272 -0
- data/lib/chd/metadata.rb +196 -0
- data/lib/chd/version.rb +4 -0
- data/lib/chd.rb +21 -0
- data/libchdr/CMakeLists.txt +104 -0
- data/libchdr/LICENSE.txt +24 -0
- data/libchdr/README.md +7 -0
- data/libchdr/deps/lzma-19.00/CMakeLists.txt +33 -0
- data/libchdr/deps/lzma-19.00/LICENSE +3 -0
- data/libchdr/deps/lzma-19.00/include/7zTypes.h +375 -0
- data/libchdr/deps/lzma-19.00/include/Alloc.h +51 -0
- data/libchdr/deps/lzma-19.00/include/Bra.h +64 -0
- data/libchdr/deps/lzma-19.00/include/Compiler.h +33 -0
- data/libchdr/deps/lzma-19.00/include/CpuArch.h +336 -0
- data/libchdr/deps/lzma-19.00/include/Delta.h +19 -0
- data/libchdr/deps/lzma-19.00/include/LzFind.h +121 -0
- data/libchdr/deps/lzma-19.00/include/LzHash.h +57 -0
- data/libchdr/deps/lzma-19.00/include/Lzma86.h +111 -0
- data/libchdr/deps/lzma-19.00/include/LzmaDec.h +234 -0
- data/libchdr/deps/lzma-19.00/include/LzmaEnc.h +76 -0
- data/libchdr/deps/lzma-19.00/include/LzmaLib.h +131 -0
- data/libchdr/deps/lzma-19.00/include/Precomp.h +10 -0
- data/libchdr/deps/lzma-19.00/include/Sort.h +18 -0
- data/libchdr/deps/lzma-19.00/lzma-history.txt +446 -0
- data/libchdr/deps/lzma-19.00/lzma.txt +328 -0
- data/libchdr/deps/lzma-19.00/lzma.vcxproj +543 -0
- data/libchdr/deps/lzma-19.00/lzma.vcxproj.filters +17 -0
- data/libchdr/deps/lzma-19.00/src/Alloc.c +455 -0
- data/libchdr/deps/lzma-19.00/src/Bra86.c +82 -0
- data/libchdr/deps/lzma-19.00/src/BraIA64.c +53 -0
- data/libchdr/deps/lzma-19.00/src/CpuArch.c +218 -0
- data/libchdr/deps/lzma-19.00/src/Delta.c +64 -0
- data/libchdr/deps/lzma-19.00/src/LzFind.c +1127 -0
- data/libchdr/deps/lzma-19.00/src/Lzma86Dec.c +54 -0
- data/libchdr/deps/lzma-19.00/src/LzmaDec.c +1185 -0
- data/libchdr/deps/lzma-19.00/src/LzmaEnc.c +1330 -0
- data/libchdr/deps/lzma-19.00/src/Sort.c +141 -0
- data/libchdr/deps/zlib-1.2.11/CMakeLists.txt +29 -0
- data/libchdr/deps/zlib-1.2.11/ChangeLog +1515 -0
- data/libchdr/deps/zlib-1.2.11/FAQ +368 -0
- data/libchdr/deps/zlib-1.2.11/INDEX +68 -0
- data/libchdr/deps/zlib-1.2.11/Makefile +5 -0
- data/libchdr/deps/zlib-1.2.11/Makefile.in +410 -0
- data/libchdr/deps/zlib-1.2.11/README +115 -0
- data/libchdr/deps/zlib-1.2.11/adler32.c +186 -0
- data/libchdr/deps/zlib-1.2.11/compress.c +86 -0
- data/libchdr/deps/zlib-1.2.11/configure +921 -0
- data/libchdr/deps/zlib-1.2.11/crc32.c +442 -0
- data/libchdr/deps/zlib-1.2.11/crc32.h +441 -0
- data/libchdr/deps/zlib-1.2.11/deflate.c +2163 -0
- data/libchdr/deps/zlib-1.2.11/deflate.h +349 -0
- data/libchdr/deps/zlib-1.2.11/doc/algorithm.txt +209 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1950.txt +619 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1951.txt +955 -0
- data/libchdr/deps/zlib-1.2.11/doc/rfc1952.txt +675 -0
- data/libchdr/deps/zlib-1.2.11/doc/txtvsbin.txt +107 -0
- data/libchdr/deps/zlib-1.2.11/gzclose.c +25 -0
- data/libchdr/deps/zlib-1.2.11/gzguts.h +218 -0
- data/libchdr/deps/zlib-1.2.11/gzlib.c +637 -0
- data/libchdr/deps/zlib-1.2.11/gzread.c +654 -0
- data/libchdr/deps/zlib-1.2.11/gzwrite.c +665 -0
- data/libchdr/deps/zlib-1.2.11/infback.c +640 -0
- data/libchdr/deps/zlib-1.2.11/inffast.c +323 -0
- data/libchdr/deps/zlib-1.2.11/inffast.h +11 -0
- data/libchdr/deps/zlib-1.2.11/inffixed.h +94 -0
- data/libchdr/deps/zlib-1.2.11/inflate.c +1561 -0
- data/libchdr/deps/zlib-1.2.11/inflate.h +125 -0
- data/libchdr/deps/zlib-1.2.11/inftrees.c +304 -0
- data/libchdr/deps/zlib-1.2.11/inftrees.h +62 -0
- data/libchdr/deps/zlib-1.2.11/make_vms.com +867 -0
- data/libchdr/deps/zlib-1.2.11/treebuild.xml +116 -0
- data/libchdr/deps/zlib-1.2.11/trees.c +1203 -0
- data/libchdr/deps/zlib-1.2.11/trees.h +128 -0
- data/libchdr/deps/zlib-1.2.11/uncompr.c +93 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h +534 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h.cmakein +536 -0
- data/libchdr/deps/zlib-1.2.11/zconf.h.in +534 -0
- data/libchdr/deps/zlib-1.2.11/zlib.3 +149 -0
- data/libchdr/deps/zlib-1.2.11/zlib.3.pdf +0 -0
- data/libchdr/deps/zlib-1.2.11/zlib.h +1912 -0
- data/libchdr/deps/zlib-1.2.11/zlib.map +94 -0
- data/libchdr/deps/zlib-1.2.11/zlib.pc.cmakein +13 -0
- data/libchdr/deps/zlib-1.2.11/zlib.pc.in +13 -0
- data/libchdr/deps/zlib-1.2.11/zlib2ansi +152 -0
- data/libchdr/deps/zlib-1.2.11/zutil.c +325 -0
- data/libchdr/deps/zlib-1.2.11/zutil.h +271 -0
- data/libchdr/include/dr_libs/dr_flac.h +12280 -0
- data/libchdr/include/libchdr/bitstream.h +43 -0
- data/libchdr/include/libchdr/cdrom.h +110 -0
- data/libchdr/include/libchdr/chd.h +427 -0
- data/libchdr/include/libchdr/chdconfig.h +10 -0
- data/libchdr/include/libchdr/coretypes.h +60 -0
- data/libchdr/include/libchdr/flac.h +50 -0
- data/libchdr/include/libchdr/huffman.h +90 -0
- data/libchdr/pkg-config.pc.in +10 -0
- data/libchdr/src/libchdr_bitstream.c +125 -0
- data/libchdr/src/libchdr_cdrom.c +415 -0
- data/libchdr/src/libchdr_chd.c +2744 -0
- data/libchdr/src/libchdr_flac.c +302 -0
- data/libchdr/src/libchdr_huffman.c +545 -0
- data/libchdr/src/link.T +5 -0
- data/libchdr/tests/CMakeLists.txt +2 -0
- data/libchdr/tests/benchmark.c +52 -0
- metadata +183 -0
data/ext/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
|
+
|