1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // Copyright (C) 2009-2012 Christian Stehno
3 // This file is part of the "Irrlicht Engine".
4 // For conditions of distribution and use, see copyright notice in irrlicht.h
5 // Based on the NPK reader from Irrlicht
6 
7 #include "CNPKReader.h"
8 
9 #ifdef __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_
10 
11 #include "os.h"
12 #include "coreutil.h"
13 
14 #ifdef _DEBUG
15 #define IRR_DEBUG_NPK_READER
16 #endif
17 
18 namespace irr
19 {
20 namespace io
21 {
22 
23 namespace
24 {
isHeaderValid(const SNPKHeader & header)25 	bool isHeaderValid(const SNPKHeader& header)
26 	{
27 		const c8* const tag = header.Tag;
28 		return tag[0] == '0' &&
29 			   tag[1] == 'K' &&
30 			   tag[2] == 'P' &&
31 			   tag[3] == 'N';
32 	}
33 } // end namespace
34 
35 
36 //! Constructor
CArchiveLoaderNPK(io::IFileSystem * fs)37 CArchiveLoaderNPK::CArchiveLoaderNPK( io::IFileSystem* fs)
38 : FileSystem(fs)
39 {
40 #ifdef _DEBUG
41 	setDebugName("CArchiveLoaderNPK");
42 #endif
43 }
44 
45 
46 //! returns true if the file maybe is able to be loaded by this class
isALoadableFileFormat(const io::path & filename) const47 bool CArchiveLoaderNPK::isALoadableFileFormat(const io::path& filename) const
48 {
49 	return core::hasFileExtension(filename, "npk");
50 }
51 
52 //! Check to see if the loader can create archives of this type.
isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const53 bool CArchiveLoaderNPK::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const
54 {
55 	return fileType == EFAT_NPK;
56 }
57 
58 //! Creates an archive from the filename
59 /** \param file File handle to check.
60 \return Pointer to newly created archive, or 0 upon error. */
createArchive(const io::path & filename,bool ignoreCase,bool ignorePaths) const61 IFileArchive* CArchiveLoaderNPK::createArchive(const io::path& filename, bool ignoreCase, bool ignorePaths) const
62 {
63 	IFileArchive *archive = 0;
64 	io::IReadFile* file = FileSystem->createAndOpenFile(filename);
65 
66 	if (file)
67 	{
68 		archive = createArchive(file, ignoreCase, ignorePaths);
69 		file->drop ();
70 	}
71 
72 	return archive;
73 }
74 
75 //! creates/loads an archive from the file.
76 //! \return Pointer to the created archive. Returns 0 if loading failed.
createArchive(io::IReadFile * file,bool ignoreCase,bool ignorePaths) const77 IFileArchive* CArchiveLoaderNPK::createArchive(io::IReadFile* file, bool ignoreCase, bool ignorePaths) const
78 {
79 	IFileArchive *archive = 0;
80 	if ( file )
81 	{
82 		file->seek ( 0 );
83 		archive = new CNPKReader(file, ignoreCase, ignorePaths);
84 	}
85 	return archive;
86 }
87 
88 
89 //! Check if the file might be loaded by this class
90 /** Check might look into the file.
91 \param file File handle to check.
92 \return True if file seems to be loadable. */
isALoadableFileFormat(io::IReadFile * file) const93 bool CArchiveLoaderNPK::isALoadableFileFormat(io::IReadFile* file) const
94 {
95 	SNPKHeader header;
96 
97 	file->read(&header, sizeof(header));
98 
99 	return isHeaderValid(header);
100 }
101 
102 
103 /*!
104 	NPK Reader
105 */
CNPKReader(IReadFile * file,bool ignoreCase,bool ignorePaths)106 CNPKReader::CNPKReader(IReadFile* file, bool ignoreCase, bool ignorePaths)
107 : CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), File(file)
108 {
109 #ifdef _DEBUG
110 	setDebugName("CNPKReader");
111 #endif
112 
113 	if (File)
114 	{
115 		File->grab();
116 		if (scanLocalHeader())
117 			sort();
118 		else
119 			os::Printer::log("Failed to load NPK archive.");
120 	}
121 }
122 
123 
~CNPKReader()124 CNPKReader::~CNPKReader()
125 {
126 	if (File)
127 		File->drop();
128 }
129 
130 
getFileList() const131 const IFileList* CNPKReader::getFileList() const
132 {
133 	return this;
134 }
135 
136 
scanLocalHeader()137 bool CNPKReader::scanLocalHeader()
138 {
139 	SNPKHeader header;
140 
141 	// Read and validate the header
142 	File->read(&header, sizeof(header));
143 	if (!isHeaderValid(header))
144 		return false;
145 
146 	// Seek to the table of contents
147 #ifdef __BIG_ENDIAN__
148 	header.Offset = os::Byteswap::byteswap(header.Offset);
149 	header.Length = os::Byteswap::byteswap(header.Length);
150 #endif
151 	header.Offset += 8;
152 	core::stringc dirName;
153 	bool inTOC=true;
154 	// Loop through each entry in the table of contents
155 	while (inTOC && (File->getPos() < File->getSize()))
156 	{
157 		// read an entry
158 		char tag[4]={0};
159 		SNPKFileEntry entry;
160 		File->read(tag, 4);
161 		const int numTag = MAKE_IRR_ID(tag[3],tag[2],tag[1],tag[0]);
162 		int size;
163 
164 		bool isDir=true;
165 
166 		switch (numTag)
167 		{
168 			case MAKE_IRR_ID('D','I','R','_'):
169 			{
170 				File->read(&size, 4);
171 				readString(entry.Name);
172 				entry.Length=0;
173 				entry.Offset=0;
174 #ifdef IRR_DEBUG_NPK_READER
175 		os::Printer::log("Dir", entry.Name);
176 #endif
177 			}
178 				break;
179 			case MAKE_IRR_ID('F','I','L','E'):
180 			{
181 				File->read(&size, 4);
182 				File->read(&entry.Offset, 4);
183 				File->read(&entry.Length, 4);
184 				readString(entry.Name);
185 				isDir=false;
186 #ifdef IRR_DEBUG_NPK_READER
187 		os::Printer::log("File", entry.Name);
188 #endif
189 #ifdef __BIG_ENDIAN__
190 				entry.Offset = os::Byteswap::byteswap(entry.Offset);
191 				entry.Length = os::Byteswap::byteswap(entry.Length);
192 #endif
193 			}
194 				break;
195 			case MAKE_IRR_ID('D','E','N','D'):
196 			{
197 				File->read(&size, 4);
198 				entry.Name="";
199 				entry.Length=0;
200 				entry.Offset=0;
201 				const s32 pos = dirName.findLast('/', dirName.size()-2);
202 				if (pos==-1)
203 					dirName="";
204 				else
205 					dirName=dirName.subString(0, pos);
206 #ifdef IRR_DEBUG_NPK_READER
207 		os::Printer::log("Dirend", dirName);
208 #endif
209 			}
210 				break;
211 			default:
212 				inTOC=false;
213 		}
214 		// skip root dir
215 		if (isDir)
216 		{
217 			if (!entry.Name.size() || (entry.Name==".") || (entry.Name=="<noname>"))
218 				continue;
219 			dirName += entry.Name;
220 			dirName += "/";
221 		}
222 #ifdef IRR_DEBUG_NPK_READER
223 		os::Printer::log("Name", entry.Name);
224 #endif
225 		addItem((isDir?dirName:dirName+entry.Name), entry.Offset+header.Offset, entry.Length, isDir);
226 	}
227 	return true;
228 }
229 
230 
231 //! opens a file by file name
createAndOpenFile(const io::path & filename)232 IReadFile* CNPKReader::createAndOpenFile(const io::path& filename)
233 {
234 	s32 index = findFile(filename, false);
235 
236 	if (index != -1)
237 		return createAndOpenFile(index);
238 
239 	return 0;
240 }
241 
242 
243 //! opens a file by index
createAndOpenFile(u32 index)244 IReadFile* CNPKReader::createAndOpenFile(u32 index)
245 {
246 	if (index >= Files.size() )
247 		return 0;
248 
249 	const SFileListEntry &entry = Files[index];
250 	return createLimitReadFile( entry.FullName, File, entry.Offset, entry.Size );
251 }
252 
readString(core::stringc & name)253 void CNPKReader::readString(core::stringc& name)
254 {
255 	short stringSize;
256 	char buf[256];
257 	File->read(&stringSize, 2);
258 #ifdef __BIG_ENDIAN__
259 	stringSize = os::Byteswap::byteswap(stringSize);
260 #endif
261 	name.reserve(stringSize);
262 	while(stringSize)
263 	{
264 		const short next = core::min_(stringSize, (short)255);
265 		File->read(buf,next);
266 		buf[next]=0;
267 		name.append(buf);
268 		stringSize -= next;
269 	}
270 }
271 
272 
273 } // end namespace io
274 } // end namespace irr
275 
276 #endif // __IRR_COMPILE_WITH_NPK_ARCHIVE_LOADER_
277 
278