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