File Coverage

blib/lib/Archive/Rgss3a.pm
Criterion Covered Total %
statement 91 91 100.0
branch 5 8 62.5
condition n/a
subroutine 11 11 100.0
pod 3 3 100.0
total 110 113 97.3


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