1 /*
2  * HLLib
3  * Copyright (C) 2006-2010 Ryan Gregg
4 
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later
9  * version.
10  */
11 
12 #include "HLLib.h"
13 #include "XZPFile.h"
14 #include "Streams.h"
15 
16 using namespace HLLib;
17 
18 const char *CXZPFile::lpAttributeNames[] = { "Version", "Preload Bytes" };
19 const char *CXZPFile::lpItemAttributeNames[] = { "Created", "Preload Bytes" };
20 
CXZPFile()21 CXZPFile::CXZPFile() : CPackage(), pHeaderView(0), pDirectoryEntryView(0), pDirectoryItemView(0), pFooterView(0), pHeader(0), lpDirectoryEntries(0), lpPreloadDirectoryEntries(0), lpPreloadDirectoryMappings(0), lpDirectoryItems(0), pFooter(0)
22 {
23 
24 }
25 
~CXZPFile()26 CXZPFile::~CXZPFile()
27 {
28 	this->Close();
29 }
30 
GetType() const31 HLPackageType CXZPFile::GetType() const
32 {
33 	return HL_PACKAGE_XZP;
34 }
35 
GetExtension() const36 const hlChar *CXZPFile::GetExtension() const
37 {
38 	return "xzp";
39 }
40 
GetDescription() const41 const hlChar *CXZPFile::GetDescription() const
42 {
43 	return "XBox Package File";
44 }
45 
MapDataStructures()46 hlBool CXZPFile::MapDataStructures()
47 {
48 	if(sizeof(XZPHeader) > this->pMapping->GetMappingSize())
49 	{
50 		LastError.SetErrorMessage("Invalid file: the file map is too small for it's header.");
51 		return hlFalse;
52 	}
53 
54 	if(!this->pMapping->Map(this->pHeaderView, 0, sizeof(XZPHeader)))
55 	{
56 		return hlFalse;
57 	}
58 	this->pHeader = (XZPHeader *)this->pHeaderView->GetView();
59 
60 	if(memcmp(this->pHeader->lpSignature, "piZx", 4) != 0)
61 	{
62 		LastError.SetErrorMessage("Invalid file: the file's header signature does not match.");
63 		return hlFalse;
64 	}
65 
66 	if(this->pHeader->uiVersion != 6)
67 	{
68 		LastError.SetErrorMessageFormated("Invalid XZP version (v%u): you have a version of a XZP file that HLLib does not know how to read. Check for product updates.", this->pHeader->uiVersion);
69 		return hlFalse;
70 	}
71 
72 	if(this->pHeader->uiHeaderLength != sizeof(XZPHeader))
73 	{
74 		LastError.SetErrorMessage("Invalid file: the file's header size does not match.");
75 		return hlFalse;
76 	}
77 
78 	if(!this->pMapping->Map(this->pDirectoryEntryView, sizeof(XZPHeader), this->pHeader->uiPreloadBytes ? (this->pHeader->uiDirectoryEntryCount + this->pHeader->uiPreloadDirectoryEntryCount) * sizeof(XZPDirectoryEntry) + this->pHeader->uiDirectoryEntryCount * sizeof(XZPDirectoryMapping) : this->pHeader->uiDirectoryEntryCount * sizeof(XZPDirectoryEntry)))
79 	{
80 		return hlFalse;
81 	}
82 	this->lpDirectoryEntries = (XZPDirectoryEntry *)this->pDirectoryEntryView->GetView();
83 	this->lpPreloadDirectoryEntries = this->pHeader->uiPreloadBytes ? this->lpDirectoryEntries + this->pHeader->uiDirectoryEntryCount : 0;
84 	this->lpPreloadDirectoryMappings = this->pHeader->uiPreloadBytes ? (XZPDirectoryMapping *)(this->lpPreloadDirectoryEntries + this->pHeader->uiPreloadDirectoryEntryCount) : 0;
85 
86 	if(this->pHeader->uiDirectoryItemCount != 0)
87 	{
88 		if(!this->pMapping->Map(this->pDirectoryItemView, this->pHeader->uiDirectoryItemOffset, this->pHeader->uiDirectoryItemLength))
89 		{
90 			return hlFalse;
91 		}
92 		this->lpDirectoryItems = (XZPDirectoryItem *)this->pDirectoryItemView->GetView();
93 	}
94 
95 	if(!this->pMapping->Map(this->pFooterView, this->pMapping->GetMappingSize() - sizeof(XZPFooter), sizeof(XZPFooter)))
96 	{
97 		return hlFalse;
98 	}
99 	this->pFooter = (XZPFooter *)this->pFooterView->GetView();
100 
101 	if(memcmp(this->pFooter->lpSignature, "tFzX", 4) != 0)
102 	{
103 		LastError.SetErrorMessage("Invalid file: the file's footer signature does not match.");
104 		return hlFalse;
105 	}
106 
107 	if(this->pFooter->uiFileLength != this->pMapping->GetMappingSize())
108 	{
109 		LastError.SetErrorMessage("Invalid file: the file map is not within mapping bounds.");
110 		return hlFalse;
111 	}
112 
113 	return hlTrue;
114 }
115 
UnmapDataStructures()116 hlVoid CXZPFile::UnmapDataStructures()
117 {
118 	this->pFooter = 0;
119 	this->pMapping->Unmap(this->pFooterView);
120 
121 	this->lpDirectoryItems = 0;
122 	this->pMapping->Unmap(this->pDirectoryItemView);
123 
124 	this->lpDirectoryEntries = 0;
125 	this->lpPreloadDirectoryEntries = 0;
126 	this->lpPreloadDirectoryMappings = 0;
127 	this->pMapping->Unmap(this->pDirectoryEntryView);
128 
129 	this->pHeader = 0;
130 	this->pMapping->Unmap(this->pHeaderView);
131 }
132 
CreateRoot()133 CDirectoryFolder *CXZPFile::CreateRoot()
134 {
135 	CDirectoryFolder *pRoot = new CDirectoryFolder(this);
136 
137 	if(this->pHeader->uiDirectoryItemCount != 0)
138 	{
139 		// Loop through each file in the XZP file.
140 		for(hlUInt i = 0; i < this->pHeader->uiDirectoryEntryCount; i++)
141 		{
142 			// Find it's info (file name).
143 			for(hlUInt j = 0; j < this->pHeader->uiDirectoryItemCount; j++)
144 			{
145 				if(this->lpDirectoryEntries[i].uiFileNameCRC == this->lpDirectoryItems[j].uiFileNameCRC)
146 				{
147 					hlChar lpFileName[256];
148 					strncpy(lpFileName, (hlChar *)this->lpDirectoryItems + this->lpDirectoryItems[j].uiNameOffset - this->pHeader->uiDirectoryItemOffset, sizeof(lpFileName));
149 					lpFileName[sizeof(lpFileName) - 1] = '\0';
150 
151 					// Check if we have just a file, or if the file has directories we need to create.
152 					if(strchr(lpFileName, '/') == 0 && strchr(lpFileName, '\\') == 0)
153 					{
154 						pRoot->AddFile(lpFileName, i);
155 					}
156 					else
157 					{
158 						// Tokenize the file path and create the directories.
159 						CDirectoryFolder *pInsertFolder = pRoot;
160 
161 						hlChar lpTemp[256] = "";
162 						hlChar *lpToken = strtok(lpFileName, "/\\");
163 						while(lpToken != 0)
164 						{
165 							strcpy(lpTemp, lpToken);
166 
167 							lpToken = strtok(0, "/\\");
168 
169 							if(lpToken != 0)
170 							{
171 								// Check if the directory exists.
172 								CDirectoryItem *pItem = pInsertFolder->GetItem(lpTemp);
173 								if(pItem == 0 || pItem->GetType() == HL_ITEM_FILE)
174 								{
175 									// It doesn't, create it.
176 									pInsertFolder = pInsertFolder->AddFolder(lpTemp);
177 								}
178 								else
179 								{
180 									// It does, use it.
181 									pInsertFolder = static_cast<CDirectoryFolder *>(pItem);
182 								}
183 							}
184 						}
185 
186 						// The file name is the last token, add it.
187 						pInsertFolder->AddFile(lpTemp, i);
188 					}
189 					break;
190 				}
191 			}
192 		}
193 	}
194 	else
195 	{
196 		// No file name information, just file name CRCs.
197 		const hlChar lpLookup[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
198 		for(hlUInt i = 0; i < this->pHeader->uiDirectoryEntryCount; i++)
199 		{
200 			hlChar lpTemp[16];
201 
202 			hlChar* lpName = lpTemp;
203 			for(hlByte *lpCRC = (hlByte *)&this->lpDirectoryEntries[i].uiFileNameCRC; lpCRC < (hlByte *)&this->lpDirectoryEntries[i].uiFileNameCRC + sizeof(hlUInt); lpCRC++)
204 			{
205 				*lpName++ = lpLookup[(hlByte)(*lpCRC >> 4)];
206 				*lpName++ = lpLookup[(hlByte)(*lpCRC & 0x0F)];
207 			}
208 			*lpName = '\0';
209 
210 			pRoot->AddFile(lpTemp, i);
211 		}
212 	}
213 
214 	return pRoot;
215 }
216 
GetAttributeCountInternal() const217 hlUInt CXZPFile::GetAttributeCountInternal() const
218 {
219 	return HL_XZP_PACKAGE_COUNT;
220 }
221 
GetAttributeNameInternal(HLPackageAttribute eAttribute) const222 const hlChar *CXZPFile::GetAttributeNameInternal(HLPackageAttribute eAttribute) const
223 {
224 	if(eAttribute < HL_XZP_PACKAGE_COUNT)
225 	{
226 		return this->lpAttributeNames[eAttribute];
227 	}
228 
229 	return 0;
230 }
231 
GetAttributeInternal(HLPackageAttribute eAttribute,HLAttribute & Attribute) const232 hlBool CXZPFile::GetAttributeInternal(HLPackageAttribute eAttribute, HLAttribute &Attribute) const
233 {
234 	switch(eAttribute)
235 	{
236 	case HL_XZP_PACKAGE_VERSION:
237 		hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pHeader->uiVersion, hlFalse);
238 		return hlTrue;
239 	case HL_XZP_PACKAGE_PRELOAD_BYTES:
240 		hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pHeader->uiPreloadBytes, hlFalse);
241 		return hlTrue;
242 	default:
243 		return hlFalse;
244 	}
245 }
246 
GetItemAttributeCountInternal() const247 hlUInt CXZPFile::GetItemAttributeCountInternal() const
248 {
249 	return HL_XZP_ITEM_COUNT;
250 }
251 
GetItemAttributeNameInternal(HLPackageAttribute eAttribute) const252 const hlChar *CXZPFile::GetItemAttributeNameInternal(HLPackageAttribute eAttribute) const
253 {
254 	if(eAttribute < HL_XZP_ITEM_COUNT)
255 	{
256 		return this->lpItemAttributeNames[eAttribute];
257 	}
258 
259 	return 0;
260 }
261 
GetItemAttributeInternal(const CDirectoryItem * pItem,HLPackageAttribute eAttribute,HLAttribute & Attribute) const262 hlBool CXZPFile::GetItemAttributeInternal(const CDirectoryItem *pItem, HLPackageAttribute eAttribute, HLAttribute &Attribute) const
263 {
264 	switch(pItem->GetType())
265 	{
266 		case HL_ITEM_FILE:
267 		{
268 			const CDirectoryFile *pFile = static_cast<const CDirectoryFile *>(pItem);
269 			const XZPDirectoryEntry *pDirectoryEntry = this->lpDirectoryEntries + pFile->GetID();
270 			switch(eAttribute)
271 			{
272 				case HL_XZP_ITEM_CREATED:
273 				{
274 					for(hlUInt i = 0; i < this->pHeader->uiDirectoryItemCount; i++)
275 					{
276 						if(this->lpDirectoryItems[i].uiFileNameCRC == pDirectoryEntry->uiFileNameCRC)
277 						{
278 							time_t Time = (time_t)this->lpDirectoryItems[i].uiTimeCreated;
279 							tm *pTime = localtime(&Time);
280 
281 							hlChar lpTime[128];
282 							strftime(lpTime, sizeof(lpTime), "%c", pTime);
283 
284 							hlAttributeSetString(&Attribute, this->lpItemAttributeNames[eAttribute], lpTime);
285 							return hlTrue;
286 						}
287 					}
288 					break;
289 				}
290 				case HL_XZP_ITEM_PRELOAD_BYTES:
291 				{
292 					hlUInt uiSize = 0;
293 					if(this->lpPreloadDirectoryMappings != 0)
294 					{
295 						hlUInt16 uiIndex = this->lpPreloadDirectoryMappings[pFile->GetID()].uiPreloadDirectoryEntryIndex;
296 						if(uiIndex != 0xffff && this->lpPreloadDirectoryEntries[uiIndex].uiFileNameCRC == pDirectoryEntry->uiFileNameCRC)
297 						{
298 							uiSize = this->lpPreloadDirectoryEntries[uiIndex].uiEntryLength;
299 						}
300 					}
301 					hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], uiSize, hlFalse);
302 					return hlTrue;
303 				}
304 			}
305 			break;
306 		}
307 	}
308 
309 	return hlFalse;
310 }
311 
GetFileSizeInternal(const CDirectoryFile * pFile,hlUInt & uiSize) const312 hlBool CXZPFile::GetFileSizeInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
313 {
314 	uiSize = this->lpDirectoryEntries[pFile->GetID()].uiEntryLength;
315 
316 	return hlTrue;
317 }
318 
GetFileSizeOnDiskInternal(const CDirectoryFile * pFile,hlUInt & uiSize) const319 hlBool CXZPFile::GetFileSizeOnDiskInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
320 {
321 	uiSize = this->lpDirectoryEntries[pFile->GetID()].uiEntryLength;
322 
323 	return hlTrue;
324 }
325 
CreateStreamInternal(const CDirectoryFile * pFile,Streams::IStream * & pStream) const326 hlBool CXZPFile::CreateStreamInternal(const CDirectoryFile *pFile, Streams::IStream *&pStream) const
327 {
328 	const XZPDirectoryEntry *pDirectoryEntry = this->lpDirectoryEntries + pFile->GetID();
329 
330 	pStream = new Streams::CMappingStream(*this->pMapping, pDirectoryEntry->uiEntryOffset, pDirectoryEntry->uiEntryLength);
331 
332 	return hlTrue;
333 }
334