Merge branch 'master' into divVerent/farplanedist-sky-fix
[xonotic/netradiant.git] / plugins / archivezip / archive.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant 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    GtkRadiant 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 GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "idatastream.h"
23 #include "cmdlib.h"
24 #include "bytestreamutils.h"
25
26 #include "modulesystem.h"
27 #include "iarchive.h"
28
29 #include <algorithm>
30 #include "stream/filestream.h"
31 #include "container/array.h"
32 #include "archivelib.h"
33 #include "zlibstream.h"
34
35 class DeflatedArchiveFile : public ArchiveFile
36 {
37 CopiedString m_name;
38 FileInputStream m_istream;
39 SubFileInputStream m_substream;
40 DeflatedInputStream m_zipstream;
41 FileInputStream::size_type m_size;
42 public:
43 typedef FileInputStream::size_type size_type;
44 typedef FileInputStream::position_type position_type;
45
46 DeflatedArchiveFile( const char* name, const char* archiveName, position_type position, size_type stream_size, size_type file_size )
47         : m_name( name ), m_istream( archiveName ), m_substream( m_istream, position, stream_size ), m_zipstream( m_substream ), m_size( file_size ){
48 }
49
50 void release(){
51         delete this;
52 }
53 size_type size() const {
54         return m_size;
55 }
56 const char* getName() const {
57         return m_name.c_str();
58 }
59 InputStream& getInputStream(){
60         return m_zipstream;
61 }
62 };
63
64 class DeflatedArchiveTextFile : public ArchiveTextFile
65 {
66 CopiedString m_name;
67 FileInputStream m_istream;
68 SubFileInputStream m_substream;
69 DeflatedInputStream m_zipstream;
70 BinaryToTextInputStream<DeflatedInputStream> m_textStream;
71 public:
72 typedef FileInputStream::size_type size_type;
73 typedef FileInputStream::position_type position_type;
74
75 DeflatedArchiveTextFile( const char* name, const char* archiveName, position_type position, size_type stream_size )
76         : m_name( name ), m_istream( archiveName ), m_substream( m_istream, position, stream_size ), m_zipstream( m_substream ), m_textStream( m_zipstream ){
77 }
78
79 void release(){
80         delete this;
81 }
82 TextInputStream& getInputStream(){
83         return m_textStream;
84 }
85 };
86
87 #include "pkzip.h"
88
89 #include <map>
90 #include "string/string.h"
91 #include "fs_filesystem.h"
92
93
94 class ZipArchive : public Archive
95 {
96 class ZipRecord
97 {
98 public:
99 enum ECompressionMode
100 {
101         eStored,
102         eDeflated,
103 };
104 ZipRecord( unsigned int position, unsigned int compressed_size, unsigned int uncompressed_size, ECompressionMode mode )
105         : m_position( position ), m_stream_size( compressed_size ), m_file_size( uncompressed_size ), m_mode( mode ){
106 }
107 unsigned int m_position;
108 unsigned int m_stream_size;
109 unsigned int m_file_size;
110 ECompressionMode m_mode;
111 };
112
113 typedef GenericFileSystem<ZipRecord> ZipFileSystem;
114 ZipFileSystem m_filesystem;
115 CopiedString m_name;
116 FileInputStream m_istream;
117
118 bool read_record(){
119         zip_magic magic;
120         istream_read_zip_magic( m_istream, magic );
121         if ( !( magic == zip_root_dirent_magic ) ) {
122                 return false;
123         }
124         zip_version version_encoder;
125         istream_read_zip_version( m_istream, version_encoder );
126         zip_version version_extract;
127         istream_read_zip_version( m_istream, version_extract );
128         //unsigned short flags =
129         istream_read_int16_le( m_istream );
130         unsigned short compression_mode = istream_read_int16_le( m_istream );
131         if ( compression_mode != Z_DEFLATED && compression_mode != 0 ) {
132                 return false;
133         }
134         zip_dostime dostime;
135         istream_read_zip_dostime( m_istream, dostime );
136         //unsigned int crc32 =
137         istream_read_int32_le( m_istream );
138         unsigned int compressed_size = istream_read_uint32_le( m_istream );
139         unsigned int uncompressed_size = istream_read_uint32_le( m_istream );
140         unsigned int namelength = istream_read_uint16_le( m_istream );
141         unsigned short extras = istream_read_uint16_le( m_istream );
142         unsigned short comment = istream_read_uint16_le( m_istream );
143         //unsigned short diskstart =
144         istream_read_int16_le( m_istream );
145         //unsigned short filetype =
146         istream_read_int16_le( m_istream );
147         //unsigned int filemode =
148         istream_read_int32_le( m_istream );
149         unsigned int position = istream_read_int32_le( m_istream );
150
151         Array<char> filename( namelength + 1 );
152         m_istream.read( reinterpret_cast<FileInputStream::byte_type*>( filename.data() ), namelength );
153         filename[namelength] = '\0';
154
155         m_istream.seek( extras + comment, FileInputStream::cur );
156
157         if ( path_is_directory( filename.data() ) ) {
158                 m_filesystem[filename.data()] = 0;
159         }
160         else
161         {
162                 ZipFileSystem::entry_type& file = m_filesystem[filename.data()];
163                 if ( !file.is_directory() ) {
164                         globalOutputStream() << "Warning: zip archive " << makeQuoted( m_name.c_str() ) << " contains duplicated file: " << makeQuoted( filename.data() ) << "\n";
165                 }
166                 else
167                 {
168                         file = new ZipRecord( position, compressed_size, uncompressed_size, ( compression_mode == Z_DEFLATED ) ? ZipRecord::eDeflated : ZipRecord::eStored );
169                 }
170         }
171
172         return true;
173 }
174
175 bool read_pkzip(){
176         SeekableStream::position_type pos = pkzip_find_disk_trailer( m_istream );
177         if ( pos != 0 ) {
178                 zip_disk_trailer disk_trailer;
179                 m_istream.seek( pos );
180                 istream_read_zip_disk_trailer( m_istream, disk_trailer );
181                 if ( !( disk_trailer.z_magic == zip_disk_trailer_magic ) ) {
182                         return false;
183                 }
184
185                 m_istream.seek( disk_trailer.z_rootseek );
186                 for ( unsigned int i = 0; i < disk_trailer.z_entries; ++i )
187                 {
188                         if ( !read_record() ) {
189                                 return false;
190                         }
191                 }
192                 return true;
193         }
194         return false;
195 }
196 public:
197 ZipArchive( const char* name )
198         : m_name( name ), m_istream( name ){
199         if ( !m_istream.failed() ) {
200                 if ( !read_pkzip() ) {
201                         globalErrorStream() << "ERROR: invalid zip-file " << makeQuoted( name ) << '\n';
202                 }
203         }
204 }
205 ~ZipArchive(){
206         for ( ZipFileSystem::iterator i = m_filesystem.begin(); i != m_filesystem.end(); ++i )
207         {
208                 delete i->second.file();
209         }
210 }
211
212 bool failed(){
213         return m_istream.failed();
214 }
215
216 void release(){
217         delete this;
218 }
219 ArchiveFile* openFile( const char* name ){
220         ZipFileSystem::iterator i = m_filesystem.find( name );
221         if ( i != m_filesystem.end() && !i->second.is_directory() ) {
222                 ZipRecord* file = i->second.file();
223
224                 m_istream.seek( file->m_position );
225                 zip_file_header file_header;
226                 istream_read_zip_file_header( m_istream, file_header );
227                 if ( file_header.z_magic != zip_file_header_magic ) {
228                         globalErrorStream() << "error reading zip file " << makeQuoted( m_name.c_str() );
229                         return 0;
230                 }
231
232                 switch ( file->m_mode )
233                 {
234                 case ZipRecord::eStored:
235                         return StoredArchiveFile::create( name, m_name.c_str(), m_istream.tell(), file->m_stream_size, file->m_file_size );
236                 case ZipRecord::eDeflated:
237                         return new DeflatedArchiveFile( name, m_name.c_str(), m_istream.tell(), file->m_stream_size, file->m_file_size );
238                 }
239         }
240         return 0;
241 }
242 ArchiveTextFile* openTextFile( const char* name ){
243         ZipFileSystem::iterator i = m_filesystem.find( name );
244         if ( i != m_filesystem.end() && !i->second.is_directory() ) {
245                 ZipRecord* file = i->second.file();
246
247                 m_istream.seek( file->m_position );
248                 zip_file_header file_header;
249                 istream_read_zip_file_header( m_istream, file_header );
250                 if ( file_header.z_magic != zip_file_header_magic ) {
251                         globalErrorStream() << "error reading zip file " << makeQuoted( m_name.c_str() );
252                         return 0;
253                 }
254
255                 switch ( file->m_mode )
256                 {
257                 case ZipRecord::eStored:
258                         return StoredArchiveTextFile::create( name, m_name.c_str(), m_istream.tell(), file->m_stream_size );
259                 case ZipRecord::eDeflated:
260                         return new DeflatedArchiveTextFile( name, m_name.c_str(), m_istream.tell(), file->m_stream_size );
261                 }
262         }
263         return 0;
264 }
265 bool containsFile( const char* name ){
266         ZipFileSystem::iterator i = m_filesystem.find( name );
267         return i != m_filesystem.end() && !i->second.is_directory();
268 }
269 void forEachFile( VisitorFunc visitor, const char* root ){
270         m_filesystem.traverse( visitor, root );
271 }
272 };
273
274 Archive* OpenArchive( const char* name ){
275         return new ZipArchive( name );
276 }
277
278 #if 0
279
280 class TestZip
281 {
282 class TestVisitor : public Archive::IVisitor
283 {
284 public:
285 void visit( const char* name ){
286         int bleh = 0;
287 }
288 };
289 public:
290 TestZip(){
291         testzip( "c:/quake3/baseq3/mapmedia.pk3", "textures/radiant/notex.tga" );
292 }
293
294 void testzip( const char* name, const char* filename ){
295         Archive* archive = OpenArchive( name );
296         ArchiveFile* file = archive->openFile( filename );
297         if ( file != 0 ) {
298                 unsigned char buffer[4096];
299                 std::size_t count = file->getInputStream().read( (InputStream::byte_type*)buffer, 4096 );
300                 file->release();
301         }
302         TestVisitor visitor;
303         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "" );
304         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "" );
305         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 1 ), "" );
306         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eDirectories, 1 ), "" );
307         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures" );
308         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures/" );
309         archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 2 ), "" );
310         archive->release();
311 }
312 };
313
314 TestZip g_TestZip;
315
316 #endif