PageRenderTime 59ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/core/NstTrackerMovie.cpp

https://github.com/bsdf/pnes
C++ | 617 lines | 476 code | 118 blank | 23 comment | 90 complexity | b020f229f5668496a97e02d09d09d0d6 MD5 | raw file
  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. #include <new>
  25. #include <iostream>
  26. #include "NstMachine.hpp"
  27. #include "NstState.hpp"
  28. #include "NstTrackerMovie.hpp"
  29. #include "NstZlib.hpp"
  30. #include "api/NstApiMovie.hpp"
  31. #include "api/NstApiUser.hpp"
  32. namespace Nes
  33. {
  34. namespace Core
  35. {
  36. #ifdef NST_MSVC_OPTIMIZE
  37. #pragma optimize("s", on)
  38. #endif
  39. class Tracker::Movie::Player
  40. {
  41. public:
  42. ~Player();
  43. void Relink();
  44. private:
  45. static dword Validate(State::Loader&,const Cpu&,dword,bool);
  46. enum
  47. {
  48. MAX_BUFFER_MASK = 0xFFFFFF,
  49. OPEN_BUS = 0x40
  50. };
  51. struct Buffer : Vector<byte>
  52. {
  53. dword pos;
  54. Buffer()
  55. : pos(0) {}
  56. };
  57. struct Loader : State::Loader
  58. {
  59. explicit Loader(std::istream& s)
  60. : State::Loader(&s,false) {}
  61. bool operator == (std::istream& s) const
  62. {
  63. return stream == &s;
  64. }
  65. };
  66. NES_DECL_PEEK( Port );
  67. NES_DECL_POKE( Port );
  68. const Io::Port* ports[2];
  69. dword frame;
  70. Buffer buffers[2];
  71. Loader state;
  72. Cpu& cpu;
  73. public:
  74. static dword Validate(std::istream& stream,const Cpu& cpu,dword prgCrc)
  75. {
  76. Loader state( stream );
  77. return Validate( state, cpu, prgCrc, true );
  78. }
  79. Player(std::istream& stream,Cpu& c,const dword prgCrc)
  80. : frame(0), state(stream), cpu(c)
  81. {
  82. Validate( state, cpu, prgCrc, false );
  83. Relink();
  84. }
  85. bool operator == (std::istream& stream) const
  86. {
  87. return state == stream;
  88. }
  89. void Stop()
  90. {
  91. state.End();
  92. }
  93. bool Execute(Machine& emulator,EmuLoadState loadState)
  94. {
  95. NST_ASSERT( loadState );
  96. if (buffers[0].pos > buffers[0].Size() || buffers[1].pos > buffers[1].Size())
  97. throw RESULT_ERR_CORRUPT_FILE;
  98. if (frame)
  99. {
  100. --frame;
  101. }
  102. else for (;;)
  103. {
  104. NST_VERIFY( buffers[0].pos == buffers[0].Size() && buffers[1].pos == buffers[1].Size() );
  105. const dword chunk = state.Begin();
  106. if (chunk == AsciiId<'K','E','Y'>::V)
  107. {
  108. for (uint i=0; i < 2; ++i)
  109. {
  110. buffers[i].pos = 0;
  111. buffers[i].Clear();
  112. }
  113. while (const dword subChunk = state.Begin())
  114. {
  115. switch (subChunk)
  116. {
  117. case AsciiId<'S','A','V'>::V:
  118. (emulator.*loadState)( state, false );
  119. break;
  120. case AsciiId<'P','T','0'>::V:
  121. case AsciiId<'P','T','1'>::V:
  122. {
  123. const uint i = (subChunk == AsciiId<'P','T','1'>::V);
  124. buffers[i].Resize( state.Read32() & MAX_BUFFER_MASK );
  125. state.Uncompress( buffers[i].Begin(), buffers[i].Size() );
  126. break;
  127. }
  128. case AsciiId<'L','E','N'>::V:
  129. frame = state.Read32();
  130. NST_VERIFY( frame <= 0xFFFFF );
  131. break;
  132. }
  133. state.End();
  134. }
  135. state.End();
  136. break;
  137. }
  138. else if (chunk)
  139. {
  140. state.End();
  141. }
  142. else
  143. {
  144. return false;
  145. }
  146. }
  147. return true;
  148. }
  149. };
  150. Tracker::Movie::Player::~Player()
  151. {
  152. for (uint i=0; i < 2; ++i)
  153. cpu.Unlink( 0x4016 + i, this, &Player::Peek_Port, &Player::Poke_Port );
  154. }
  155. dword Tracker::Movie::Player::Validate(State::Loader& state,const Cpu& cpu,const dword prgCrc,const bool end)
  156. {
  157. if (state.Begin() != (AsciiId<'N','S','V'>::V | 0x1AUL << 24))
  158. throw RESULT_ERR_INVALID_FILE;
  159. const dword length = state.Length();
  160. Region region = REGION_NTSC;
  161. dword crc = 0;
  162. while (const dword chunk = state.Check())
  163. {
  164. if (chunk == AsciiId<'P','A','L'>::V)
  165. {
  166. state.Begin();
  167. region = REGION_PAL;
  168. state.End();
  169. }
  170. else if (chunk == AsciiId<'C','R','C'>::V)
  171. {
  172. state.Begin();
  173. crc = state.Read32();
  174. state.End();
  175. }
  176. else if (chunk & 0xFFFFFF00)
  177. {
  178. break;
  179. }
  180. else
  181. {
  182. throw RESULT_ERR_UNSUPPORTED_FILE_VERSION;
  183. }
  184. }
  185. if (end)
  186. state.End( length );
  187. if (region != cpu.GetRegion())
  188. throw RESULT_ERR_WRONG_MODE;
  189. if (crc && prgCrc && crc != prgCrc && Api::User::questionCallback( Api::User::QUESTION_NSV_PRG_CRC_FAIL_CONTINUE ) == Api::User::ANSWER_NO)
  190. throw RESULT_ERR_INVALID_CRC;
  191. return length;
  192. }
  193. void Tracker::Movie::Player::Relink()
  194. {
  195. for (uint i=0; i < 2; ++i)
  196. ports[i] = cpu.Link( 0x4016 + i, Cpu::LEVEL_HIGHEST, this, &Player::Peek_Port, &Player::Poke_Port );
  197. }
  198. #ifdef NST_MSVC_OPTIMIZE
  199. #pragma optimize("", on)
  200. #endif
  201. NES_PEEK_A(Tracker::Movie::Player,Port)
  202. {
  203. address &= 0x1;
  204. const uint pos = buffers[address].pos++;
  205. if (pos < buffers[address].Size())
  206. {
  207. return buffers[address][pos];
  208. }
  209. else
  210. {
  211. NST_DEBUG_MSG("buffer >> data failed!");
  212. return OPEN_BUS;
  213. }
  214. }
  215. NES_POKE_AD(Tracker::Movie::Player,Port)
  216. {
  217. ports[address & 0x1]->Poke( address, data );
  218. }
  219. #ifdef NST_MSVC_OPTIMIZE
  220. #pragma optimize("s", on)
  221. #endif
  222. class Tracker::Movie::Recorder
  223. {
  224. public:
  225. ~Recorder();
  226. void Relink();
  227. private:
  228. void BeginKey(Machine&,EmuSaveState);
  229. void EndKey();
  230. enum
  231. {
  232. BAD_FRAME = dword(~0UL),
  233. MAX_BUFFER_BLOCK = SIZE_1K * 8192UL
  234. };
  235. typedef Vector<byte> Buffer;
  236. struct Saver : State::Saver
  237. {
  238. Saver(std::ostream& s,dword a)
  239. : State::Saver(&s,true,true,a) {}
  240. bool operator == (std::ostream& s) const
  241. {
  242. return stream == &s;
  243. }
  244. };
  245. NES_DECL_PEEK( Port );
  246. NES_DECL_POKE( Port );
  247. const Io::Port* ports[2];
  248. ibool resync;
  249. dword frame;
  250. Buffer buffers[2];
  251. Saver state;
  252. Cpu& cpu;
  253. public:
  254. Recorder(std::iostream& stream,Cpu& c,const dword prgCrc,const bool append)
  255. : resync(true), frame(0), state(stream,append ? Player::Validate(stream,c,prgCrc) : 0), cpu(c)
  256. {
  257. if (!append)
  258. {
  259. state.Begin( AsciiId<'N','S','V'>::V | 0x1AUL << 24 );
  260. if (cpu.GetRegion() == REGION_PAL)
  261. state.Begin( AsciiId<'P','A','L'>::V ).End();
  262. if (prgCrc)
  263. state.Begin( AsciiId<'C','R','C'>::V ).Write32( prgCrc ).End();
  264. }
  265. Relink();
  266. }
  267. bool operator == (std::ostream& stream) const
  268. {
  269. return state == stream;
  270. }
  271. void Resync()
  272. {
  273. resync = true;
  274. }
  275. void Stop()
  276. {
  277. EndKey();
  278. state.End();
  279. }
  280. void Execute(Machine& machine,EmuSaveState saveState)
  281. {
  282. NST_ASSERT( saveState );
  283. if (frame == BAD_FRAME)
  284. throw RESULT_ERR_OUT_OF_MEMORY;
  285. if (resync || buffers[0].Size() >= MAX_BUFFER_BLOCK || buffers[1].Size() >= MAX_BUFFER_BLOCK)
  286. {
  287. EndKey();
  288. BeginKey( machine, saveState );
  289. }
  290. ++frame;
  291. }
  292. };
  293. Tracker::Movie::Recorder::~Recorder()
  294. {
  295. for (uint i=0; i < 2; ++i)
  296. cpu.Unlink( 0x4016 + i, this, &Recorder::Peek_Port, &Recorder::Poke_Port );
  297. }
  298. void Tracker::Movie::Recorder::Relink()
  299. {
  300. for (uint i=0; i < 2; ++i)
  301. ports[i] = cpu.Link( 0x4016 + i, Cpu::LEVEL_HIGHEST, this, &Recorder::Peek_Port, &Recorder::Poke_Port );
  302. }
  303. void Tracker::Movie::Recorder::BeginKey(Machine& machine,EmuSaveState saveState)
  304. {
  305. state.Begin( AsciiId<'K','E','Y'>::V );
  306. if (resync)
  307. {
  308. resync = false;
  309. state.Begin( AsciiId<'S','A','V'>::V );
  310. (machine.*saveState)( state );
  311. state.End();
  312. }
  313. }
  314. void Tracker::Movie::Recorder::EndKey()
  315. {
  316. if (frame == BAD_FRAME)
  317. throw RESULT_ERR_OUT_OF_MEMORY;
  318. if (frame)
  319. {
  320. state.Begin( AsciiId<'L','E','N'>::V ).Write32( frame-1 ).End();
  321. frame = 0;
  322. for (uint i=0; i < 2; ++i)
  323. {
  324. if (buffers[i].Size())
  325. {
  326. state.Begin( AsciiId<'P','T','0'>::R(0,0,i) ).Write32( buffers[i].Size() ).Compress( buffers[i].Begin(), buffers[i].Size() ).End();
  327. buffers[i].Clear();
  328. }
  329. }
  330. state.End();
  331. }
  332. }
  333. #ifdef NST_MSVC_OPTIMIZE
  334. #pragma optimize("", on)
  335. #endif
  336. NES_PEEK_A(Tracker::Movie::Recorder,Port)
  337. {
  338. const uint data = ports[address & 0x1]->Peek( address );
  339. if (frame != BAD_FRAME)
  340. {
  341. try
  342. {
  343. buffers[address & 0x1].Append( data );
  344. }
  345. catch (...)
  346. {
  347. frame = BAD_FRAME;
  348. }
  349. }
  350. return data;
  351. }
  352. NES_POKE_AD(Tracker::Movie::Recorder,Port)
  353. {
  354. ports[address & 0x1]->Poke( address, data );
  355. }
  356. #ifdef NST_MSVC_OPTIMIZE
  357. #pragma optimize("s", on)
  358. #endif
  359. Tracker::Movie::Movie(Machine& e,EmuLoadState l,EmuSaveState s,Cpu& c,dword crc)
  360. :
  361. player (NULL),
  362. recorder (NULL),
  363. emulator (e),
  364. saveState (s),
  365. loadState (l),
  366. cpu (c),
  367. prgCrc (crc)
  368. {
  369. }
  370. Tracker::Movie::~Movie()
  371. {
  372. Stop();
  373. }
  374. bool Tracker::Movie::Record(std::iostream& stream,const bool append)
  375. {
  376. if (!Zlib::AVAILABLE)
  377. throw RESULT_ERR_UNSUPPORTED;
  378. if (player)
  379. throw RESULT_ERR_NOT_READY;
  380. if (recorder && *recorder == stream)
  381. return false;
  382. Stop();
  383. recorder = new Recorder( stream, cpu, prgCrc, append );
  384. Api::Movie::eventCallback( Api::Movie::EVENT_RECORDING );
  385. return true;
  386. }
  387. bool Tracker::Movie::Play(std::istream& stream)
  388. {
  389. if (!Zlib::AVAILABLE)
  390. throw RESULT_ERR_UNSUPPORTED;
  391. if (recorder)
  392. throw RESULT_ERR_NOT_READY;
  393. if (player && *player == stream)
  394. return false;
  395. Stop();
  396. player = new Player( stream, cpu, prgCrc );
  397. Api::Movie::eventCallback( Api::Movie::EVENT_PLAYING );
  398. return true;
  399. }
  400. void Tracker::Movie::Stop()
  401. {
  402. Stop( RESULT_OK );
  403. }
  404. bool Tracker::Movie::Stop(Result result)
  405. {
  406. if (recorder || player)
  407. {
  408. if (NES_SUCCEEDED(result))
  409. {
  410. try
  411. {
  412. if (recorder)
  413. recorder->Stop();
  414. else
  415. player->Stop();
  416. }
  417. catch (Result r)
  418. {
  419. result = r;
  420. }
  421. catch (const std::bad_alloc&)
  422. {
  423. result = RESULT_ERR_OUT_OF_MEMORY;
  424. }
  425. catch (...)
  426. {
  427. result = RESULT_ERR_GENERIC;
  428. }
  429. }
  430. if (recorder)
  431. {
  432. delete recorder;
  433. recorder = NULL;
  434. Api::Movie::eventCallback( Api::Movie::EVENT_RECORDING_STOPPED, result );
  435. }
  436. else
  437. {
  438. delete player;
  439. player = NULL;
  440. Api::Movie::eventCallback( Api::Movie::EVENT_PLAYING_STOPPED, result );
  441. if (NES_FAILED(result))
  442. return false;
  443. }
  444. }
  445. return true;
  446. }
  447. void Tracker::Movie::Reset()
  448. {
  449. if (recorder)
  450. {
  451. recorder->Relink();
  452. }
  453. else if (player)
  454. {
  455. player->Relink();
  456. }
  457. Resync();
  458. }
  459. void Tracker::Movie::Resync()
  460. {
  461. if (recorder)
  462. recorder->Resync();
  463. }
  464. #ifdef NST_MSVC_OPTIMIZE
  465. #pragma optimize("", on)
  466. #endif
  467. bool Tracker::Movie::Execute()
  468. {
  469. Result result = RESULT_OK;
  470. try
  471. {
  472. if (recorder)
  473. {
  474. recorder->Execute( emulator, saveState );
  475. return true;
  476. }
  477. else if (player && player->Execute( emulator, loadState ))
  478. {
  479. return true;
  480. }
  481. }
  482. catch (Result r)
  483. {
  484. result = r;
  485. }
  486. catch (const std::bad_alloc&)
  487. {
  488. result = RESULT_ERR_OUT_OF_MEMORY;
  489. }
  490. catch (...)
  491. {
  492. result = RESULT_ERR_GENERIC;
  493. }
  494. if (!Stop( result ))
  495. throw result;
  496. return false;
  497. }
  498. }
  499. }