Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>. |
||
3 | * It is copyright by its individual contributors, as recorded in the |
||
4 | * project's Git history. See COPYING.txt at the top level for license |
||
5 | * terms and a link to the Git history. |
||
6 | */ |
||
7 | #include <string.h> // for mem* functions |
||
8 | #if !defined(_WIN32) && !defined(macintosh) |
||
9 | #include <unistd.h> |
||
10 | #include <sys/types.h> |
||
11 | #include <sys/stat.h> |
||
12 | #include <fcntl.h> |
||
13 | #endif |
||
14 | |||
15 | #include "mvelib.h" |
||
16 | #include <memory> |
||
17 | |||
18 | static const char MVE_HEADER[] = "Interplay MVE File\x1A"; |
||
19 | constexpr short MVE_HDRCONST1 = 0x001A; |
||
20 | constexpr short MVE_HDRCONST2 = 0x0100; |
||
21 | constexpr short MVE_HDRCONST3 = 0x1133; |
||
22 | |||
23 | mve_cb_Read mve_read; |
||
24 | mve_cb_Alloc mve_alloc; |
||
25 | mve_cb_Free mve_free; |
||
26 | mve_cb_ShowFrame mve_showframe; |
||
27 | mve_cb_SetPalette mve_setpalette; |
||
28 | |||
29 | /* |
||
30 | * private utility functions |
||
31 | */ |
||
32 | static int16_t _mve_get_short(const unsigned char *data); |
||
33 | static uint16_t _mve_get_ushort(const unsigned char *data); |
||
34 | |||
35 | /* |
||
36 | * private functions for mvefile |
||
37 | */ |
||
38 | static int _mvefile_open(MVEFILE *movie, void *stream); |
||
39 | static int _mvefile_read_header(const MVEFILE *movie); |
||
40 | static void _mvefile_set_buffer_size(MVEFILE *movie, std::size_t buf_size); |
||
41 | static int _mvefile_fetch_next_chunk(MVEFILE *movie); |
||
42 | |||
43 | /* |
||
44 | * private functions for mvestream |
||
45 | */ |
||
46 | static int _mvestream_open(MVESTREAM *movie, void *stream); |
||
47 | static void _mvestream_reset(MVESTREAM *movie); |
||
48 | |||
49 | /************************************************************ |
||
50 | * public MVEFILE functions |
||
51 | ************************************************************/ |
||
52 | |||
53 | /* |
||
54 | * open an MVE file |
||
55 | */ |
||
56 | std::unique_ptr<MVEFILE> mvefile_open(void *stream) |
||
57 | { |
||
58 | /* create the file */ |
||
59 | auto file = std::make_unique<MVEFILE>(); |
||
60 | if (! _mvefile_open(file.get(), stream)) |
||
61 | { |
||
62 | return nullptr; |
||
63 | } |
||
64 | |||
65 | /* initialize the file */ |
||
66 | _mvefile_set_buffer_size(file.get(), 1024); |
||
67 | |||
68 | /* verify the file's header */ |
||
69 | if (! _mvefile_read_header(file.get())) |
||
70 | { |
||
71 | return nullptr; |
||
72 | } |
||
73 | |||
74 | /* now, prefetch the next chunk */ |
||
75 | _mvefile_fetch_next_chunk(file.get()); |
||
76 | return file; |
||
77 | } |
||
78 | |||
79 | /* |
||
80 | * reset a MVE file |
||
81 | */ |
||
82 | static void mvefile_reset(MVEFILE *file) |
||
83 | { |
||
84 | /* initialize the file */ |
||
85 | _mvefile_set_buffer_size(file, 1024); |
||
86 | |||
87 | /* verify the file's header */ |
||
88 | if (! _mvefile_read_header(file)) |
||
89 | { |
||
90 | *file = {}; |
||
91 | } |
||
92 | |||
93 | /* now, prefetch the next chunk */ |
||
94 | _mvefile_fetch_next_chunk(file); |
||
95 | } |
||
96 | |||
97 | static bool have_segment_header(const MVEFILE *movie) |
||
98 | { |
||
99 | /* if nothing is cached, fail */ |
||
100 | if (movie->next_segment >= movie->cur_chunk.size()) |
||
101 | return false; |
||
102 | /* if we don't have enough data to get a segment, fail */ |
||
103 | if (movie->cur_chunk.size() - movie->next_segment <= 4) |
||
104 | return false; |
||
105 | return true; |
||
106 | } |
||
107 | |||
108 | /* |
||
109 | * get the size of the next segment |
||
110 | */ |
||
111 | int_fast32_t mvefile_get_next_segment_size(const MVEFILE *movie) |
||
112 | { |
||
113 | if (!have_segment_header(movie)) |
||
114 | return -1; |
||
115 | /* otherwise, get the data length */ |
||
116 | return _mve_get_short(&movie->cur_chunk[movie->next_segment]); |
||
117 | } |
||
118 | |||
119 | /* |
||
120 | * get type of next segment in chunk (0xff if no more segments in chunk) |
||
121 | */ |
||
122 | unsigned char mvefile_get_next_segment_major(const MVEFILE *movie) |
||
123 | { |
||
124 | if (!have_segment_header(movie)) |
||
125 | return 0xff; |
||
126 | /* otherwise, get the data length */ |
||
127 | return movie->cur_chunk[movie->next_segment + 2]; |
||
128 | } |
||
129 | |||
130 | /* |
||
131 | * get subtype (version) of next segment in chunk (0xff if no more segments in |
||
132 | * chunk) |
||
133 | */ |
||
134 | unsigned char mvefile_get_next_segment_minor(const MVEFILE *movie) |
||
135 | { |
||
136 | if (!have_segment_header(movie)) |
||
137 | return 0xff; |
||
138 | /* otherwise, get the data length */ |
||
139 | return movie->cur_chunk[movie->next_segment + 3]; |
||
140 | } |
||
141 | |||
142 | /* |
||
143 | * see next segment (return NULL if no next segment) |
||
144 | */ |
||
145 | const unsigned char *mvefile_get_next_segment(const MVEFILE *movie) |
||
146 | { |
||
147 | if (!have_segment_header(movie)) |
||
148 | return NULL; |
||
149 | |||
150 | /* otherwise, get the data length */ |
||
151 | return &movie->cur_chunk[movie->next_segment + 4]; |
||
152 | } |
||
153 | |||
154 | /* |
||
155 | * advance to next segment |
||
156 | */ |
||
157 | void mvefile_advance_segment(MVEFILE *movie) |
||
158 | { |
||
159 | if (!have_segment_header(movie)) |
||
160 | return; |
||
161 | /* else, advance to next segment */ |
||
162 | movie->next_segment += |
||
163 | (4 + _mve_get_ushort(&movie->cur_chunk[movie->next_segment])); |
||
164 | } |
||
165 | |||
166 | /* |
||
167 | * fetch the next chunk (return 0 if at end of stream) |
||
168 | */ |
||
169 | int mvefile_fetch_next_chunk(MVEFILE *movie) |
||
170 | { |
||
171 | return _mvefile_fetch_next_chunk(movie); |
||
172 | } |
||
173 | |||
174 | /************************************************************ |
||
175 | * public MVESTREAM functions |
||
176 | ************************************************************/ |
||
177 | |||
178 | /* |
||
179 | * open an MVE stream |
||
180 | */ |
||
181 | MVESTREAM_ptr_t mve_open(void *stream) |
||
182 | { |
||
183 | /* allocate */ |
||
184 | auto movie = std::make_unique<MVESTREAM>(); |
||
185 | |||
186 | /* open */ |
||
187 | if (! _mvestream_open(movie.get(), stream)) |
||
188 | { |
||
189 | return nullptr; |
||
190 | } |
||
191 | return MVESTREAM_ptr_t(movie.release()); |
||
192 | } |
||
193 | |||
194 | /* |
||
195 | * reset an MVE stream |
||
196 | */ |
||
197 | void mve_reset(MVESTREAM *movie) |
||
198 | { |
||
199 | _mvestream_reset(movie); |
||
200 | } |
||
201 | |||
202 | /* |
||
203 | * set segment type handler |
||
204 | */ |
||
205 | void mve_set_handler(MVESTREAM &movie, unsigned char major, MVESEGMENTHANDLER handler) |
||
206 | { |
||
207 | if (major < 32) |
||
208 | movie.handlers[major] = handler; |
||
209 | } |
||
210 | |||
211 | /* |
||
212 | * set segment handler context |
||
213 | */ |
||
214 | void mve_set_handler_context(MVESTREAM *movie, void *context) |
||
215 | { |
||
216 | movie->context = context; |
||
217 | } |
||
218 | |||
219 | /* |
||
220 | * play next chunk |
||
221 | */ |
||
222 | int mve_play_next_chunk(MVESTREAM &movie) |
||
223 | { |
||
224 | const auto m = movie.movie.get(); |
||
225 | /* loop over segments */ |
||
226 | /* advance to next segment */ |
||
227 | for (;; mvefile_advance_segment(m)) |
||
228 | { |
||
229 | const auto major = mvefile_get_next_segment_major(m); |
||
230 | if (major == 0xff) |
||
231 | break; |
||
232 | if (major >= movie.handlers.size()) |
||
233 | continue; |
||
234 | /* check whether to handle the segment */ |
||
235 | if (const auto handler = movie.handlers[major]) |
||
236 | { |
||
237 | const auto minor = mvefile_get_next_segment_minor(m); |
||
238 | const auto len = mvefile_get_next_segment_size(m); |
||
239 | const auto data = mvefile_get_next_segment(m); |
||
240 | |||
241 | if (!handler(major, minor, data, len, movie.context)) |
||
242 | return 0; |
||
243 | } |
||
244 | } |
||
245 | |||
246 | if (!mvefile_fetch_next_chunk(m)) |
||
247 | return 0; |
||
248 | |||
249 | /* return status */ |
||
250 | return 1; |
||
251 | } |
||
252 | |||
253 | /************************************************************ |
||
254 | * private functions |
||
255 | ************************************************************/ |
||
256 | |||
257 | /* |
||
258 | * allocate an MVEFILE |
||
259 | */ |
||
260 | MVEFILE::MVEFILE() |
||
261 | { |
||
262 | } |
||
263 | |||
264 | /* |
||
265 | * free an MVE file |
||
266 | */ |
||
267 | MVEFILE::~MVEFILE() |
||
268 | { |
||
269 | } |
||
270 | |||
271 | /* |
||
272 | * open the file stream in thie object |
||
273 | */ |
||
274 | static int _mvefile_open(MVEFILE *file, void *stream) |
||
275 | { |
||
276 | file->stream = stream; |
||
277 | if (! file->stream) |
||
278 | return 0; |
||
279 | |||
280 | return 1; |
||
281 | } |
||
282 | |||
283 | /* |
||
284 | * read and verify the header of the recently opened file |
||
285 | */ |
||
286 | static int _mvefile_read_header(const MVEFILE *movie) |
||
287 | { |
||
288 | unsigned char buffer[26]; |
||
289 | |||
290 | /* check the file is open */ |
||
291 | if (! movie->stream) |
||
292 | return 0; |
||
293 | |||
294 | /* check the file is long enough */ |
||
295 | if (! mve_read(movie->stream, buffer, 26)) |
||
296 | return 0; |
||
297 | |||
298 | /* check the signature */ |
||
299 | if (memcmp(buffer, MVE_HEADER, 20)) |
||
300 | return 0; |
||
301 | |||
302 | /* check the hard-coded constants */ |
||
303 | if (_mve_get_short(buffer+20) != MVE_HDRCONST1) |
||
304 | return 0; |
||
305 | if (_mve_get_short(buffer+22) != MVE_HDRCONST2) |
||
306 | return 0; |
||
307 | if (_mve_get_short(buffer+24) != MVE_HDRCONST3) |
||
308 | return 0; |
||
309 | |||
310 | return 1; |
||
311 | } |
||
312 | |||
313 | static void _mvefile_set_buffer_size(MVEFILE *movie, std::size_t buf_size) |
||
314 | { |
||
315 | /* check if this would be a redundant operation */ |
||
316 | if (buf_size <= movie->cur_chunk.size()) |
||
317 | return; |
||
318 | |||
319 | /* allocate new buffer */ |
||
320 | /* copy old data */ |
||
321 | /* free old buffer */ |
||
322 | /* install new buffer */ |
||
323 | movie->cur_chunk.resize(100 + buf_size); |
||
324 | } |
||
325 | |||
326 | static int _mvefile_fetch_next_chunk(MVEFILE *movie) |
||
327 | { |
||
328 | unsigned char buffer[4]; |
||
329 | unsigned short length; |
||
330 | |||
331 | /* fail if not open */ |
||
332 | if (! movie->stream) |
||
333 | return 0; |
||
334 | |||
335 | /* fail if we can't read the next segment descriptor */ |
||
336 | if (! mve_read(movie->stream, buffer, 4)) |
||
337 | return 0; |
||
338 | |||
339 | /* pull out the next length */ |
||
340 | length = _mve_get_short(buffer); |
||
341 | |||
342 | /* make sure we've got sufficient space */ |
||
343 | _mvefile_set_buffer_size(movie, length); |
||
344 | |||
345 | /* read the chunk */ |
||
346 | if (! mve_read(movie->stream, &movie->cur_chunk[0], length)) |
||
347 | return 0; |
||
348 | movie->cur_chunk.resize(length); |
||
349 | movie->next_segment = 0; |
||
350 | |||
351 | return 1; |
||
352 | } |
||
353 | |||
354 | static int16_t _mve_get_short(const unsigned char *data) |
||
355 | { |
||
356 | short value; |
||
357 | value = data[0] | (data[1] << 8); |
||
358 | return value; |
||
359 | } |
||
360 | |||
361 | static uint16_t _mve_get_ushort(const unsigned char *data) |
||
362 | { |
||
363 | unsigned short value; |
||
364 | value = data[0] | (data[1] << 8); |
||
365 | return value; |
||
366 | } |
||
367 | |||
368 | /* |
||
369 | * allocate an MVESTREAM |
||
370 | */ |
||
371 | MVESTREAM::MVESTREAM() |
||
372 | /* allocate and zero-initialize everything */ |
||
373 | { |
||
374 | } |
||
375 | |||
376 | MVESTREAM::~MVESTREAM() |
||
377 | { |
||
378 | } |
||
379 | |||
380 | /* |
||
381 | * open an MVESTREAM object |
||
382 | */ |
||
383 | static int _mvestream_open(MVESTREAM *movie, void *stream) |
||
384 | { |
||
385 | movie->movie = mvefile_open(stream); |
||
386 | |||
387 | return (movie->movie == NULL) ? 0 : 1; |
||
388 | } |
||
389 | |||
390 | /* |
||
391 | * reset an MVESTREAM |
||
392 | */ |
||
393 | static void _mvestream_reset(MVESTREAM *movie) |
||
394 | { |
||
395 | mvefile_reset(movie->movie.get()); |
||
396 | } |