File Coverage

blib/lib/EBook/MOBI/MobiPerl/MobiHeader.pm
Criterion Covered Total %
statement 119 279 42.6
branch 8 52 15.3
condition 1 12 8.3
subroutine 26 44 59.0
pod 0 38 0.0
total 154 425 36.2


line stmt bran cond sub pod time code
1 9     9   41 use strict;
  9         18  
  9         1704  
2              
3             # Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
4             #
5             # MobiPerl/MobiHeader.pm, Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
6             #
7             # This program is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 2 of the License, or
10             # (at your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19              
20              
21             #
22             # This is a patch of a function in Palm::Doc to be able to handle
23             # DRM:ed files.
24             #
25              
26              
27             package EBook::MOBI::Palm::Doc;
28              
29             our $VERSION = 2011.11.26;
30              
31             sub _parse_headerrec($) {
32 0     0   0 my $record = shift;
33 0 0       0 return undef unless exists $record->{'data'};
34              
35             # Doc header is minimum of 16 bytes
36 0 0       0 return undef if length $record->{'data'} < 16;
37              
38              
39             my ($version,$spare,$ulen, $records, $recsize, $position)
40 0         0 = unpack( 'n n N n n N', $record->{'data'} );
41              
42             # the header is followed by a list of record sizes. We don't use
43             # this since we can guess the sizes pretty easily by looking at
44             # the actual records.
45              
46             # According to the spec, $version is either 1 (uncompressed)
47             # or 2 (compress), while spare is always zero. AportisDoc supposedly sets
48             # spare to something else, so screw AportisDoc.
49              
50             #
51             # $version is 17480 for DRM:ed MobiPocket books
52             #
53             # So comment away the check
54             ### return undef if $version != DOC_UNCOMPRESSED and $version != DOC_COMPRESSED;
55              
56 0 0       0 return undef if $spare != 0;
57              
58 0         0 $record->{'version'} = $version;
59 0         0 $record->{'length'} = $ulen;
60 0         0 $record->{'records'} = $records;
61 0         0 $record->{'recsize'} = $recsize;
62 0         0 $record->{'position'} = $position;
63              
64 0         0 return $record;
65             }
66              
67              
68              
69              
70             package EBook::MOBI::MobiPerl::MobiHeader;
71              
72 9     9   6308 use FindBin qw($RealBin);
  9         8791  
  9         1159  
73 9     9   5970 use lib "$RealBin";
  9         5460  
  9         51  
74              
75 9     9   6324 use EBook::MOBI::MobiPerl::EXTH;
  9         26  
  9         292  
76              
77 9     9   47 use strict;
  9         15  
  9         26941  
