1 /* pzx_read.c: Routines for reading .pzx files
2    Copyright (c) 2001, 2002 Philip Kendall, Darren Salt
3    Copyright (c) 2011-2015 Fredrick Meunier
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License along
16    with this program; if not, write to the Free Software Foundation, Inc.,
17    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 
19    Author contact information:
20 
21    E-mail: philip-fuse@shadowmagic.org.uk
22 
23 */
24 
25 #include "config.h"
26 
27 #include <stddef.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 #include "internals.h"
32 
33 /* Used for passing internal data around */
34 
35 typedef struct pzx_context {
36 
37   libspectrum_word version;
38 
39 } pzx_context;
40 
41 /* Constants etc for each block type */
42 
43 #define PZX_HEADER "PZXT"
44 struct info_t {
45 
46   const char *id;
47   int archive_info_id;
48 
49 };
50 
51 /* Needs to be in strcmp  order */
52 static struct info_t info_ids[] = {
53 
54   { "Author",       0x02 },
55   { "Comment",      0xff },
56   { "Language",     0x04 },
57   { "Origin",       0x08 },
58   { "Price",        0x06 },
59   { "Protection",   0x07 },
60   { "Publisher",    0x01 },
61   { "Type",         0x05 },
62   { "Year",         0x03 },
63 
64 };
65 
66 #define PZX_PULSE  "PULS"
67 
68 #define PZX_DATA   "DATA"
69 
70 #define PZX_PAUSE  "PAUS"
71 
72 #define PZX_BROWSE "BRWS"
73 
74 #define PZX_STOP   "STOP"
75 static const libspectrum_byte PZXF_STOP48 = 1;
76 
77 /* TODO: an extension to be similar to the TZX Custom Block Picture type */
78 #define PZX_INLAY  "inly"
79 
80 static const char * const signature = PZX_HEADER;
81 static const size_t signature_length = 4;
82 
83 static libspectrum_error
84 read_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
85             const libspectrum_byte *end, pzx_context *ctx );
86 
87 typedef libspectrum_error (*read_block_fn)( libspectrum_tape *tape,
88 					    const libspectrum_byte **buffer,
89 					    const libspectrum_byte *end,
90 					    size_t data_length,
91                                             pzx_context *ctx );
92 
93 static libspectrum_error
94 pzx_read_data( const libspectrum_byte **ptr, const libspectrum_byte *end,
95 	       size_t length, libspectrum_byte **data );
96 
97 static libspectrum_error
98 pzx_read_string( const libspectrum_byte **ptr, const libspectrum_byte *end,
99 		 char **dest );
100 
101 static int
info_t_compar(const void * a,const void * b)102 info_t_compar(const void *a, const void *b)
103 {
104   const char *key = a;
105   const struct info_t *test = b;
106   return strcmp( key, test->id );
107 }
108 
109 static int
get_id_byte(char * info_tag)110 get_id_byte( char *info_tag ) {
111   struct info_t *info =
112     (struct info_t*)bsearch( info_tag, info_ids, ARRAY_SIZE( info_ids ),
113                              sizeof( struct info_t ), info_t_compar );
114   return info == NULL ? -1 : info->archive_info_id;
115 }
116 
117 static libspectrum_error
read_pzxt_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)118 read_pzxt_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
119                  const libspectrum_byte *end, size_t data_length,
120                  pzx_context *ctx )
121 {
122   libspectrum_error error;
123 
124   size_t i = 0;
125   size_t count = 0;
126   int id;
127   int *ids = NULL;
128   char *info_tag = NULL;
129   char *string;
130   char **strings = NULL;
131   const libspectrum_byte *block_end = *buffer + data_length;
132 
133   if( data_length < 2 ) {
134     libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
135 			     "read_pzxt_block: length %lu too short",
136 			     (unsigned long)data_length );
137     return LIBSPECTRUM_ERROR_CORRUPT;
138   }
139 
140   ctx->version = (**buffer) << 8; (*buffer)++;
141   ctx->version |= **buffer; (*buffer)++;
142 
143   if( ctx->version < 0x0100 || ctx->version >= 0x0200 ) {
144     libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
145 			     "read_pzxt_block: only version 1 pzx files are "
146                              "supported" );
147     return LIBSPECTRUM_ERROR_UNKNOWN;
148   }
149 
150   if( *buffer < block_end ) {
151     ids = libspectrum_new( int, 1 );
152     strings = libspectrum_new( char *, 1 );
153     count = 1;
154     i = 0;
155 
156     ids[0] = 0x00;
157 
158     /* Read in the title string itself */
159     error = pzx_read_string( buffer, block_end, &strings[0] );
160     if( error ) {
161       libspectrum_free( strings[0] );
162       return error;
163     }
164   }
165 
166   while( *buffer < block_end ) {
167     error = pzx_read_string( buffer, block_end, &info_tag );
168     if( error ) {
169       size_t j;
170       for( j = 0; j < i; j++ ) libspectrum_free( strings[j] );
171       libspectrum_free( strings ); libspectrum_free( ids );
172       return error;
173     }
174 
175     /* Get the ID byte */
176     id = get_id_byte( info_tag );
177 
178     /* Read in the string itself */
179     error = pzx_read_string( buffer, block_end, &string );
180     if( error ) {
181       size_t j;
182       for( j = 0; j < i; j++ ) libspectrum_free( strings[j] );
183       libspectrum_free( strings ); libspectrum_free( ids );
184       return error;
185     }
186 
187     i = count++;
188     ids = libspectrum_renew( int, ids, count );
189     strings = libspectrum_renew( char *, strings, count );
190 
191     if( id == -1 ) {
192       size_t new_len = strlen( info_tag ) + strlen( string ) +
193                        strlen( ": " ) + 1;
194       char *comment = libspectrum_new( char, new_len );
195       snprintf( comment, new_len, "%s: %s", info_tag, string );
196       libspectrum_free( string );
197       ids[i] = 0xff;
198       strings[i] = comment;
199     } else {
200       ids[i] = id;
201       strings[i] = string;
202     }
203 
204     libspectrum_free( info_tag );
205   }
206 
207   if( count ) {
208     libspectrum_tape_block* block =
209       libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_ARCHIVE_INFO );
210 
211     libspectrum_tape_block_set_count( block, count );
212     libspectrum_tape_block_set_ids( block, ids );
213     libspectrum_tape_block_set_texts( block, strings );
214 
215     libspectrum_tape_append_block( tape, block );
216   }
217 
218   return LIBSPECTRUM_ERROR_NONE;
219 }
220 
221 
222 static libspectrum_error
read_data_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)223 read_data_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
224                  const libspectrum_byte *end, size_t data_length,
225                  pzx_context *ctx )
226 {
227   const libspectrum_byte *block_end = *buffer + data_length;
228   libspectrum_tape_block* block;
229   libspectrum_byte *data;
230 
231   libspectrum_error error;
232   libspectrum_dword count;
233   int initial_level;
234   size_t count_bytes;
235   size_t bits_in_last_byte;
236   libspectrum_word tail;
237   libspectrum_byte p0_count;
238   libspectrum_byte p1_count;
239   libspectrum_word *p0_pulses;
240   libspectrum_word *p1_pulses;
241 
242   /* Check there's enough left in the buffer for all the metadata */
243   if( data_length < 8 ) {
244     libspectrum_print_error(
245       LIBSPECTRUM_ERROR_CORRUPT,
246       "read_data_block: not enough data in buffer"
247     );
248     return LIBSPECTRUM_ERROR_CORRUPT;
249   }
250 
251   /* Get the metadata */
252   count = libspectrum_read_dword( buffer );
253   initial_level = !!(count & 0x80000000);
254   count &= 0x7fffffff;
255   count_bytes = libspectrum_bits_to_bytes( count );
256   bits_in_last_byte =
257     count % LIBSPECTRUM_BITS_IN_BYTE ?
258       count % LIBSPECTRUM_BITS_IN_BYTE : LIBSPECTRUM_BITS_IN_BYTE;
259   tail = libspectrum_read_word( buffer );
260   p0_count = **buffer; (*buffer)++;
261   p1_count = **buffer; (*buffer)++;
262 
263   /* need to confirm that we have enough length left for the pulse definitions
264    */
265   if( data_length < 8 + 2*(p0_count + p1_count) ) {
266     libspectrum_print_error(
267       LIBSPECTRUM_ERROR_CORRUPT,
268       "read_data_block: not enough data in buffer"
269     );
270     return LIBSPECTRUM_ERROR_CORRUPT;
271   }
272 
273   error = pzx_read_data( buffer, block_end,
274                          p0_count * sizeof( libspectrum_word ),
275                          (libspectrum_byte**)&p0_pulses );
276   if( error ) return error;
277 
278   error = pzx_read_data( buffer, block_end,
279                          p1_count * sizeof( libspectrum_word ),
280                          (libspectrum_byte**)&p1_pulses );
281   if( error ) { libspectrum_free( p0_pulses ); return error; }
282 
283   /* And the actual data */
284   error = pzx_read_data( buffer, block_end, count_bytes, &data );
285   if( error ) {
286     libspectrum_free( p0_pulses );
287     libspectrum_free( p1_pulses );
288     return error;
289   }
290 
291   block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_DATA_BLOCK );
292 
293   libspectrum_tape_block_set_count( block, count );
294   libspectrum_tape_block_set_tail_length( block, tail );
295   libspectrum_tape_block_set_level( block, initial_level );
296   libspectrum_tape_block_set_bit0_pulse_count( block, p0_count );
297   libspectrum_tape_block_set_bit0_pulses( block, p0_pulses );
298   libspectrum_tape_block_set_bit1_pulse_count( block, p1_count );
299   libspectrum_tape_block_set_bit1_pulses( block, p1_pulses );
300   libspectrum_tape_block_set_data_length( block, count_bytes );
301   libspectrum_tape_block_set_bits_in_last_byte( block, bits_in_last_byte );
302   libspectrum_tape_block_set_data( block, data );
303 
304   libspectrum_tape_append_block( tape, block );
305 
306   return LIBSPECTRUM_ERROR_NONE;
307 }
308 
309 static libspectrum_error
read_next_pulse(const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t * pulse_repeats,libspectrum_dword * length)310 read_next_pulse( const libspectrum_byte **buffer, const libspectrum_byte *end,
311                  size_t *pulse_repeats, libspectrum_dword *length )
312 {
313   /* While we have at least one int 16 left try to extract the next pulse */
314   if( ( end - (*buffer) ) < (ptrdiff_t)2 ) goto pzx_corrupt;
315 
316   *pulse_repeats = 1;
317   *length = libspectrum_read_word( buffer );
318   if( *length > 0x8000 ) {
319     if( ( end - (*buffer) ) < (ptrdiff_t)2 ) goto pzx_corrupt;
320     *pulse_repeats = *length & 0x7fff;
321     *length = libspectrum_read_word( buffer );
322   }
323   if( *length >= 0x8000 ) {
324     if( ( end - (*buffer) ) < (ptrdiff_t)2 ) goto pzx_corrupt;
325     *length &= 0x7fff;
326     *length <<= 16;
327     *length |= libspectrum_read_word( buffer );
328   }
329 
330   /* And return */
331   return LIBSPECTRUM_ERROR_NONE;
332 
333 pzx_corrupt:
334   libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
335                            "read_next_pulse: not enough data in buffer" );
336   return LIBSPECTRUM_ERROR_CORRUPT;
337 }
338 
339 static libspectrum_error
read_puls_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)340 read_puls_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
341                  const libspectrum_byte *end, size_t data_length,
342                  pzx_context *ctx )
343 {
344   size_t count = 0;
345   size_t pulse_repeats;
346   libspectrum_dword length;
347   libspectrum_tape_block *block;
348   libspectrum_error error;
349   size_t buffer_sizes = 64;
350   size_t *pulse_repeats_buffer =
351     libspectrum_new( size_t, buffer_sizes );
352   libspectrum_dword *lengths_buffer =
353     libspectrum_new( libspectrum_dword, buffer_sizes );
354   const libspectrum_byte *block_end = *buffer + data_length;
355 
356   while( ( block_end - (*buffer) ) > (ptrdiff_t)0 ) {
357     error = read_next_pulse( buffer, block_end, &pulse_repeats, &length );
358     if( error ) {
359       libspectrum_free( pulse_repeats_buffer );
360       libspectrum_free( lengths_buffer );
361       return error;
362     }
363     pulse_repeats_buffer[ count ] = pulse_repeats;
364     lengths_buffer[ count ] = length;
365     count++;
366     if( buffer_sizes == count ) {
367       buffer_sizes *= 2;
368       pulse_repeats_buffer =
369         libspectrum_renew( size_t, pulse_repeats_buffer, buffer_sizes );
370       lengths_buffer =
371         libspectrum_renew( libspectrum_dword, lengths_buffer, buffer_sizes );
372     }
373   }
374 
375   if( count == 0 ) {
376     libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
377                            "read_puls_block: no pulses found in pulse block" );
378     return LIBSPECTRUM_ERROR_CORRUPT;
379   }
380 
381   if( buffer_sizes != count ) {
382     pulse_repeats_buffer =
383       libspectrum_renew( size_t, pulse_repeats_buffer, count );
384     lengths_buffer =
385       libspectrum_renew( libspectrum_dword, lengths_buffer, count );
386   }
387 
388   block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_PULSE_SEQUENCE );
389 
390   libspectrum_tape_block_set_count( block, count );
391   libspectrum_tape_block_set_pulse_lengths( block, lengths_buffer );
392   libspectrum_tape_block_set_pulse_repeats( block, pulse_repeats_buffer );
393 
394   libspectrum_tape_append_block( tape, block );
395 
396   /* And return */
397   return LIBSPECTRUM_ERROR_NONE;
398 }
399 
400 static libspectrum_error
read_paus_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)401 read_paus_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
402                  const libspectrum_byte *end, size_t data_length,
403                  pzx_context *ctx )
404 {
405   libspectrum_tape_block *block;
406   libspectrum_dword pause_tstates;
407   int initial_level;
408 
409   /* Check the pause actually exists */
410   if( data_length < 2 ) {
411     libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
412 			     "read_paus_block: not enough data in buffer" );
413     return LIBSPECTRUM_ERROR_CORRUPT;
414   }
415 
416   block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_PAUSE );
417 
418   pause_tstates = libspectrum_read_dword( buffer );
419   initial_level = !!(pause_tstates & 0x80000000);
420   pause_tstates &= 0x7fffffff;
421 
422   /* Set the pause length */
423   libspectrum_set_pause_tstates( block, pause_tstates );
424   libspectrum_tape_block_set_level( block, initial_level );
425 
426   libspectrum_tape_append_block( tape, block );
427 
428   /* And return */
429   return LIBSPECTRUM_ERROR_NONE;
430 }
431 
432 static libspectrum_error
read_brws_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)433 read_brws_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
434                  const libspectrum_byte *end, size_t data_length,
435                  pzx_context *ctx )
436 {
437   libspectrum_tape_block* block;
438   char *text;
439   libspectrum_error error;
440 
441   block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_COMMENT );
442 
443   /* Get the actual comment */
444   error = pzx_read_string( buffer, *buffer + data_length, &text );
445   if( error ) { libspectrum_free( block ); return error; }
446   libspectrum_tape_block_set_text( block, text );
447 
448   libspectrum_tape_append_block( tape, block );
449 
450   return LIBSPECTRUM_ERROR_NONE;
451 }
452 
453 static libspectrum_error
read_stop_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,size_t data_length,pzx_context * ctx)454 read_stop_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
455                  const libspectrum_byte *end, size_t data_length,
456                  pzx_context *ctx )
457 {
458   libspectrum_tape_block *block;
459   libspectrum_word flags;
460 
461   if( data_length < 2 ) {
462     libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
463 			     "tzx_read_stop: not enough data in buffer" );
464     return LIBSPECTRUM_ERROR_CORRUPT;
465   }
466 
467   flags = libspectrum_read_word( buffer );
468 
469   if( flags == PZXF_STOP48 ) {
470     block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_STOP48 );
471   } else {
472     /* General stop is a 0 duration pause */
473     block = libspectrum_tape_block_alloc( LIBSPECTRUM_TAPE_BLOCK_PAUSE );
474     libspectrum_tape_block_set_pause( block, 0 );
475   }
476 
477   libspectrum_tape_append_block( tape, block );
478 
479   return LIBSPECTRUM_ERROR_NONE;
480 }
481 
482 static libspectrum_error
skip_block(libspectrum_tape * tape GCC_UNUSED,const libspectrum_byte ** buffer,const libspectrum_byte * end GCC_UNUSED,size_t data_length,pzx_context * ctx GCC_UNUSED)483 skip_block( libspectrum_tape *tape GCC_UNUSED,
484 	    const libspectrum_byte **buffer,
485 	    const libspectrum_byte *end GCC_UNUSED,
486             size_t data_length,
487             pzx_context *ctx GCC_UNUSED )
488 {
489   *buffer += data_length;
490   return LIBSPECTRUM_ERROR_NONE;
491 }
492 
493 struct read_block_t {
494 
495   const char *id;
496   read_block_fn function;
497 
498 };
499 
500 static struct read_block_t read_blocks[] = {
501 
502   { PZX_HEADER,     read_pzxt_block },
503   { PZX_PULSE,      read_puls_block },
504   { PZX_DATA,       read_data_block },
505   { PZX_PAUSE,      read_paus_block },
506   { PZX_BROWSE,     read_brws_block },
507   { PZX_STOP,       read_stop_block },
508   { PZX_INLAY,      skip_block      },
509 
510 };
511 
512 static libspectrum_error
read_block_header(char * id,libspectrum_dword * data_length,const libspectrum_byte ** buffer,const libspectrum_byte * end)513 read_block_header( char *id, libspectrum_dword *data_length,
514 		   const libspectrum_byte **buffer,
515 		   const libspectrum_byte *end )
516 {
517   if( end - *buffer < 8 ) {
518     libspectrum_print_error(
519       LIBSPECTRUM_ERROR_CORRUPT,
520       "read_block_header: not enough data for block header"
521     );
522     return LIBSPECTRUM_ERROR_CORRUPT;
523   }
524 
525   memcpy( id, *buffer, 4 ); id[4] = '\0'; *buffer += 4;
526   *data_length = libspectrum_read_dword( buffer );
527 
528   return LIBSPECTRUM_ERROR_NONE;
529 }
530 
531 static libspectrum_error
read_block(libspectrum_tape * tape,const libspectrum_byte ** buffer,const libspectrum_byte * end,pzx_context * ctx)532 read_block( libspectrum_tape *tape, const libspectrum_byte **buffer,
533             const libspectrum_byte *end, pzx_context *ctx )
534 {
535   char id[5];
536   libspectrum_dword data_length;
537   libspectrum_error error;
538   size_t i; int done;
539 
540   error = read_block_header( id, &data_length, buffer, end );
541   if( error ) return error;
542 
543   if( end - *buffer < data_length ) {
544     libspectrum_print_error(
545       LIBSPECTRUM_ERROR_CORRUPT,
546       "read_block: block length goes beyond end of file"
547     );
548     return LIBSPECTRUM_ERROR_CORRUPT;
549   }
550 
551   done = 0;
552 
553   for( i = 0; !done && i < ARRAY_SIZE( read_blocks ); i++ ) {
554 
555     if( !memcmp( id, read_blocks[i].id, 4 ) ) {
556       error = read_blocks[i].function( tape, buffer, end, data_length, ctx );
557       if( error ) return error;
558       done = 1;
559     }
560 
561   }
562 
563   if( !done ) {
564     libspectrum_print_error( LIBSPECTRUM_ERROR_UNKNOWN,
565 			     "read_block: unknown block id '%s'", id );
566     *buffer += data_length;
567   }
568 
569   return LIBSPECTRUM_ERROR_NONE;
570 }
571 
572 /* The main load function */
573 
574 libspectrum_error
internal_pzx_read(libspectrum_tape * tape,const libspectrum_byte * buffer,size_t length)575 internal_pzx_read( libspectrum_tape *tape, const libspectrum_byte *buffer,
576                    size_t length )
577 {
578   libspectrum_error error;
579   const libspectrum_byte *end = buffer + length;
580   pzx_context *ctx;
581 
582   if( end - buffer < 8 ) {
583     libspectrum_print_error(
584       LIBSPECTRUM_ERROR_CORRUPT,
585       "internal_pzx_read: not enough data for PZX header"
586     );
587     return LIBSPECTRUM_ERROR_CORRUPT;
588   }
589 
590   if( memcmp( buffer, signature, signature_length ) ) {
591     libspectrum_print_error(
592       LIBSPECTRUM_ERROR_SIGNATURE,
593       "internal_pzx_read: wrong signature"
594     );
595     return LIBSPECTRUM_ERROR_SIGNATURE;
596   }
597 
598   ctx = libspectrum_new( pzx_context, 1 );
599   ctx->version = 0;
600 
601   while( buffer < end ) {
602     error = read_block( tape, &buffer, end, ctx );
603     if( error ) {
604       libspectrum_free( ctx );
605       return error;
606     }
607   }
608 
609   libspectrum_free( ctx );
610   return LIBSPECTRUM_ERROR_NONE;
611 }
612 
613 static libspectrum_error
pzx_read_data(const libspectrum_byte ** ptr,const libspectrum_byte * end,size_t length,libspectrum_byte ** data)614 pzx_read_data( const libspectrum_byte **ptr, const libspectrum_byte *end,
615 	       size_t length, libspectrum_byte **data )
616 {
617   /* Have we got enough bytes left in buffer? */
618   if( ( end - (*ptr) ) < (ptrdiff_t)(length) ) {
619     libspectrum_print_error( LIBSPECTRUM_ERROR_CORRUPT,
620 			     "pzx_read_data: not enough data in buffer" );
621     return LIBSPECTRUM_ERROR_CORRUPT;
622   }
623 
624   /* Allocate memory for the data; the check for *length is to avoid
625      the implementation-defined behaviour of malloc( 0 ) */
626   if( length ) {
627     *data = libspectrum_new( libspectrum_byte, length );
628     /* Copy the block data across, and move along */
629     memcpy( *data, *ptr, length ); *ptr += length;
630   } else {
631     *data = NULL;
632   }
633 
634   return LIBSPECTRUM_ERROR_NONE;
635 }
636 
637 static libspectrum_error
pzx_read_string(const libspectrum_byte ** ptr,const libspectrum_byte * end,char ** dest)638 pzx_read_string( const libspectrum_byte **ptr, const libspectrum_byte *end,
639 		 char **dest )
640 {
641   size_t length = 0;
642   char *ptr2;
643   size_t buffer_size = 64;
644   char *buffer = libspectrum_new( char, buffer_size );
645 
646   while( **ptr != '\0' && *ptr < end ) {
647     if( length == buffer_size ) {
648       buffer_size *= 2;
649       buffer = libspectrum_renew( char, buffer, buffer_size );
650     }
651     *(buffer + length++) = **ptr; (*ptr)++;
652   }
653 
654   /* Advance past the null terminator discarding any garbage */
655   *ptr = end;
656 
657   *dest = libspectrum_new( char, (length + 1) );
658 
659   strncpy( *dest, buffer, length );
660 
661   /* Null terminate the string */
662   (*dest)[length] = '\0';
663 
664   /* Translate line endings */
665   for( ptr2 = (*dest); *ptr2; ptr2++ ) if( *ptr2 == '\r' ) *ptr2 = '\n';
666 
667   libspectrum_free( buffer );
668 
669   return LIBSPECTRUM_ERROR_NONE;
670 }
671