File Coverage

blib/lib/Archive/Rgssad.pm
Criterion Covered Total %
statement 73 87 83.9
branch 4 14 28.5
condition n/a
subroutine 10 12 83.3
pod 7 7 100.0
total 94 120 78.3


line stmt bran cond sub pod time code
1             package Archive::Rgssad;
2              
3 3     3   22707 use 5.010;
  3         9  
  3         114  
4 3     3   15 use strict;
  3         5  
  3         99  
5 3     3   14 use warnings FATAL => 'all';
  3         12  
  3         115  
6              
7 3     3   1665 use Archive::Rgssad::Entry;
  3         8  
  3         85  
8 3     3   1625 use Archive::Rgssad::Keygen 'keygen';
  3         9  
  3         2979  
9              
10             =head1 NAME
11              
12             Archive::Rgssad - Provide an interface to rgssad and rgss2a archive files.
13              
14             =head1 VERSION
15              
16             Version 0.11
17              
18             =cut
19              
20             our $VERSION = '0.11';
21              
22              
23             =head1 SYNOPSIS
24              
25             use Archive::Rgssad;
26              
27             my $rgssad = Archive::Rgssad->new('Game.rgssad');
28             for my $entry ($rgssad->entries) {
29             ...
30             }
31              
32             =head1 SUBROUTINES/METHODS
33              
34             =head2 Constructor
35              
36             =over 4
37              
38             =item new([$io])
39              
40             Create an empty rgssad archive. If an additional argument is passed, call
41             C to load the entries from it.
42              
43             =back
44              
45             =cut
46              
47             sub new {
48 1     1 1 13 my $class = shift;
49 1         5 my $self = {
50             magic => "RGSSAD\x00\x01",
51             seed => 0xDEADCAFE,
52             entries => []
53             };
54 1         3 bless $self, $class;
55 1 50       5 $self->load(shift) if @_;
56 1         3 return $self;
57             }
58              
59             =head2 Load and Save
60              
61             =over 4
62              
63             =item load($io)
64              
65             Load entries from C<$io>, which should be either a readable instance of
66             IO::Handle or its subclasses or a valid filepath.
67              
68             =cut
69              
70             sub load {
71 1     1 1 908 my $self = shift;
72 1         1 my $file = shift;
73 1 50       4 my $fh = ref($file) eq '' ? IO::File->new($file, 'r') : $file;
74 1         4 $fh->binmode(1);
75              
76 1         4 my @entries = ();
77 1         2 my $key = $self->{seed};
78              
79 1         4 $fh->read($_, 8);
80 1         19 until ($fh->eof) {
81 3         28 my $entry = Archive::Rgssad::Entry->new;
82 3         4 my ($buf, $len);
83              
84 3         8 $fh->read($buf, 4);
85 3         40 $len = unpack('V', $buf) ^ keygen($key);
86              
87 3         8 $fh->read($buf, $len);
88 3         34 $buf ^= pack('C*', map { $_ & 0xFF } keygen($key, $len));
  47         59  
89 3         10 $entry->path($buf);
90              
91 3         7 $fh->read($buf, 4);
92 3         32 $len = unpack('V', $buf) ^ keygen($key);
93              
94 3         11 $fh->read($buf, $len);
95 3         31 $_ = $key;
96 3         11 $buf ^= pack('V*', keygen($_, ($len + 3) / 4));
97 3         12 $entry->data(substr($buf, 0, $len));
98              
99 3         12 push @entries, $entry;
100             }
101              
102 1         14 $self->{entries} = \@entries;
103 1         3 $fh->close;
104             }
105              
106             =item save($io)
107              
108             Save the entries to C<$io>, which should be either a writable instance of
109             IO::Handle or its subclasses or a valid filepath.
110              
111             =back
112              
113             =cut
114              
115             sub save {
116 1     1 1 63 my $self = shift;
117 1         1 my $file = shift;
118 1 50       4 my $fh = ref($file) eq '' ? IO::File->new($file, 'w') : $file;
119 1         5 $fh->binmode(1);
120              
121 1         4 my $key = $self->{seed};
122              
123 1         6 $fh->write($self->{magic}, 8);
124 1         20 for my $entry ($self->entries) {
125 3         22 my ($buf, $len);
126              
127 3         9 $len = length $entry->path;
128 3         12 $fh->write(pack('V', $len ^ keygen($key)), 4);
129              
130 3         36 $buf = $entry->path ^ pack('C*', map { $_ & 0xFF } keygen($key, $len));
  47         52  
131 3         10 $fh->write($buf, $len);
132              
133 3         35 $len = length $entry->data;
134 3         9 $fh->write(pack('V', $len ^ keygen($key)), 4);
135              
136 3         31 $_ = $key;
137 3         8 $buf = $entry->data ^ pack('V*', keygen($_, ($len + 3) / 4));
138 3         10 $fh->write($buf, $len);
139             }
140              
141 1         18 $fh->close;
142             }
143              
144             =head2 Manipulate Entries
145              
146             =over 4
147              
148             =item entries
149              
150             Return all entries.
151              
152             =cut
153              
154             sub entries {
155 8     8 1 52 my $self = shift;
156 8         9 return @{$self->{entries}};
  8         39  
157             }
158              
159             =item get($path)
160              
161             Return all entries with specified path. In scalar context, just return the
162             first one.
163              
164             =cut
165              
166             sub get {
167 0     0 1 0 my $self = shift;
168 0         0 my $arg = shift;
169 0         0 my @ret = grep { $_->path eq $arg } $self->entries;
  0         0  
170 0 0       0 return wantarray ? @ret : $ret[0];
171             }
172              
173             =item add($path => $data, ...)
174              
175             =item add($entry, ...)
176              
177             Add new entries like $entry or Archive::Rgssad::Entry->new($path, $data).
178              
179             =cut
180              
181             sub add {
182 6     6 1 463 my $self = shift;
183 6         21 while (@_ > 0) {
184 6         9 $_ = shift;
185 6 50       17 if (ref eq 'Archive::Rgssad::Entry') {
186 0         0 push @{$self->{entries}}, $_;
  0         0  
187             } else {
188 6         8 push @{$self->{entries}}, Archive::Rgssad::Entry->new($_, shift);
  6         68  
189             }
190             }
191             }
192              
193             =item remove($path)
194              
195             =item remove($entry)
196              
197             If an entry is passed, remove the entries with the same path and data.
198             Otherwise, remove all entries with specified path.
199              
200             =back
201              
202             =cut
203              
204             sub remove {
205 0     0 1   my $self = shift;
206 0           my $arg = shift;
207 0 0         if (ref($arg) eq 'Archive::Rgssad::Entry') {
208 0 0         $self->{entries} = [grep { $_->path ne $arg->path ||
  0            
209             $_->data ne $arg->data } $self->entries];
210             } else {
211 0           $self->{entries} = [grep { $_->path ne $arg } $self->entries];
  0            
212             }
213             }
214              
215             =head1 AUTHOR
216              
217             Zejun Wu, C<< >>
218              
219             =head1 SUPPORT
220              
221             You can find documentation for this module with the perldoc command.
222              
223             perldoc Archive::Rgssad
224              
225              
226             You can also look for information at:
227              
228             =over 4
229              
230             =item * GitHub
231              
232             L
233              
234             =back
235              
236              
237             =head1 ACKNOWLEDGEMENTS
238              
239             A special thanks to leexuany, who shared his discovery about the rgssad format and published the decryption algorithm.
240              
241             =head1 LICENSE AND COPYRIGHT
242              
243             Copyright 2012 Zejun Wu.
244              
245             This program is free software; you can redistribute it and/or modify it
246             under the terms of either: the GNU General Public License as published
247             by the Free Software Foundation; or the Artistic License.
248              
249             See L for more information.
250              
251              
252             =cut
253              
254             1; # End of Archive::Rgssad