78              
79             #
80             # TYPE: 2=book
81             #
82             our $VERSION = '0.71'; # TRIAL VERSION: Should be 3 or 4
83             #
84             # CODEPAGE: utf-8: 65001; westerner: 1252
85             #
86             # IMAGERECORDINDEX: the index of the first record with image in it
87             #
88             # Language seems to be stored in 4E: en-us 0409
89             # sv 041d
90             # fi 000b
91             # en 0009
92             #
93             # 0x50 and 0x54 might also be some kind of language specification
94             #
95              
96             #
97             # 0000: MOBI header-size type codepage
98             # 0010: unique-id version FFFFFFFF FFFFFFFF
99             #
100             # header-size = E4 if version = 4
101             # type = 2 - book
102             # codepage = 1252 - westerner
103             # unique-id = seems to be random
104             # version = 3 or 4
105             #
106             # 0040: data4 exttitleoffset exttitlelength language
107             # 0050: data1 data2 data3 nonbookrecordpointer
108             # 0060: data5
109             #
110             # data1 and data2 id 09 in Oxford dictionary. The same as languange...
111             # nonbookrecordpointer in Oxford is 0x7167. data5 is 0x7157
112             # data3 is 05 in Oxford so maybe this is the version?
113             #
114             #pdurrant:
115             #
116             # 0040: nonbookrecordpointer exttitleoffset exttitlelength language
117             # 0050: data1 data2 data3 firstimagerecordpointer
118             # 0060: data5
119             #
120              
121              
122             my %langmap = (
123             "es" => 0x000a,
124             "sv" => 0x001d,
125             "sv-se" => 0x041d,
126             "sv-fi" => 0x081d,
127             "fi" => 0x000b,
128             "en" => 0x0009,
129             "en-au" => 0x0C09,
130             "en-bz" => 0x2809,
131             "en-ca" => 0x1009,
132             "en-cb" => 0x2409,
133             "en-ie" => 0x1809,
134             "en-jm" => 0x2009,
135             "en-nz" => 0x1409,
136             "en-ph" => 0x3409,
137             "en-za" => 0x1c09,
138             "en-tt" => 0x2c09,
139             "en-us" => 0x0409,
140             "en-gb" => 0x0809,
141             "en-zw" => 0x3009,
142             "da" => 0x0006,
143             "da-dk" => 0x0406,
144             "da" => 0x0006,
145             "da" => 0x0006,
146             "nl" => 0x0013,
147             "nl-be" => 0x0813,
148             "nl-nl" => 0x0413,
149             "fi" => 0x000b,
150             "fi-fi" => 0x040b,
151             "fr" => 0x000c,
152             "fr-fr" => 0x040c,
153             "de" => 0x0007,
154             "de-at" => 0x0c07,
155             "de-de" => 0x0407,
156             "de-lu" => 0x1007,
157             "de-ch" => 0x0807,
158             "no" => 0x0014,
159             "nb-no" => 0x0414,
160             "nn-no" => 0x0814,
161             "zh-cn" => 0x0804,
162             );
163              
164              
165             my %mainlanguage = (
166             0 => "NEUTRAL",
167             54 => "AFRIKAANS",
168             28 => "ALBANIAN",
169             1 => "ARABIC",
170             43 => "ARMENIAN",
171             77 => "ASSAMESE",
172             44 => "AZERI",
173             45 => "BASQUE",
174             35 => "BELARUSIAN",
175             69 => "BENGALI",
176             2 => "BULGARIAN",
177             3 => "CATALAN",
178             4 => "CHINESE",
179             26 => "CROATIAN",
180             5 => "CZECH",
181             6 => "DANISH",
182             19 => "DUTCH",
183             9 => "ENGLISH",
184             37 => "ESTONIAN",
185             56 => "FAEROESE",
186             41 => "FARSI",
187             11 => "FINNISH",
188             12 => "FRENCH",
189             55 => "GEORGIAN",
190             7 => "GERMAN",
191             8 => "GREEK",
192             71 => "GUJARATI",
193             13 => "HEBREW",
194             57 => "HINDI",
195             14 => "HUNGARIAN",
196             15 => "ICELANDIC",
197             33 => "INDONESIAN",
198             16 => "ITALIAN",
199             17 => "JAPANESE",
200             75 => "KANNADA",
201             63 => "KAZAK",
202             87 => "KONKANI",
203             18 => "KOREAN",
204             38 => "LATVIAN",
205             39 => "LITHUANIAN",
206             47 => "MACEDONIAN",
207             62 => "MALAY",
208             76 => "MALAYALAM",
209             58 => "MALTESE",
210             78 => "MARATHI",
211             97 => "NEPALI",
212             20 => "NORWEGIAN",
213             72 => "ORIYA",
214             21 => "POLISH",
215             22 => "PORTUGUESE",
216             70 => "PUNJABI",
217             23 => "RHAETOROMANIC",
218             24 => "ROMANIAN",
219             25 => "RUSSIAN",
220             59 => "SAMI",
221             79 => "SANSKRIT",
222             26 => "SERBIAN",
223             27 => "SLOVAK",
224             36 => "SLOVENIAN",
225             46 => "SORBIAN",
226             10 => "SPANISH",
227             48 => "SUTU",
228             65 => "SWAHILI",
229             29 => "SWEDISH",
230             73 => "TAMIL",
231             68 => "TATAR",
232             74 => "TELUGU",
233             30 => "THAI",
234             49 => "TSONGA",
235             50 => "TSWANA",
236             31 => "TURKISH",
237             34 => "UKRAINIAN",
238             32 => "URDU",
239             67 => "UZBEK",
240             42 => "VIETNAMESE",
241             52 => "XHOSA",
242             53 => "ZULU",
243             );
244              
245              
246             my $langmap = {};
247             $langmap->{"ENGLISH"} = {
248             1 => "ENGLISH_US",
249             2 => "ENGLISH_UK",
250             3 => "ENGLISH_AUS",
251             4 => "ENGLISH_CAN",
252             5 => "ENGLISH_NZ",
253             6 => "ENGLISH_EIRE",
254             7 => "ENGLISH_SOUTH_AFRICA",
255             8 => "ENGLISH_JAMAICA",
256             10 => "ENGLISH_BELIZE",
257             11 => "ENGLISH_TRINIDAD",
258             12 => "ENGLISH_ZIMBABWE",
259             13 => "ENGLISH_PHILIPPINES",
260             };
261              
262             my %sublanguage = (
263             0 => "NEUTRAL",
264             1 => "ARABIC_SAUDI_ARABIA",
265             2 => "ARABIC_IRAQ",
266             3 => "ARABIC_EGYPT",
267             4 => "ARABIC_LIBYA",
268             5 => "ARABIC_ALGERIA",
269             6 => "ARABIC_MOROCCO",
270             7 => "ARABIC_TUNISIA",
271             8 => "ARABIC_OMAN",
272             9 => "ARABIC_YEMEN",
273             10 => "ARABIC_SYRIA",
274             11 => "ARABIC_JORDAN",
275             12 => "ARABIC_LEBANON",
276             13 => "ARABIC_KUWAIT",
277             14 => "ARABIC_UAE",
278             15 => "ARABIC_BAHRAIN",
279             16 => "ARABIC_QATAR",
280             1 => "AZERI_LATIN",
281             2 => "AZERI_CYRILLIC",
282             1 => "CHINESE_TRADITIONAL",
283             2 => "CHINESE_SIMPLIFIED",
284             3 => "CHINESE_HONGKONG",
285             4 => "CHINESE_SINGAPORE",
286             1 => "DUTCH",
287             2 => "DUTCH_BELGIAN",
288             1 => "FRENCH",
289             2 => "FRENCH_BELGIAN",
290             3 => "FRENCH_CANADIAN",
291             4 => "FRENCH_SWISS",
292             5 => "FRENCH_LUXEMBOURG",
293             6 => "FRENCH_MONACO",
294             1 => "GERMAN",
295             2 => "GERMAN_SWISS",
296             3 => "GERMAN_AUSTRIAN",
297             4 => "GERMAN_LUXEMBOURG",
298             5 => "GERMAN_LIECHTENSTEIN",
299             1 => "ITALIAN",
300             2 => "ITALIAN_SWISS",
301             1 => "KOREAN",
302             1 => "LITHUANIAN",
303             1 => "MALAY_MALAYSIA",
304             2 => "MALAY_BRUNEI_DARUSSALAM",
305             1 => "NORWEGIAN_BOKMAL",
306             2 => "NORWEGIAN_NYNORSK",
307             2 => "PORTUGUESE",
308             1 => "PORTUGUESE_BRAZILIAN",
309             2 => "SERBIAN_LATIN",
310             3 => "SERBIAN_CYRILLIC",
311             1 => "SPANISH",
312             2 => "SPANISH_MEXICAN",
313             4 => "SPANISH_GUATEMALA",
314             5 => "SPANISH_COSTA_RICA",
315             6 => "SPANISH_PANAMA",
316             7 => "SPANISH_DOMINICAN_REPUBLIC",
317             8 => "SPANISH_VENEZUELA",
318             9 => "SPANISH_COLOMBIA",
319             10 => "SPANISH_PERU",
320             11 => "SPANISH_ARGENTINA",
321             12 => "SPANISH_ECUADOR",
322             13 => "SPANISH_CHILE",
323             14 => "SPANISH_URUGUAY",
324             15 => "SPANISH_PARAGUAY",
325             16 => "SPANISH_BOLIVIA",
326             17 => "SPANISH_EL_SALVADOR",
327             18 => "SPANISH_HONDURAS",
328             19 => "SPANISH_NICARAGUA",
329             20 => "SPANISH_PUERTO_RICO",
330             1 => "SWEDISH",
331             2 => "SWEDISH_FINLAND",
332             1 => "UZBEK_LATIN",
333             2 => "UZBEK_CYRILLIC",
334             );
335              
336             my %booktypedesc = (2 => "BOOK",
337             3 => "PALMDOC",
338             4 => "AUDIO",
339             257 => "NEWS",
340             258 => "NEWS_FEED",
341             259 => "NEWS_MAGAZINE",
342             513 => "PICS",
343             514 => "WORD",
344             515 => "XLS",
345             516 => "PPT",
346             517 => "TEXT",
347             518 => "HTML",
348             );
349              
350             sub new {
351 3     3 0 13 my $this = shift;
352 3   33     21 my $class = ref($this) || $this;
353 3         43 bless {
354             TYPE => 2,
355             VERSION => 4,
356             CODEPAGE => 1252,
357             TITLE => "Unspecified Title",
358             AUTHOR => "Unspecified Author",
359             PUBLISHER => "",
360             DESCRIPTION => "",
361             SUBJECT => "",
362             IMAGERECORDINDEX => 0,
363             LANGUAGE => "en",
364             COVEROFFSET => -1,
365             THUMBOFFSET => -1,
366             @_
367             }, $class;
368             }
369              
370             sub set_author {
371 3     3 0 4 my $self = shift;
372 3         6 my $val = shift;
373 3         9 $self->{AUTHOR} = $val;
374             }
375              
376             sub get_author {
377 6     6 0 8 my $self = shift;
378 6         12 return $self->{AUTHOR};
379             }
380              
381             sub set_cover_offset {
382 0     0 0 0 my $self = shift;
383 0         0 my $val = shift;
384 0         0 $self->{COVEROFFSET} = $val;
385             }
386              
387             sub get_cover_offset {
388 6     6 0 11 my $self = shift;
389 6         11 return $self->{COVEROFFSET};
390             }
391              
392             sub set_thumb_offset {
393 0     0 0 0 my $self = shift;
394 0         0 my $val = shift;
395 0         0 $self->{THUMBOFFSET} = $val;
396             }
397              
398             sub get_thumb_offset {
399 6     6 0 6 my $self = shift;
400 6         9 return $self->{THUMBOFFSET};
401             }
402              
403             sub set_publisher {
404 0     0 0 0 my $self = shift;
405 0         0 my $val = shift;
406 0         0 $self->{PUBLISHER} = $val;
407             }
408              
409             sub get_publisher {
410 6     6 0 13 my $self = shift;
411 6         11 return $self->{PUBLISHER};
412             }
413              
414             sub set_description {
415 0     0 0 0 my $self = shift;
416 0         0 my $val = shift;
417 0         0 $self->{DESCRIPTION} = $val;
418             }
419              
420             sub get_description {
421 6     6 0 8 my $self = shift;
422 6         12 return $self->{DESCRIPTION};
423             }
424              
425             sub set_subject {
426 0     0 0 0 my $self = shift;
427 0         0 my $val = shift;
428 0         0 $self->{SUBJECT} = $val;
429             }
430              
431             sub get_subject {
432 6     6 0 7 my $self = shift;
433 6         10 return $self->{SUBJECT};
434             }
435              
436             sub set_language {
437 0     0 0 0 my $self = shift;
438 0         0 my $val = shift;
439 0         0 $self->{LANGUAGE} = $val;
440             }
441              
442             sub get_language {
443 3     3 0 5 my $self = shift;
444 3         7 return $self->{LANGUAGE};
445             }
446              
447             sub set_title {
448 3     3 0 6 my $self = shift;
449 3         5 my $val = shift;
450 3         15 $self->{TITLE} = $val;
451             }
452              
453             sub get_title {
454 6     6 0 9 my $self = shift;
455 6         15 return $self->{TITLE};
456             }
457              
458             sub set_image_record_index {
459 3     3 0 5 my $self = shift;
460 3         4 my $val = shift;
461 3         8 $self->{IMAGERECORDINDEX} = $val;
462             }
463              
464             sub get_image_record_index {
465 3     3 0 4 my $self = shift;
466 3         10 return $self->{IMAGERECORDINDEX};
467             }
468              
469             sub get_type {
470 3     3 0 5 my $self = shift;
471 3         11 return $self->{TYPE};
472             }
473              
474             sub get_codepage {
475 3     3 0 5 my $self = shift;
476 3         12 return $self->{CODEPAGE};
477             }
478              
479             sub set_codepage {
480 3     3 0 4 my $self = shift;
481 3         6 my $value = shift;
482 3         8 $self->{CODEPAGE} = $value;
483             }
484              
485             sub set_version {
486 0     0 0 0 my $self = shift;
487 0         0 my $val = shift;
488 0         0 $self->{VERSION} = $val;
489             }
490              
491             sub get_version {
492 96     96 0 100 my $self = shift;
493 96         197 return $self->{VERSION};
494             }
495              
496             sub get_unique_id {
497 3     3 0 5 my $self = shift;
498 3         8 my $r1 = int (rand (256));
499 3         5 my $r2 = int (rand (256));
500 3         6 my $r3 = int (rand (256));
501 3         6 my $r4 = int (rand (256));
502 3         10 my $res = $r1+$r2*256+$r3*256*256+$r4*256*256*256;
503 3         8 return $res;
504             }
505              
506             sub get_header_size {
507 93     93 0 104 my $self = shift;
508 93         86 my $res = 0x74;
509 93 50       154 if ($self->get_version () == 4) {
510 93         90 $res = 0xE4;
511             }
512 93         201 return $res;
513             }
514              
515             sub get_extended_header_data {
516 6     6 0 7 my $self = shift;
517 6         77 my $author = $self->get_author ();
518              
519 6         34 my $eh = new EBook::MOBI::MobiPerl::EXTH;
520 6         21 $eh->set ("author", $author);
521 6         17 my $pub = $self->get_publisher ();
522 6 50       15 $eh->set ("publisher", $pub) if $pub;
523              
524 6         15 my $desc = $self->get_description ();
525 6 50       19 $eh->set ("description", $desc) if $desc;
526              
527 6         14 my $subj = $self->get_subject ();
528 6 50       13 $eh->set ("subject", $subj) if $subj;
529              
530 6         20 my $coffset = $self->get_cover_offset ();
531 6 50       13 if ($coffset >= 0) {
532             ## my $data = pack ("N", $coffset);
533             ## print STDERR "COFFSET:$coffset:$data:\n";
534 0         0 $eh->set ("coveroffset", $coffset);
535             }
536              
537 6         17 my $toffset = $self->get_thumb_offset ();
538 6 50       15 if ($toffset >= 0) {
539             ## my $data = pack ("N", $toffset);
540             ## my $hex = MobiPerl::Util::iso2hex ($data);
541             ## print STDERR "TOFFSET:$toffset:$hex\n";
542 0         0 $eh->set ("thumboffset", $toffset);
543             }
544              
545             ## $eh->set ("hasfakecover", pack ("N", 0));
546              
547 6         19 return $eh->get_data ();
548             }
549              
550             sub get_data {
551 3     3 0 5 my $self = shift;
552 3         12 my $res = "";
553              
554 3         5 my $vie1 = 0; # 0x11 Alice 0x0D Rosenbaum 0xFFFFFFFF, Around the world
555 3         6 $vie1 = 0xFFFFFFFF;
556              
557 3         4 my $vie2 = 0x04; # had this, around the world have 0x01
558              
559 3         5 my $use_extended_header = 1;
560 3         5 my $extended_header_flag = 0x00;
561 3 50       9 if ($use_extended_header) {
562 3         4 $extended_header_flag = 0x50; # At MOBI+0x70
563             }
564              
565 3         12 my $extended_title_offset = $self->get_header_size () + 16 + length ($self->get_extended_header_data ());
566 3         12 my $extended_title_length = length ($self->get_title ());
567              
568             #print STDERR "MOBIHDR: imgrecpointer: ", $self->get_image_record_index (), "\n";
569              
570 3         9 $res .= pack ("a*NNNNN", "MOBI",
571             $self->get_header_size (),
572             $self->get_type (),
573             $self->get_codepage (),
574             $self->get_unique_id (),
575             $self->get_version ());
576              
577 3         6 $res .= pack ("NN", 0xFFFFFFFF, 0xFFFFFFFF);
578 3         10 $res .= pack ("NNNN", 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
579 3         6 $res .= pack ("NNNN", 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
580 3         9 my $langnumber = $self->get_language ();
581 3 50       19 if (defined $langmap{$langnumber}) {
582 3         7 $langnumber = $langmap{$langnumber};
583             }
584 3         8 $res .= pack ("NNNN", $vie1, $extended_title_offset,
585             $extended_title_length, $langnumber);
586 3         10 $res .= pack ("NNNN", 0xFFFFFFFF, 0xFFFFFFFF, $vie2, $self->get_image_record_index ());
587 3         6 $res .= pack ("NNNN", 0xFFFFFFFF, 0, 0xFFFFFFFF, 0);
588 3         6 $res .= pack ("N", $extended_header_flag);
589             # print STDERR "MOBIHEADERSIZE: $mobiheadersize " . length ($header->{'data'}). "\n";
590 3         8 while (length ($res) < $self->get_header_size ()) {
591             ### print STDERR "LEN: " . length ($res) . " - " . $self->get_header_size () . "\n";
592 84         144 $res .= pack ("N", 0);
593             }
594              
595 3         11 substr ($res, 0x94, 4, pack ("N", 0xFFFFFFFF));
596 3         8 substr ($res, 0x98, 4, pack ("N", 0xFFFFFFFF));
597              
598 3         13 substr ($res, 0xb0, 4, pack ("N", 0xFFFFFFFF));
599             # maybe pointer to last image or to thumbnail image record
600              
601 3         5 substr ($res, 0xb8, 4, pack ("N", 0xFFFFFFFF)); # record pointer
602 3         7 substr ($res, 0xc0, 4, pack ("N", 0xFFFFFFFF)); # record pointer
603 3         6 substr ($res, 0xc8, 4, pack ("N", 0xFFFFFFFF)); # record pointer
604              
605             #
606             # unknown
607             #
608              
609 3         3 substr ($res, 0xd0, 4, pack ("N", 0xFFFFFFFF));
610 3         5 substr ($res, 0xd8, 4, pack ("N", 0xFFFFFFFF));
611 3         4 substr ($res, 0xdc, 4, pack ("N", 0xFFFFFFFF));
612              
613              
614 3         7 $res .= $self->get_extended_header_data ();
615 3         8 $res .= pack ("a*", $self->get_title ());
616            
617             #
618             # Why?
619             #
620 3         12 for (1..48) {
621 144         193 $res .= pack ("N", 0);
622             }
623 3         14 return $res;
624             }
625              
626              
627             #
628             # Help function that is not dependent on object state
629             #
630              
631             sub get_extended_title {
632 0     0 0   my $h = shift;
633 0           my $len = length ($h);
634 0           my ($exttitleoffset) = unpack ("N", substr ($h, 0x44));
635 0           my ($exttitlelength) = unpack ("N", substr ($h, 0x48));
636 0           my ($title) = unpack ("a$exttitlelength", substr ($h, $exttitleoffset-16));
637 0           return $title;
638             }
639              
640             sub set_extended_title {
641 0     0 0   my $mh = shift;
642 0           my $len = length ($mh);
643 0           my $title = shift;
644 0           my $titlelen = length ($title);
645 0           my ($exttitleoffset) = unpack ("N", substr ($mh, 0x44));
646 0           my ($exttitlelength) = unpack ("N", substr ($mh, 0x48));
647 0           my ($version) = unpack ("N", substr ($mh, 0x14));
648              
649 0           my $res = substr ($mh, 0, $exttitleoffset-16);
650 0           my $aftertitle = substr ($mh, $exttitleoffset-16+$exttitlelength);
651              
652 0           $res .= $title;
653              
654 0           my $diff = $titlelen - $exttitlelength;
655 0 0         if ($diff <= 0) {
656 0           foreach ($diff .. -1) {
657 0           $res .= pack ("C", 0);
658 0           $diff++;
659             }
660             } else {
661 0           my $comp = $diff % 4;
662 0 0         if ($comp) {
663 0           foreach ($comp .. 3) {
664 0           $res .= pack ("C", 0);
665 0           $diff++;
666             }
667             }
668             }
669 0           $res = fix_pointers ($res, $exttitleoffset, $diff);
670              
671 0           $res .= $aftertitle;
672 0           substr ($res, 0x48, 4, pack ("N", $titlelen));
673              
674 0           return $res;
675             }
676              
677             sub get_mh_language_code {
678 0     0 0   my $h = shift;
679 0           my $len = length ($h);
680 0           my ($lang) = unpack ("N", substr ($h, 0x4C));
681 0           return $lang;
682             }
683              
684             sub get_language_desc {
685 0     0 0   my $code = shift;
686 0           my $lid = $code & 0xFF;
687 0           my $lang = $mainlanguage{$lid};
688 0           my $sublid = ($code >> 10) & 0xFF;
689 0           my $sublang = $langmap->{$lang}->{$sublid};
690 0           my $res = "";
691 0           $res .= "$lang";
692 0           $res .= " - $sublang";
693 0           return $res;
694             }
695              
696              
697             sub set_booktype {
698 0     0 0   my $mh = shift;
699 0           my $len = length ($mh);
700 0           my $type = shift;
701 0           substr ($mh, 0x08, 4, pack ("N", $type));
702 0           return $mh;
703             }
704              
705             sub set_language_in_header {
706 0     0 0   my $mh = shift;
707 0           my $len = length ($mh);
708 0           my $lan = shift;
709              
710 0           my $langnumber = $lan;
711 0 0         if (defined $langmap{$langnumber}) {
712 0           $langnumber = $langmap{$langnumber};
713             }
714              
715 0           substr ($mh, 0x4C, 4, pack ("N", $langnumber));
716 0           return $mh;
717             }
718              
719             sub add_exth_data {
720 0     0 0   my $h = shift;
721 0           my $type = shift;
722 0           my $data = shift;
723 0           return set_exth_data ($h, $type, $data, 1);
724             }
725              
726             sub set_exth_data {
727 0     0 0   my $h = shift;
728 0           my $len = length ($h);
729 0           my $type = shift;
730 0           my $data = shift;
731 0           my $addflag = shift;
732 0           my $delexthindex = shift;
733 0           my $res = $h;
734 0 0         if (defined $data) {
735 0           print STDERR "Setting extended header data: $type - $data\n";
736             } else {
737 0           print STDERR "Deleting extended header data of type: $type - $delexthindex\n";
738             }
739              
740 0           my ($doctype, $length, $htype, $codepage, $uniqueid, $ver) =
741             unpack ("a4NNNNN", $h);
742              
743 0           my ($exthflg) = unpack ("N", substr ($h, 0x70));
744              
745 0           my $exth = substr ($h, $length);
746 0           my $prefix = substr ($h, 0, $length);
747 0           my $suffix;
748 0           my $mobidiff = 0;
749 0           my $eh;
750 0           my $exthlen = 0;
751 0 0         if ($exthflg & 0x40) {
752 0           my ($doctype, $exthlen1, $n_items) = unpack ("a4NN", $exth);
753 0           $exthlen = $exthlen1;
754 0           $suffix = substr ($exth, $exthlen);
755 0           $eh = new MobiPerl::EXTH ($exth);
756             } else {
757 0           $eh = new MobiPerl::EXTH ();
758 0           $suffix = $exth;
759 0           substr ($prefix, 0x70, 4, pack ("N", $exthflg | 0x40));
760             # pdurrant: as well as setting the exthflg, we need make sure the version >= 4
761 0 0         if ($ver < 4) {
762 0           substr($prefix, 0x14, 4, pack("N",4));
763             }
764              
765             # pdurrant: and if the mobi header is short, we need to increase its size
766 0 0         if ($length < 0xE8) {
767 0 0         if ($length < 0x9C) {
768             #get rid of any old bad data inappropriate for new header
769 0           $prefix = substr($prefix, 0, 0x74);
770             }
771 0           $prefix .= substr(pack("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF), length($prefix)-0xE8);
772 0           $mobidiff = 0xE8-$length;
773 0           substr ($prefix, 4, 4, pack ("N", 0xE8));
774             }
775             }
776              
777 0 0         if ($addflag) {
778 0           $eh->add ($type, $data);
779             } else {
780 0 0         if (defined $data) {
781 0           $eh->set ($type, $data);
782             } else {
783 0           $eh->delete ($type, $delexthindex);
784             }
785             }
786 0           print STDERR "GETSTRING: ", $eh->get_string ();
787              
788             #
789             # Fix DRM and TITLE info pointers...
790             #
791            
792 0           my $exthdata = $eh->get_data ();
793              
794 0           my $exthdiff = length ($exthdata)-$exthlen;
795 0 0         if ($exthdiff <= 0) {
796 0           foreach ($exthdiff .. -1) {
797 0           $exthdata .= pack ("C", 0);
798 0           $exthdiff++;
799             }
800             }
801              
802 0           $res = $prefix . $exthdata . $suffix;
803              
804 0           $res = fix_pointers ($res, $length, $mobidiff+$exthdiff);
805              
806 0           return $res;
807             }
808              
809              
810             sub fix_pointers {
811 0     0 0   my $mh = shift;
812 0           my $startblock = shift;
813 0           my $offset = shift;
814              
815             #
816             # Fix pointers to long title and to DRM record
817             #
818              
819 0           my ($exttitleoffset) = unpack ("N", substr ($mh, 0x44));
820 0 0 0       if ($exttitleoffset > $startblock and $offset > 0) {
821 0           substr ($mh, 0x44, 4, pack ("N", $exttitleoffset+$offset));
822             }
823             # pdurrant
824 0           my ($ehlen) = unpack ("N", substr ($mh,0x04));
825 0 0         if ($ehlen > 0x98 ) { #pdurrant
826 0           my ($drmoffset) = unpack ("N", substr ($mh, 0x98));
827 0 0 0       if ($drmoffset != 0xFFFFFFFF and
      0        
828             $drmoffset > $startblock and $offset > 0) {
829 0           substr ($mh, 0x98, 4, pack ("N", $drmoffset+$offset));
830             }
831             }
832 0           return $mh;
833             }
834              
835             sub get_booktype_desc {
836 0     0 0   my $type = shift;
837 0           my $res = $type;
838 0 0         if (defined $booktypedesc{$type}) {
839 0           $res = $booktypedesc{$type};
840             }
841 0           return $res;
842             }
843              
844              
845              
846             return 1;
847              
848             __END__