1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include <cstring>
26 #include <new>
27 #include "NstStream.hpp"
28 #include "NstVector.hpp"
29 #include "NstChecksum.hpp"
30 #include "NstPatcher.hpp"
31 #include "NstFile.hpp"
32 #include "api/NstApiUser.hpp"
33 
34 namespace Nes
35 {
36 	namespace Core
37 	{
38 
39 		struct File::Context
40 		{
41 			Checksum checksum;
42 			Vector<byte> data;
43 		};
44 
File()45 		File::File()
46 		: context( *new Context )
47 		{
48 		}
49 
~File()50 		File::~File()
51 		{
52 			delete &context;
53 		}
54 
Load(const byte * data,dword size) const55 		void File::Load(const byte* data,dword size) const
56 		{
57 			NST_ASSERT( data && size );
58 
59 			context.checksum.Clear();
60 			context.checksum.Compute( data, size );
61 			context.data.Destroy();
62 		}
63 
Load(byte * data,dword size,Type type) const64 		void File::Load(byte* data,dword size,Type type) const
65 		{
66 			NST_ASSERT( data && size );
67 
68 			context.data.Assign( data, size );
69 
70 			bool altered = false;
71 
72 			const LoadBlock loadBlock = {data,size};
73 			Load( type, &loadBlock, 1, &altered );
74 
75 			if (altered)
76 				context.checksum.Clear();
77 		}
78 
Load(Type type,byte * data,dword size) const79 		void File::Load(Type type,byte* data,dword size) const
80 		{
81 			const LoadBlock loadBlock = {data,size};
82 			Load( type, &loadBlock, 1 );
83 		}
84 
Load(const Type type,const LoadBlock * const loadBlock,const uint loadBlockCount,bool * const altered) const85 		void File::Load(const Type type,const LoadBlock* const loadBlock,const uint loadBlockCount,bool* const altered) const
86 		{
87 			class Loader : public Api::User::File
88 			{
89 				const Action action;
90 				const LoadBlock* const loadBlock;
91 				const uint loadBlockCount;
92 				bool* const altered;
93 
94 				Action GetAction() const throw()
95 				{
96 					return action;
97 				}
98 
99 				ulong GetMaxSize() const throw()
100 				{
101 					dword maxsize = 0;
102 
103 					for (const LoadBlock* NST_RESTRICT it=loadBlock, *const end=loadBlock+loadBlockCount; it != end; ++it)
104 						maxsize += it->size;
105 
106 					return maxsize;
107 				}
108 
109 				Result SetContent(const void* data,ulong filesize) throw()
110 				{
111 					if (altered)
112 						*altered = true;
113 
114 					if (!data || !filesize)
115 						return RESULT_ERR_INVALID_PARAM;
116 
117 					const byte* NST_RESTRICT filedata = static_cast<const byte*>(data);
118 
119 					for (const LoadBlock* NST_RESTRICT it=loadBlock, *const end=loadBlock+loadBlockCount; it != end; ++it)
120 					{
121 						if (const dword size = NST_MIN(filesize,it->size))
122 						{
123 							std::memcpy( it->data, filedata, size );
124 							filedata += size;
125 							filesize -= size;
126 						}
127 					}
128 
129 					return RESULT_OK;
130 				}
131 
132 				void GetRawStorage(void*& data, ulong& size) const throw()
133 				{
134 					if (loadBlockCount == 1)
135 					{
136 						data = loadBlock->data;
137 						size = loadBlock->size;
138 					}
139 					else
140 					{
141 						data = 0;
142 						size = 0;
143 					}
144 				}
145 
146 				Result SetContent(std::istream& stdStream) throw()
147 				{
148 					if (altered)
149 						*altered = true;
150 
151 					try
152 					{
153                   Nes::Core::Stream::In stream( &stdStream );
154 
155 						if (ulong length = stream.Length())
156 						{
157 							for (const LoadBlock* NST_RESTRICT it=loadBlock, *const end=loadBlock+loadBlockCount; it != end; ++it)
158 							{
159 								if (const dword size = NST_MIN(length,it->size))
160 								{
161 									stream.Read( it->data, size );
162 									length -= size;
163 								}
164 							}
165 						}
166 						else
167 						{
168 							return RESULT_ERR_INVALID_PARAM;
169 						}
170 					}
171 					catch (Result result)
172 					{
173 						return result;
174 					}
175 					catch (const std::bad_alloc&)
176 					{
177 						return RESULT_ERR_OUT_OF_MEMORY;
178 					}
179 					catch (...)
180 					{
181 						return RESULT_ERR_GENERIC;
182 					}
183 
184 					return RESULT_OK;
185 				}
186 
187 				Result SetPatchContent(std::istream& stream) throw()
188 				{
189 					if (altered)
190 						*altered = true;
191 
192 					Patcher patcher;
193 
194 					Result result = patcher.Load( stream );
195 
196 					if (NES_FAILED(result))
197 						return result;
198 
199 					if (loadBlockCount > 1)
200 					{
201 						Patcher::Block* const patchBlocks = new (std::nothrow) Patcher::Block [loadBlockCount];
202 
203 						if (!patchBlocks)
204 							return RESULT_ERR_OUT_OF_MEMORY;
205 
206 						for (uint i=0; i < loadBlockCount; ++i)
207 						{
208 							patchBlocks[i].data = loadBlock[i].data;
209 							patchBlocks[i].size = loadBlock[i].size;
210 						}
211 
212 						result = patcher.Test( patchBlocks, loadBlockCount );
213 
214 						delete [] patchBlocks;
215 					}
216 					else
217 					{
218 						result = patcher.Test( loadBlockCount ? loadBlock->data : NULL, loadBlockCount ? loadBlock->size : 0 );
219 					}
220 
221 					if (NES_SUCCEEDED(result))
222 					{
223 						for (dword i=0, offset=0; i < loadBlockCount; offset += loadBlock[i].size, ++i)
224 							patcher.Patch( loadBlock[i].data, loadBlock[i].data, loadBlock[i].size, offset );
225 					}
226 
227 					return result;
228 				}
229 
230 			public:
231 
232 				Loader(Type t,const LoadBlock* l,uint c,bool* a)
233 				:
234 				action
235 				(
236 					t == EEPROM    ? LOAD_EEPROM :
237 					t == TAPE      ? LOAD_TAPE :
238 					t == TURBOFILE ? LOAD_TURBOFILE :
239 					t == DISK      ? LOAD_FDS :
240                                      LOAD_BATTERY
241 				),
242 				loadBlock      (l),
243 				loadBlockCount (c),
244 				altered        (a)
245 				{
246 					if (altered)
247 						*altered = false;
248 				}
249 			};
250 
251 			{
252 				Loader loader( type, loadBlock, loadBlockCount, altered );
253 				Api::User::fileIoCallback( loader );
254 			}
255 
256 			context.checksum.Clear();
257 
258 			for (const LoadBlock* NST_RESTRICT it=loadBlock, *const end=loadBlock+loadBlockCount; it != end; ++it)
259 				context.checksum.Compute( it->data, it->size );
260 		}
261 
Load(const Type type,Vector<byte> & buffer,const dword maxsize) const262 		void File::Load(const Type type,Vector<byte>& buffer,const dword maxsize) const
263 		{
264 			NST_ASSERT( maxsize && type != DISK );
265 
266 			class Loader : public Api::User::File
267 			{
268 				const Action action;
269 				Vector<byte>& buffer;
270 				const dword maxsize;
271 
272 				Action GetAction() const throw()
273 				{
274 					return action;
275 				}
276 
277 				ulong GetMaxSize() const throw()
278 				{
279 					return maxsize;
280 				}
281 
282 				Result SetContent(const void* filedata,ulong filesize) throw()
283 				{
284 					if (!filedata || !filesize)
285 						return RESULT_ERR_INVALID_PARAM;
286 
287 					try
288 					{
289 						buffer.Assign( static_cast<const byte*>(filedata), NST_MIN(filesize,maxsize) );
290 					}
291 					catch (const std::bad_alloc&)
292 					{
293 						return RESULT_ERR_OUT_OF_MEMORY;
294 					}
295 					catch (...)
296 					{
297 						return RESULT_ERR_GENERIC;
298 					}
299 
300 					return RESULT_OK;
301 				}
302 
303 				Result SetContent(std::istream& stdStream) throw()
304 				{
305 					try
306 					{
307                   Nes::Core::Stream::In stream( &stdStream );
308 
309 						if (const ulong length = stream.Length())
310 						{
311 							buffer.Resize( NST_MIN(length,maxsize) );
312 
313 							try
314 							{
315 								stream.Read( buffer.Begin(), buffer.Size() );
316 							}
317 							catch (...)
318 							{
319 								buffer.Destroy();
320 								throw;
321 							}
322 						}
323 						else
324 						{
325 							return RESULT_ERR_INVALID_PARAM;
326 						}
327 					}
328 					catch (Result result)
329 					{
330 						return result;
331 					}
332 					catch (const std::bad_alloc&)
333 					{
334 						return RESULT_ERR_OUT_OF_MEMORY;
335 					}
336 					catch (...)
337 					{
338 						return RESULT_ERR_GENERIC;
339 					}
340 
341 					return RESULT_OK;
342 				}
343 
344 			public:
345 
346 				Loader(Type t,Vector<byte>& b,dword m)
347 				:
348 				action
349 				(
350 					t == EEPROM    ? LOAD_EEPROM :
351 					t == TAPE      ? LOAD_TAPE :
352 					t == TURBOFILE ? LOAD_TURBOFILE :
353                                      LOAD_BATTERY
354 				),
355 				buffer  (b),
356 				maxsize (m)
357 				{
358 				}
359 			};
360 
361 			{
362 				Loader loader( type, buffer, maxsize );
363 				Api::User::fileIoCallback( loader );
364 			}
365 
366 			if (buffer.Size())
367 				Load( buffer.Begin(), buffer.Size() );
368 		}
369 
Save(Type type,const byte * data,dword size) const370 		void File::Save(Type type,const byte* data,dword size) const
371 		{
372 			const SaveBlock saveBlock = {data,size};
373 			Save( type, &saveBlock, 1 );
374 		}
375 
Save(const Type type,const SaveBlock * const saveBlock,const uint saveBlockCount) const376 		void File::Save(const Type type,const SaveBlock* const saveBlock,const uint saveBlockCount) const
377 		{
378 			NST_ASSERT( saveBlock && saveBlockCount );
379 
380 			Checksum checksum;
381 
382 			for (const SaveBlock *NST_RESTRICT it=saveBlock, *const end=saveBlock+saveBlockCount; it != end; ++it)
383 				checksum.Compute( it->data, it->size );
384 
385 			if (checksum != context.checksum)
386 			{
387 				class Saver : public Api::User::File
388 				{
389 					const Action action;
390 					const SaveBlock* const saveBlock;
391 					const uint saveBlockCount;
392 					mutable Vector<byte> buffer;
393 					const Vector<byte> original;
394 
395 					Action GetAction() const throw()
396 					{
397 						return action;
398 					}
399 
400 					ulong GetMaxSize() const throw()
401 					{
402 						dword size = 0;
403 
404 						for (const SaveBlock* NST_RESTRICT it=saveBlock, *const end=saveBlock+saveBlockCount; it != end; ++it)
405 							size += it->size;
406 
407 						return size;
408 					}
409 
410 					Result GetContent(const void*& filedata,ulong& filesize) const throw()
411 					{
412 						if (saveBlockCount <= 1)
413 						{
414 							filedata = saveBlock->data;
415 							filesize = saveBlock->size;
416 						}
417 						else
418 						{
419 							if (!buffer.Size())
420 							{
421 								try
422 								{
423 									buffer.Resize( Saver::GetMaxSize() );
424 								}
425 								catch (...)
426 								{
427 									filedata = NULL;
428 									filesize = 0;
429 									return RESULT_ERR_OUT_OF_MEMORY;
430 								}
431 
432 								dword offset = 0;
433 
434 								for (const SaveBlock* NST_RESTRICT it=saveBlock, *const end=saveBlock+saveBlockCount; it != end; ++it)
435 								{
436 									std::memcpy( &buffer[offset], it->data, it->size );
437 									offset += it->size;
438 								}
439 							}
440 
441 							filedata = buffer.Begin();
442 							filesize = buffer.Size();
443 						}
444 
445 						return RESULT_OK;
446 					}
447 
448 					Result GetContent(std::ostream& stdStream) const throw()
449 					{
450 						try
451 						{
452                      Nes::Core::Stream::Out stream( &stdStream );
453 
454 							for (const SaveBlock* NST_RESTRICT it=saveBlock, *const end=saveBlock+saveBlockCount; it != end; ++it)
455 							{
456 								if (it->size)
457 									stream.Write( it->data, it->size );
458 							}
459 						}
460 						catch (Result result)
461 						{
462 							return result;
463 						}
464 						catch (const std::bad_alloc&)
465 						{
466 							return RESULT_ERR_OUT_OF_MEMORY;
467 						}
468 						catch (...)
469 						{
470 							return RESULT_ERR_GENERIC;
471 						}
472 
473 						return RESULT_OK;
474 					}
475 
476 					Result GetPatchContent(Patch format,std::ostream& stream) const throw()
477 					{
478 						if (!original.Size() || (format != PATCH_UPS && format != PATCH_IPS))
479 							return RESULT_ERR_UNSUPPORTED;
480 
481 						const void* data;
482 						ulong size;
483 
484 						Result result = Saver::GetContent( data, size );
485 
486 						if (NES_FAILED(result))
487 							return result;
488 
489 						if (size != original.Size())
490 							return RESULT_ERR_UNSUPPORTED;
491 
492 						Patcher patcher;
493 
494 						result = patcher.Create
495 						(
496 							format == PATCH_UPS ? Patcher::UPS : Patcher::IPS,
497 							original.Begin(),
498 							static_cast<const byte*>(data),
499 							size
500 						);
501 
502 						if (NES_FAILED(result))
503 							return result;
504 
505 						return patcher.Save( stream );
506 					}
507 
508 				public:
509 
510 					Saver(Type t,const SaveBlock* s,uint c,const Vector<byte>& o)
511 					:
512 					action
513 					(
514 						t == EEPROM    ? SAVE_EEPROM :
515 						t == TAPE      ? SAVE_TAPE :
516 						t == TURBOFILE ? SAVE_TURBOFILE :
517 						t == DISK      ? SAVE_FDS :
518                                          SAVE_BATTERY
519 					),
520 					saveBlock      (s),
521 					saveBlockCount (c),
522 					original       (o)
523 					{
524 					}
525 				};
526 
527 				Saver saver( type, saveBlock, saveBlockCount, context.data );
528 				Api::User::fileIoCallback( saver );
529 			}
530 		}
531 	}
532 }
533