#include #include #include #include #include #include "make_unique.h" #include "string_format.h" #include "PS2VM.h" #include "PS2VM_Preferences.h" #include "ee/PS2OS.h" #include "Ps2Const.h" #include "iop/Iop_SifManPs2.h" #include "StdStream.h" #include "StdStreamUtils.h" #include "GZipStream.h" #include "MemoryStateFile.h" #include "zip/ZipArchiveWriter.h" #include "zip/ZipArchiveReader.h" #include "xml/Node.h" #include "xml/Writer.h" #include "xml/Parser.h" #include "AppConfig.h" #include "PathUtils.h" #include "iop/IopBios.h" #include "iop/DirectoryDevice.h" #include "iop/OpticalMediaDevice.h" #include "Log.h" #include "ISO9660/BlockProvider.h" #include "DiskUtils.h" #define LOG_NAME ("ps2vm") #define PREF_PS2_HOST_DIRECTORY_DEFAULT ("vfs/host") #define PREF_PS2_MC0_DIRECTORY_DEFAULT ("vfs/mc0") #define PREF_PS2_MC1_DIRECTORY_DEFAULT ("vfs/mc1") #define FRAME_TICKS (PS2::EE_CLOCK_FREQ / 60) #define ONSCREEN_TICKS (FRAME_TICKS * 9 / 10) #define VBLANK_TICKS (FRAME_TICKS / 10) namespace filesystem = boost::filesystem; CPS2VM::CPS2VM() : m_nStatus(PAUSED) , m_nEnd(false) , m_pad(NULL) , m_singleStepEe(false) , m_singleStepIop(false) , m_singleStepVu0(false) , m_singleStepVu1(false) , m_vblankTicks(0) , m_inVblank(false) , m_eeExecutionTicks(0) , m_iopExecutionTicks(0) , m_spuUpdateTicks(SPU_UPDATE_TICKS) , m_eeProfilerZone(CProfiler::GetInstance().RegisterZone("EE")) , m_iopProfilerZone(CProfiler::GetInstance().RegisterZone("IOP")) , m_spuProfilerZone(CProfiler::GetInstance().RegisterZone("SPU")) , m_gsSyncProfilerZone(CProfiler::GetInstance().RegisterZone("GSSYNC")) , m_otherProfilerZone(CProfiler::GetInstance().RegisterZone("OTHER")) { static const std::pair basicDirectorySettings[] = { std::make_pair(PREF_PS2_HOST_DIRECTORY, PREF_PS2_HOST_DIRECTORY_DEFAULT), std::make_pair(PREF_PS2_MC0_DIRECTORY, PREF_PS2_MC0_DIRECTORY_DEFAULT), std::make_pair(PREF_PS2_MC1_DIRECTORY, PREF_PS2_MC1_DIRECTORY_DEFAULT), }; for(const auto& basicDirectorySetting : basicDirectorySettings) { auto setting = basicDirectorySetting.first; auto path = basicDirectorySetting.second; auto absolutePath = CAppConfig::GetBasePath() / path; Framework::PathUtils::EnsurePathExists(absolutePath); CAppConfig::GetInstance().RegisterPreferencePath(setting, absolutePath); } CAppConfig::GetInstance().RegisterPreferencePath(PREF_PS2_CDROM0_PATH, ""); m_iop = std::make_unique(true); m_iopOs = std::make_shared(m_iop->m_cpu, m_iop->m_executor, m_iop->m_ram, PS2::IOP_RAM_SIZE, m_iop->m_scratchPad); m_ee = std::make_unique(m_iop->m_ram, *m_iopOs); m_ee->m_os->OnRequestLoadExecutable.connect(boost::bind(&CPS2VM::ReloadExecutable, this, _1, _2)); } CPS2VM::~CPS2VM() { { //Big hack to force deletion of the IopBios m_iop->SetBios(Iop::BiosBasePtr()); m_iopOs.reset(); } } ////////////////////////////////////////////////// //Various Message Functions ////////////////////////////////////////////////// void CPS2VM::CreateGSHandler(const CGSHandler::FactoryFunction& factoryFunction) { if(m_ee->m_gs != nullptr) return; m_mailBox.SendCall([this, factoryFunction] () { CreateGsHandlerImpl(factoryFunction); }, true); } CGSHandler* CPS2VM::GetGSHandler() { return m_ee->m_gs; } void CPS2VM::DestroyGSHandler() { if(m_ee->m_gs == nullptr) return; m_mailBox.SendCall([this] () { DestroyGsHandlerImpl(); }, true); } void CPS2VM::CreatePadHandler(const CPadHandler::FactoryFunction& factoryFunction) { if(m_pad != nullptr) return; m_mailBox.SendCall([this, factoryFunction] () { CreatePadHandlerImpl(factoryFunction); }, true); } CPadHandler* CPS2VM::GetPadHandler() { return m_pad; } void CPS2VM::DestroyPadHandler() { if(m_pad == nullptr) return; m_mailBox.SendCall([this] () { DestroyPadHandlerImpl(); }, true); } void CPS2VM::CreateSoundHandler(const CSoundHandler::FactoryFunction& factoryFunction) { if(m_soundHandler != nullptr) return; m_mailBox.SendCall([this, factoryFunction] () { CreateSoundHandlerImpl(factoryFunction); }, true); } void CPS2VM::DestroySoundHandler() { if(m_soundHandler == nullptr) return; m_mailBox.SendCall([this] () { DestroySoundHandlerImpl(); }, true); } CVirtualMachine::STATUS CPS2VM::GetStatus() const { return m_nStatus; } void CPS2VM::StepEe() { if(GetStatus() == RUNNING) return; m_singleStepEe = true; m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true); } void CPS2VM::StepIop() { if(GetStatus() == RUNNING) return; m_singleStepIop = true; m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true); } void CPS2VM::StepVu0() { if(GetStatus() == RUNNING) return; m_singleStepVu0 = true; m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true); } void CPS2VM::StepVu1() { if(GetStatus() == RUNNING) return; m_singleStepVu1 = true; m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true); } void CPS2VM::Resume() { if(m_nStatus == RUNNING) return; m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true); OnRunningStateChange(); } void CPS2VM::Pause() { if(m_nStatus == PAUSED) return; m_mailBox.SendCall(std::bind(&CPS2VM::PauseImpl, this), true); OnMachineStateChange(); OnRunningStateChange(); } void CPS2VM::Reset() { assert(m_nStatus == PAUSED); ResetVM(); } void CPS2VM::DumpEEIntcHandlers() { // if(m_pOS == NULL) return; if(m_nStatus != PAUSED) return; m_ee->m_os->DumpIntcHandlers(); } void CPS2VM::DumpEEDmacHandlers() { // if(m_pOS == NULL) return; if(m_nStatus != PAUSED) return; m_ee->m_os->DumpDmacHandlers(); } void CPS2VM::Initialize() { CreateVM(); m_nEnd = false; m_thread = std::thread([&] () { EmuThread(); }); } void CPS2VM::Destroy() { m_mailBox.SendCall(std::bind(&CPS2VM::DestroyImpl, this)); m_thread.join(); DestroyVM(); } boost::filesystem::path CPS2VM::GetStateDirectoryPath() { return CAppConfig::GetBasePath() / boost::filesystem::path("states/"); } boost::filesystem::path CPS2VM::GenerateStatePath(unsigned int slot) const { auto stateFileName = string_format("%s.st%d.zip", m_ee->m_os->GetExecutableName(), slot); return GetStateDirectoryPath() / boost::filesystem::path(stateFileName); } std::future CPS2VM::SaveState(const filesystem::path& statePath) { auto promise = std::make_shared>(); auto future = promise->get_future(); m_mailBox.SendCall( [this, promise, statePath] () { auto result = SaveVMState(statePath); promise->set_value(result); } ); return future; } std::future CPS2VM::LoadState(const filesystem::path& statePath) { auto promise = std::make_shared>(); auto future = promise->get_future(); m_mailBox.SendCall( [this, promise, statePath] () { auto result = LoadVMState(statePath); promise->set_value(result); } ); return future; } void CPS2VM::TriggerFrameDump(const FrameDumpCallback& frameDumpCallback) { m_mailBox.SendCall( [=] () { std::unique_lock frameDumpCallbackMutexLock(m_frameDumpCallbackMutex); if(m_frameDumpCallback) return; m_frameDumpCallback = frameDumpCallback; }, false ); } CPS2VM::CPU_UTILISATION_INFO CPS2VM::GetCpuUtilisationInfo() const { return m_cpuUtilisation; } #ifdef DEBUGGER_INCLUDED #define TAGS_SECTION_TAGS ("tags") #define TAGS_SECTION_EE_FUNCTIONS ("ee_functions") #define TAGS_SECTION_EE_COMMENTS ("ee_comments") #define TAGS_SECTION_VU1_FUNCTIONS ("vu1_functions") #define TAGS_SECTION_VU1_COMMENTS ("vu1_comments") #define TAGS_SECTION_IOP ("iop") #define TAGS_SECTION_IOP_FUNCTIONS ("functions") #define TAGS_SECTION_IOP_COMMENTS ("comments") #define TAGS_PATH ("tags/") std::string CPS2VM::MakeDebugTagsPackagePath(const char* packageName) { auto tagsPath = CAppConfig::GetBasePath() / boost::filesystem::path(TAGS_PATH); Framework::PathUtils::EnsurePathExists(tagsPath); auto tagsPackagePath = tagsPath / (std::string(packageName) + std::string(".tags.xml")); return tagsPackagePath.string(); } void CPS2VM::LoadDebugTags(const char* packageName) { try { std::string packagePath = MakeDebugTagsPackagePath(packageName); Framework::CStdStream stream(packagePath.c_str(), "rb"); boost::scoped_ptr document(Framework::Xml::CParser::ParseDocument(stream)); Framework::Xml::CNode* tagsNode = document->Select(TAGS_SECTION_TAGS); if(!tagsNode) return; m_ee->m_EE.m_Functions.Unserialize(tagsNode, TAGS_SECTION_EE_FUNCTIONS); m_ee->m_EE.m_Comments.Unserialize(tagsNode, TAGS_SECTION_EE_COMMENTS); m_ee->m_VU1.m_Functions.Unserialize(tagsNode, TAGS_SECTION_VU1_FUNCTIONS); m_ee->m_VU1.m_Comments.Unserialize(tagsNode, TAGS_SECTION_VU1_COMMENTS); { Framework::Xml::CNode* sectionNode = tagsNode->Select(TAGS_SECTION_IOP); if(sectionNode) { m_iop->m_cpu.m_Functions.Unserialize(sectionNode, TAGS_SECTION_IOP_FUNCTIONS); m_iop->m_cpu.m_Comments.Unserialize(sectionNode, TAGS_SECTION_IOP_COMMENTS); m_iopOs->LoadDebugTags(sectionNode); } } } catch(...) { } } void CPS2VM::SaveDebugTags(const char* packageName) { try { std::string packagePath = MakeDebugTagsPackagePath(packageName); Framework::CStdStream stream(packagePath.c_str(), "wb"); boost::scoped_ptr document(new Framework::Xml::CNode(TAGS_SECTION_TAGS, true)); m_ee->m_EE.m_Functions.Serialize(document.get(), TAGS_SECTION_EE_FUNCTIONS); m_ee->m_EE.m_Comments.Serialize(document.get(), TAGS_SECTION_EE_COMMENTS); m_ee->m_VU1.m_Functions.Serialize(document.get(), TAGS_SECTION_VU1_FUNCTIONS); m_ee->m_VU1.m_Comments.Serialize(document.get(), TAGS_SECTION_VU1_COMMENTS); { Framework::Xml::CNode* iopNode = new Framework::Xml::CNode(TAGS_SECTION_IOP, true); m_iop->m_cpu.m_Functions.Serialize(iopNode, TAGS_SECTION_IOP_FUNCTIONS); m_iop->m_cpu.m_Comments.Serialize(iopNode, TAGS_SECTION_IOP_COMMENTS); m_iopOs->SaveDebugTags(iopNode); document->InsertNode(iopNode); } Framework::Xml::CWriter::WriteDocument(stream, document.get()); } catch(...) { } } #endif ////////////////////////////////////////////////// //Non extern callable methods ////////////////////////////////////////////////// void CPS2VM::CreateVM() { ResetVM(); } void CPS2VM::ResetVM() { m_ee->Reset(); m_iop->Reset(); m_iop->SetBios(m_iopOs); //LoadBIOS(); if(m_ee->m_gs != NULL) { m_ee->m_gs->Reset(); } m_iopOs->Reset(std::make_shared(m_ee->m_sif, m_ee->m_ram, m_iop->m_ram)); CDROM0_SyncPath(); m_iopOs->GetIoman()->RegisterDevice("host", Iop::CIoman::DevicePtr(new Iop::Ioman::CDirectoryDevice(PREF_PS2_HOST_DIRECTORY))); m_iopOs->GetIoman()->RegisterDevice("mc0", Iop::CIoman::DevicePtr(new Iop::Ioman::CDirectoryDevice(PREF_PS2_MC0_DIRECTORY))); m_iopOs->GetIoman()->RegisterDevice("mc1", Iop::CIoman::DevicePtr(new Iop::Ioman::CDirectoryDevice(PREF_PS2_MC1_DIRECTORY))); m_iopOs->GetIoman()->RegisterDevice("cdrom", Iop::CIoman::DevicePtr(new Iop::Ioman::COpticalMediaDevice(m_cdrom0))); m_iopOs->GetIoman()->RegisterDevice("cdrom0", Iop::CIoman::DevicePtr(new Iop::Ioman::COpticalMediaDevice(m_cdrom0))); m_iopOs->GetLoadcore()->SetLoadExecutableHandler(std::bind(&CPS2OS::LoadExecutable, m_ee->m_os, std::placeholders::_1, std::placeholders::_2)); m_vblankTicks = ONSCREEN_TICKS; m_inVblank = false; m_eeExecutionTicks = 0; m_iopExecutionTicks = 0; m_spuUpdateTicks = SPU_UPDATE_TICKS; m_currentSpuBlock = 0; RegisterModulesInPadHandler(); } void CPS2VM::DestroyVM() { CDROM0_Reset(); } bool CPS2VM::SaveVMState(const filesystem::path& statePath) { if(m_ee->m_gs == NULL) { printf("PS2VM: GS Handler was not instancied. Cannot save state.\r\n"); return false; } try { auto stateStream = Framework::CreateOutputStdStream(statePath.native()); Framework::CZipArchiveWriter archive; m_ee->SaveState(archive); m_iop->SaveState(archive); m_ee->m_gs->SaveState(archive); archive.Write(stateStream); } catch(...) { return false; } return true; } bool CPS2VM::LoadVMState(const filesystem::path& statePath) { if(m_ee->m_gs == NULL) { printf("PS2VM: GS Handler was not instancied. Cannot load state.\r\n"); return false; } try { auto stateStream = Framework::CreateInputStdStream(statePath.native()); Framework::CZipArchiveReader archive(stateStream); try { m_ee->LoadState(archive); m_iop->LoadState(archive); m_ee->m_gs->LoadState(archive); } catch(...) { //Any error that occurs in the previous block is critical PauseImpl(); throw; } } catch(...) { return false; } OnMachineStateChange(); return true; } void CPS2VM::PauseImpl() { m_nStatus = PAUSED; } void CPS2VM::ResumeImpl() { #ifdef DEBUGGER_INCLUDED m_ee->m_executor.DisableBreakpointsOnce(); m_iop->m_executor.DisableBreakpointsOnce(); m_ee->m_vpu1->DisableBreakpointsOnce(); #endif m_nStatus = RUNNING; } void CPS2VM::DestroyImpl() { DestroyGsHandlerImpl(); DestroyPadHandlerImpl(); DestroySoundHandlerImpl(); m_nEnd = true; } void CPS2VM::CreateGsHandlerImpl(const CGSHandler::FactoryFunction& factoryFunction) { m_ee->m_gs = factoryFunction(); m_ee->m_gs->Initialize(); m_ee->m_gs->OnNewFrame.connect(boost::bind(&CPS2VM::OnGsNewFrame, this)); } void CPS2VM::DestroyGsHandlerImpl() { if(m_ee->m_gs == nullptr) return; m_ee->m_gs->Release(); delete m_ee->m_gs; m_ee->m_gs = nullptr; } void CPS2VM::CreatePadHandlerImpl(const CPadHandler::FactoryFunction& factoryFunction) { m_pad = factoryFunction(); RegisterModulesInPadHandler(); } void CPS2VM::DestroyPadHandlerImpl() { if(m_pad == nullptr) return; delete m_pad; m_pad = nullptr; } void CPS2VM::CreateSoundHandlerImpl(const CSoundHandler::FactoryFunction& factoryFunction) { m_soundHandler = factoryFunction(); } void CPS2VM::DestroySoundHandlerImpl() { if(m_soundHandler == nullptr) return; delete m_soundHandler; m_soundHandler = nullptr; } void CPS2VM::OnGsNewFrame() { #ifdef DEBUGGER_INCLUDED std::unique_lock dumpFrameCallbackMutexLock(m_frameDumpCallbackMutex); if(m_dumpingFrame && !m_frameDump.GetPackets().empty()) { m_ee->m_gs->SetFrameDump(nullptr); m_frameDumpCallback(m_frameDump); m_dumpingFrame = false; m_frameDumpCallback = FrameDumpCallback(); } else if(m_frameDumpCallback) { m_frameDump.Reset(); memcpy(m_frameDump.GetInitialGsRam(), m_ee->m_gs->GetRam(), CGSHandler::RAMSIZE); memcpy(m_frameDump.GetInitialGsRegisters(), m_ee->m_gs->GetRegisters(), CGSHandler::REGISTER_MAX * sizeof(uint64)); m_frameDump.SetInitialSMODE2(m_ee->m_gs->GetSMODE2()); m_ee->m_gs->SetFrameDump(&m_frameDump); m_dumpingFrame = true; } #endif } void CPS2VM::UpdateEe() { #ifdef PROFILE CProfilerZone profilerZone(m_eeProfilerZone); #endif while(m_eeExecutionTicks > 0) { int executed = m_ee->ExecuteCpu(m_singleStepEe ? 1 : m_eeExecutionTicks); if(m_ee->IsCpuIdle()) { #ifdef PROFILE m_cpuUtilisation.eeIdleTicks += (m_eeExecutionTicks - executed); #endif executed = m_eeExecutionTicks; } #ifdef PROFILE m_cpuUtilisation.eeTotalTicks += executed; #endif m_ee->m_vpu0->Execute(m_singleStepVu0 ? 1 : executed); m_ee->m_vpu1->Execute(m_singleStepVu1 ? 1 : executed); m_eeExecutionTicks -= executed; m_ee->CountTicks(executed); m_vblankTicks -= executed; #ifdef DEBUGGER_INCLUDED if(m_singleStepEe) break; if(m_ee->m_executor.MustBreak()) break; #endif } } void CPS2VM::UpdateIop() { #ifdef PROFILE CProfilerZone profilerZone(m_iopProfilerZone); #endif while(m_iopExecutionTicks > 0) { int executed = m_iop->ExecuteCpu(m_singleStepIop ? 1 : m_iopExecutionTicks); if(m_iop->IsCpuIdle()) { #ifdef PROFILE m_cpuUtilisation.iopIdleTicks += (m_iopExecutionTicks - executed); #endif executed = m_iopExecutionTicks; } #ifdef PROFILE m_cpuUtilisation.iopTotalTicks += executed; #endif m_iopExecutionTicks -= executed; m_spuUpdateTicks -= executed; m_iop->CountTicks(executed); #ifdef DEBUGGER_INCLUDED if(m_singleStepIop) break; if(m_iop->m_executor.MustBreak()) break; #endif } } void CPS2VM::UpdateSpu() { #ifdef PROFILE CProfilerZone profilerZone(m_spuProfilerZone); #endif unsigned int blockOffset = (BLOCK_SIZE * m_currentSpuBlock); int16* samplesSpu0 = m_samples + blockOffset; m_iop->m_spuCore0.Render(samplesSpu0, BLOCK_SIZE, DST_SAMPLE_RATE); if(m_iop->m_spuCore1.IsEnabled()) { int16 samplesSpu1[BLOCK_SIZE]; m_iop->m_spuCore1.Render(samplesSpu1, BLOCK_SIZE, DST_SAMPLE_RATE); for(unsigned int i = 0; i < BLOCK_SIZE; i++) { int32 resultSample = static_cast(samplesSpu0[i]) + static_cast(samplesSpu1[i]); resultSample = std::max(resultSample, SHRT_MIN); resultSample = std::min(resultSample, SHRT_MAX); samplesSpu0[i] = static_cast(resultSample); } } m_currentSpuBlock++; if(m_currentSpuBlock == BLOCK_COUNT) { if(m_soundHandler) { if(m_soundHandler->HasFreeBuffers()) { m_soundHandler->RecycleBuffers(); } m_soundHandler->Write(m_samples, BLOCK_SIZE * BLOCK_COUNT, DST_SAMPLE_RATE); } m_currentSpuBlock = 0; } } void CPS2VM::CDROM0_SyncPath() { //TODO: Check if there's an m_cdrom0 already //TODO: Check if files are linked to this m_cdrom0 too and do something with them CDROM0_Reset(); auto path = CAppConfig::GetInstance().GetPreferencePath(PREF_PS2_CDROM0_PATH); if(!path.empty()) { try { m_cdrom0 = DiskUtils::CreateOpticalMediaFromPath(path); SetIopOpticalMedia(m_cdrom0.get()); } catch(const std::exception& Exception) { printf("PS2VM: Error mounting cdrom0 device: %s\r\n", Exception.what()); } } } void CPS2VM::CDROM0_Reset() { SetIopOpticalMedia(nullptr); m_cdrom0.reset(); } void CPS2VM::SetIopOpticalMedia(COpticalMedia* opticalMedia) { m_iopOs->GetCdvdfsv()->SetOpticalMedia(opticalMedia); m_iopOs->GetCdvdman()->SetOpticalMedia(opticalMedia); } void CPS2VM::RegisterModulesInPadHandler() { if(m_pad == nullptr) return; m_pad->RemoveAllListeners(); m_pad->InsertListener(m_iopOs->GetPadman()); m_pad->InsertListener(&m_iop->m_sio2); } void CPS2VM::ReloadExecutable(const char* executablePath, const CPS2OS::ArgumentList& arguments) { ResetVM(); m_ee->m_os->BootFromVirtualPath(executablePath, arguments); } void CPS2VM::EmuThread() { fesetround(FE_TOWARDZERO); CProfiler::GetInstance().SetWorkThread(); #ifdef PROFILE CProfilerZone profilerZone(m_otherProfilerZone); #endif m_ee->m_executor.AddExceptionHandler(); while(1) { while(m_mailBox.IsPending()) { m_mailBox.ReceiveCall(); } if(m_nEnd) break; if(m_nStatus == PAUSED) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if(m_nStatus == RUNNING) { if(m_spuUpdateTicks <= 0) { UpdateSpu(); m_spuUpdateTicks += SPU_UPDATE_TICKS; } //EE execution { //Check vblank stuff if(m_vblankTicks <= 0) { m_inVblank = !m_inVblank; if(m_inVblank) { m_vblankTicks += VBLANK_TICKS; m_ee->NotifyVBlankStart(); m_iop->NotifyVBlankStart(); if(m_ee->m_gs != NULL) { #ifdef PROFILE CProfilerZone profilerZone(m_gsSyncProfilerZone); #endif m_ee->m_gs->SetVBlank(); } if(m_pad != NULL) { m_pad->Update(m_ee->m_ram); } #ifdef PROFILE { CProfiler::GetInstance().CountCurrentZone(); auto stats = CProfiler::GetInstance().GetStats(); ProfileFrameDone(stats); CProfiler::GetInstance().Reset(); } m_cpuUtilisation = CPU_UTILISATION_INFO(); #endif } else { m_vblankTicks += ONSCREEN_TICKS; m_ee->NotifyVBlankEnd(); m_iop->NotifyVBlankEnd(); if(m_ee->m_gs != NULL) { m_ee->m_gs->ResetVBlank(); } } } //EE CPU is 8 times faster than the IOP CPU static const int tickStep = 4800; m_eeExecutionTicks += tickStep; m_iopExecutionTicks += tickStep / 8; UpdateEe(); UpdateIop(); } #ifdef DEBUGGER_INCLUDED if( m_ee->m_executor.MustBreak() || m_iop->m_executor.MustBreak() || m_ee->m_vpu1->MustBreak() || m_singleStepEe || m_singleStepIop || m_singleStepVu0 || m_singleStepVu1) { m_nStatus = PAUSED; m_singleStepEe = false; m_singleStepIop = false; m_singleStepVu0 = false; m_singleStepVu1 = false; OnRunningStateChange(); OnMachineStateChange(); } #endif } } m_ee->m_executor.RemoveExceptionHandler(); }