mirror of https://github.com/PCSX2/pcsx2.git
newHostVM: More exception / error handling mess.
git-svn-id: http://pcsx2.googlecode.com/svn/branches/newHostVM@3994 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
parent
dc7f00d05e
commit
a7726871dc
|
@ -259,43 +259,40 @@ public: \
|
|||
virtual classname& SetStreamName( const wxString& name ) { StreamName = name; return *this; } \
|
||||
virtual classname& SetStreamName( const char* name ) { StreamName = fromUTF8(name); return *this; }
|
||||
|
||||
#define DEFINE_STREAM_EXCEPTION( classname, parent, message ) \
|
||||
DEFINE_RUNTIME_EXCEPTION( classname, parent, message ) \
|
||||
#define DEFINE_STREAM_EXCEPTION( classname, parent ) \
|
||||
DEFINE_RUNTIME_EXCEPTION( classname, parent, wxEmptyString ) \
|
||||
classname( const wxString& filename ) { \
|
||||
StreamName = filename; \
|
||||
SetBothMsgs(message); \
|
||||
} \
|
||||
DEFINE_STREAM_EXCEPTION_ACCESSORS( classname )
|
||||
|
||||
// Generic stream error. Contains the name of the stream and a message.
|
||||
// This exception is usually thrown via derived classes, except in the (rare) case of a
|
||||
// generic / unknown error.
|
||||
// A generic base error class for bad streams -- corrupted data, sudden closures, loss of
|
||||
// connection, or anything else that would indicate a failure to open a stream or read the
|
||||
// data after the stream was successfully opened.
|
||||
//
|
||||
class Stream : public RuntimeError
|
||||
class BadStream : public RuntimeError
|
||||
{
|
||||
DEFINE_STREAM_EXCEPTION( Stream, RuntimeError, wxLt("General file operation error.") )
|
||||
DEFINE_STREAM_EXCEPTION( BadStream, RuntimeError )
|
||||
|
||||
public:
|
||||
wxString StreamName; // name of the stream (if applicable)
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
// A generic base error class for bad streams -- corrupted data, sudden closures, loss of
|
||||
// connection, or anything else that would indicate a failure to read the data after the
|
||||
// stream was successfully opened.
|
||||
//
|
||||
class BadStream : public Stream
|
||||
{
|
||||
DEFINE_STREAM_EXCEPTION( BadStream, Stream, wxLt("File data is corrupted or incomplete, or the stream connection closed unexpectedly.") )
|
||||
protected:
|
||||
void _formatDiagMsg( FastFormatUnicode& dest ) const;
|
||||
void _formatUserMsg( FastFormatUnicode& dest ) const;
|
||||
};
|
||||
|
||||
// A generic exception for odd-ball stream creation errors.
|
||||
//
|
||||
class CannotCreateStream : public Stream
|
||||
class CannotCreateStream : public BadStream
|
||||
{
|
||||
DEFINE_STREAM_EXCEPTION( CannotCreateStream, Stream, wxLt("File could not be created or opened.") )
|
||||
DEFINE_STREAM_EXCEPTION( CannotCreateStream, BadStream )
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
// Exception thrown when an attempt to open a non-existent file is made.
|
||||
|
@ -304,22 +301,31 @@ public: \
|
|||
class FileNotFound : public CannotCreateStream
|
||||
{
|
||||
public:
|
||||
DEFINE_STREAM_EXCEPTION( FileNotFound, CannotCreateStream, wxLt("File not found.") )
|
||||
DEFINE_STREAM_EXCEPTION( FileNotFound, CannotCreateStream )
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
class AccessDenied : public CannotCreateStream
|
||||
{
|
||||
public:
|
||||
DEFINE_STREAM_EXCEPTION( AccessDenied, CannotCreateStream, wxLt("Permission denied to file.") )
|
||||
DEFINE_STREAM_EXCEPTION( AccessDenied, CannotCreateStream )
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
// EndOfStream can be used either as an error, or used just as a shortcut for manual
|
||||
// feof checks.
|
||||
//
|
||||
class EndOfStream : public Stream
|
||||
class EndOfStream : public BadStream
|
||||
{
|
||||
public:
|
||||
DEFINE_STREAM_EXCEPTION( EndOfStream, Stream, wxLt("Unexpected end of file or stream.") );
|
||||
DEFINE_STREAM_EXCEPTION( EndOfStream, BadStream )
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
#ifdef __WXMSW__
|
||||
|
|
|
@ -219,11 +219,11 @@ static __fi PageProtectionMode PageAccess_Any()
|
|||
namespace HostSys
|
||||
{
|
||||
void* MmapReserve(uptr base, size_t size);
|
||||
void MmapCommit(uptr base, size_t size, const PageProtectionMode& mode);
|
||||
bool MmapCommit(uptr base, size_t size, const PageProtectionMode& mode);
|
||||
void MmapReset(uptr base, size_t size);
|
||||
|
||||
void* MmapReservePtr(void* base, size_t size);
|
||||
void MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode);
|
||||
bool MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode);
|
||||
void MmapResetPtr(void* base, size_t size);
|
||||
|
||||
// Maps a block of memory for use as a recompiled code buffer.
|
||||
|
|
|
@ -163,10 +163,6 @@ protected:
|
|||
|
||||
// This function is called for every committed block.
|
||||
virtual void OnCommittedBlock( void* block )=0;
|
||||
virtual void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled )
|
||||
{
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -212,9 +208,9 @@ public:
|
|||
|
||||
virtual void* Reserve( uint size, uptr base = 0, uptr upper_bounds = 0 );
|
||||
virtual void Reset();
|
||||
virtual bool TryResize( uint newsize );
|
||||
|
||||
void OnCommittedBlock( void* block );
|
||||
void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled );
|
||||
|
||||
SpatialArrayReserve& SetBlockCount( uint blocks );
|
||||
SpatialArrayReserve& SetBlockSizeInPages( uint bytes );
|
||||
|
|
|
@ -60,6 +60,26 @@ extern void pcsx2_aligned_free(void* pmem);
|
|||
# define _aligned_realloc pcsx2_aligned_realloc
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// pxDoOutOfMemory
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
typedef void FnType_OutOfMemory( uptr blocksize );
|
||||
typedef FnType_OutOfMemory* Fnptr_OutOfMemory;
|
||||
|
||||
// This method is meant to be assigned by applications that link against pxWex. It is called
|
||||
// (invoked) prior to most pxWex built-in memory/array classes throwing exceptions, and can be
|
||||
// used by an application to remove unneeded memory allocations and/or reduce internal cache
|
||||
// reserves.
|
||||
//
|
||||
// Example: PCSX2 uses several bloated recompiler code caches. Larger caches improve performance,
|
||||
// however a rouge cache growth could cause memory constraints in the operating system. If an out-
|
||||
// of-memory error occurs, PCSX2's implementation of this function attempts to reset all internal
|
||||
// recompiler caches. This can typically free up 100-150 megs of memory, and will allow the app
|
||||
// to continue running without crashing or hanging the operating system, etc.
|
||||
//
|
||||
extern Fnptr_OutOfMemory pxDoOutOfMemory;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// BaseScopedAlloc
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
// for lack of a better place...
|
||||
Fnptr_OutOfMemory pxDoOutOfMemory = NULL;
|
||||
|
||||
static wxString GetTranslation( const wxChar* msg )
|
||||
{
|
||||
return msg ? wxGetTranslation( msg ) : wxEmptyString;
|
||||
|
@ -195,22 +198,30 @@ Exception::RuntimeError::RuntimeError( const std::exception& ex, const wxString&
|
|||
Exception::OutOfMemory::OutOfMemory( const wxString& allocdesc )
|
||||
{
|
||||
AllocDescription = allocdesc;
|
||||
m_message_user = _("Memory allocation failure! Your system has insufficient memory or resources to meet PCSX2's lofty needs.");
|
||||
}
|
||||
|
||||
wxString Exception::OutOfMemory::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode details;
|
||||
if (!m_message_diag.IsEmpty())
|
||||
details.Write(":\n%s",m_message_diag.c_str());
|
||||
FastFormatUnicode retmsg;
|
||||
retmsg.Write(L"Out of memory");
|
||||
if (!AllocDescription.IsEmpty())
|
||||
retmsg.Write(L" while allocating '%s'", AllocDescription.c_str());
|
||||
|
||||
return pxsFmt(L"Out of memory while allocating '%s'%s", AllocDescription.c_str(), details.c_str());
|
||||
if (!m_message_diag.IsEmpty())
|
||||
retmsg.Write(L":\n%s", m_message_diag.c_str());
|
||||
|
||||
return retmsg;
|
||||
}
|
||||
|
||||
wxString Exception::OutOfMemory::FormatDisplayMessage() const
|
||||
{
|
||||
if (m_message_user.IsEmpty()) return FormatDisplayMessage();
|
||||
return m_message_user;
|
||||
FastFormatUnicode retmsg;
|
||||
retmsg.Write( L"%s", _("Oh noes! Out of memory!") );
|
||||
|
||||
if (!m_message_diag.IsEmpty())
|
||||
retmsg.Write(L"\n\n%s", m_message_diag.c_str());
|
||||
|
||||
return retmsg;
|
||||
}
|
||||
|
||||
|
||||
|
@ -225,17 +236,31 @@ Exception::VirtualMemoryMapConflict::VirtualMemoryMapConflict( const wxString& a
|
|||
|
||||
wxString Exception::VirtualMemoryMapConflict::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode details;
|
||||
if (!m_message_diag.IsEmpty())
|
||||
details.Write(":\n%s",m_message_diag.c_str());
|
||||
FastFormatUnicode retmsg;
|
||||
retmsg.Write(L"Virtual memory map failed");
|
||||
if (!AllocDescription.IsEmpty())
|
||||
retmsg.Write(L" while reserving '%s'", AllocDescription.c_str());
|
||||
|
||||
return pxsFmt(L"Out of virtual memory while reserving '%s'%s", AllocDescription.c_str(), details.c_str());
|
||||
if (!m_message_diag.IsEmpty())
|
||||
retmsg.Write(L":\n%s", m_message_diag.c_str());
|
||||
|
||||
return retmsg;
|
||||
}
|
||||
|
||||
wxString Exception::VirtualMemoryMapConflict::FormatDisplayMessage() const
|
||||
{
|
||||
if (m_message_user.IsEmpty()) return FormatDisplayMessage();
|
||||
return m_message_user;
|
||||
FastFormatUnicode retmsg;
|
||||
retmsg.Write( L"%s",
|
||||
pxE( ".Error:VirtualMemoryMap",
|
||||
L"There is not enough virtual memory available, or necessary virtual memory "
|
||||
L"mappings have already been reserved by other processes, services, or DLLs."
|
||||
)
|
||||
);
|
||||
|
||||
if (!m_message_diag.IsEmpty())
|
||||
retmsg.Write(L"\n\n%s", m_message_diag.c_str());
|
||||
|
||||
return retmsg;
|
||||
}
|
||||
|
||||
|
||||
|
@ -250,20 +275,120 @@ wxString Exception::CancelEvent::FormatDisplayMessage() const
|
|||
return L"Action canceled: " + m_message_diag;
|
||||
}
|
||||
|
||||
wxString Exception::Stream::FormatDiagnosticMessage() const
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Exception::BadStream (implementations)
|
||||
// --------------------------------------------------------------------------------------
|
||||
wxString Exception::BadStream::FormatDiagnosticMessage() const
|
||||
{
|
||||
return pxsFmt(
|
||||
L"%s\n\tFile/Object: %s",
|
||||
m_message_diag.c_str(), StreamName.c_str()
|
||||
);
|
||||
FastFormatUnicode retval;
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::Stream::FormatDisplayMessage() const
|
||||
wxString Exception::BadStream::FormatDisplayMessage() const
|
||||
{
|
||||
wxString retval( m_message_user );
|
||||
if (!StreamName.IsEmpty())
|
||||
retval += L"\n\n" + pxsFmt( _("Path: %s"), StreamName.c_str() );
|
||||
FastFormatUnicode retval;
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Exception::BadStream::_formatDiagMsg( FastFormatUnicode& dest ) const
|
||||
{
|
||||
dest.Write( L"Path: " );
|
||||
if (!StreamName.IsEmpty())
|
||||
dest.Write( L"%s", StreamName.c_str() );
|
||||
else
|
||||
dest.Write( L"[Unnamed or unknown]" );
|
||||
|
||||
if (!m_message_diag.IsEmpty())
|
||||
dest.Write(L"\n%s", m_message_diag.c_str());
|
||||
}
|
||||
|
||||
void Exception::BadStream::_formatUserMsg( FastFormatUnicode& dest ) const
|
||||
{
|
||||
dest.Write( _("Path: ") );
|
||||
if (!StreamName.IsEmpty())
|
||||
dest.Write( L"%s", StreamName.c_str() );
|
||||
else
|
||||
dest.Write( _("[Unnamed or unknown]") );
|
||||
|
||||
if (!m_message_user.IsEmpty())
|
||||
dest.Write(L"\n%s", m_message_user.c_str());
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Exception::CannotCreateStream (implementations)
|
||||
// --------------------------------------------------------------------------------------
|
||||
wxString Exception::CannotCreateStream::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("File could not be created.");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::CannotCreateStream::FormatDisplayMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write(_("A file could not be created."));
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Exception::FileNotFound (implementations)
|
||||
// --------------------------------------------------------------------------------------
|
||||
wxString Exception::FileNotFound::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("File not found.");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::FileNotFound::FormatDisplayMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write(_("File not found."));
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Exception::AccessDenied (implementations)
|
||||
// --------------------------------------------------------------------------------------
|
||||
wxString Exception::AccessDenied::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("Permission denied to file.");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::AccessDenied::FormatDisplayMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write(_("Permission denied while trying to open file, likely due to insufficient user account rights."));
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Exception::EndOfStream (implementations)
|
||||
// --------------------------------------------------------------------------------------
|
||||
wxString Exception::EndOfStream::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("Unexpected end of file or stream.");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::EndOfStream::FormatDisplayMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write(_("Unexpected end of file or stream encountered. File is probably truncated or corrupted."));
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -281,7 +406,7 @@ BaseException* Exception::FromErrno( const wxString& streamname, int errcode )
|
|||
{
|
||||
case EINVAL:
|
||||
pxFailDev( L"Invalid argument" );
|
||||
return &(new Exception::Stream( streamname ))->SetDiagMsg(L"Invalid argument? (likely caused by an unforgivable programmer error!)" );
|
||||
return &(new Exception::BadStream( streamname ))->SetDiagMsg(L"Invalid argument? (likely caused by an unforgivable programmer error!)" );
|
||||
|
||||
case EACCES: // Access denied!
|
||||
return new Exception::AccessDenied( streamname );
|
||||
|
@ -302,6 +427,6 @@ BaseException* Exception::FromErrno( const wxString& streamname, int errcode )
|
|||
return &(new Exception::BadStream( streamname ))->SetDiagMsg(L"Bad file number");
|
||||
|
||||
default:
|
||||
return &(new Exception::Stream( streamname ))->SetDiagMsg(pxsFmt( L"General file/stream error [errno: %d]", errcode ));
|
||||
return &(new Exception::BadStream( streamname ))->SetDiagMsg(pxsFmt( L"General file/stream error [errno: %d]", errcode ));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,44 @@ static __ri void PageSizeAssertionTest( size_t size )
|
|||
);
|
||||
}
|
||||
|
||||
// returns FALSE if the mprotect call fails with an ENOMEM.
|
||||
// Raises assertions on other types of POSIX errors (since those typically reflect invalid object
|
||||
// or memory states).
|
||||
static bool _memprotect( void* baseaddr, size_t size, const PageProtectionMode& mode )
|
||||
{
|
||||
PageSizeAssertionTest(size);
|
||||
|
||||
uint lnxmode = 0;
|
||||
|
||||
if (mode.CanWrite()) lnxmode |= PROT_WRITE;
|
||||
if (mode.CanRead()) lnxmode |= PROT_READ;
|
||||
if (mode.CanExecute()) lnxmode |= PROT_EXEC | PROT_READ;
|
||||
|
||||
const int result = mprotect( baseaddr, size, lnxmode );
|
||||
|
||||
if (result == 0) return true;
|
||||
|
||||
switch(errno)
|
||||
{
|
||||
case EINVAL:
|
||||
pxFailDev(pxsFmt(L"mprotect returned EINVAL @ 0x%08X -> 0x%08X (mode=%s)",
|
||||
baseaddr, (uptr)baseaddr+size, mode.ToString().c_str())
|
||||
);
|
||||
break;
|
||||
|
||||
case EACCES:
|
||||
pxFailDev(pxsFmt(L"mprotect returned EACCES @ 0x%08X -> 0x%08X (mode=%s)",
|
||||
baseaddr, (uptr)baseaddr+size, mode.ToString().c_str())
|
||||
);
|
||||
break;
|
||||
|
||||
case ENOMEM:
|
||||
// caller handles assertion or exception, or whatever.
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void* HostSys::MmapReservePtr(void* base, size_t size)
|
||||
{
|
||||
PageSizeAssertionTest(size);
|
||||
|
@ -46,7 +84,7 @@ void* HostSys::MmapReservePtr(void* base, size_t size)
|
|||
return mmap(base, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
}
|
||||
|
||||
void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode)
|
||||
bool HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
// In linux, reserved memory is automatically committed when its permissions are
|
||||
// changed to something other than PROT_NONE. If the user is committing memory
|
||||
|
@ -54,7 +92,12 @@ void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& m
|
|||
// later when the user changes permissions to something useful via calls to MemProtect).
|
||||
|
||||
if (mode.IsNone()) return;
|
||||
MemProtect( base, size, mode );
|
||||
|
||||
if (_memprotect( base, size, mode )) return true;
|
||||
|
||||
if (!pxDoOutOfMemory) return false;
|
||||
pxDoOutOfMemory(size);
|
||||
return _memprotect( base, size, mode );
|
||||
}
|
||||
|
||||
void HostSys::MmapResetPtr(void* base, size_t size)
|
||||
|
@ -82,9 +125,9 @@ void* HostSys::MmapReserve(uptr base, size_t size)
|
|||
return MmapReservePtr((void*)base, size);
|
||||
}
|
||||
|
||||
void HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode)
|
||||
bool HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
MmapCommitPtr( (void*)base, size, mode );
|
||||
return MmapCommitPtr( (void*)base, size, mode );
|
||||
}
|
||||
|
||||
void HostSys::MmapReset(uptr base, size_t size)
|
||||
|
@ -108,35 +151,12 @@ void HostSys::Munmap(uptr base, size_t size)
|
|||
|
||||
void HostSys::MemProtect( void* baseaddr, size_t size, const PageProtectionMode& mode )
|
||||
{
|
||||
PageSizeAssertionTest(size);
|
||||
|
||||
uint lnxmode = 0;
|
||||
|
||||
if (mode.CanWrite()) lnxmode |= PROT_WRITE;
|
||||
if (mode.CanRead()) lnxmode |= PROT_READ;
|
||||
if (mode.CanExecute()) lnxmode |= PROT_EXEC | PROT_READ;
|
||||
|
||||
int result = mprotect( baseaddr, size, lnxmode );
|
||||
|
||||
if (result != 0)
|
||||
if (!_memprotect(baseaddr, size, mode))
|
||||
{
|
||||
switch(errno)
|
||||
{
|
||||
case EINVAL:
|
||||
pxFailDev(pxsFmt(L"mprotect returned EINVAL @ 0x%08X -> 0x%08X (mode=%s)",
|
||||
baseaddr, (uptr)baseaddr+size, mode.ToString().c_str())
|
||||
);
|
||||
break;
|
||||
|
||||
case ENOMEM:
|
||||
throw Exception::OutOfMemory( pxsFmt( L"mprotect failed @ 0x%08X -> 0x%08X (mode=%s)",
|
||||
baseaddr, (uptr)baseaddr+size, mode.ToString().c_str())
|
||||
);
|
||||
break;
|
||||
|
||||
case EACCES:
|
||||
break;
|
||||
}
|
||||
throw Exception::OutOfMemory();
|
||||
throw Exception::OutOfMemory( "MemProtect" )
|
||||
.SetDiagMsg(pxsFmt( L"mprotect failed @ 0x%08X -> 0x%08X (mode=%s)",
|
||||
baseaddr, (uptr)baseaddr+size, mode.ToString().c_str()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,14 +167,19 @@ bool BaseVirtualMemoryReserve::TryResize( uint newsize )
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
void BaseVirtualMemoryReserve::CommitBlocks( uptr page, uint blocks )
|
||||
{
|
||||
const uint blocksbytes = blocks * m_blocksize * __pagesize;
|
||||
const uptr blocksbytes = blocks * m_blocksize * __pagesize;
|
||||
void* blockptr = (u8*)m_baseptr + (page * __pagesize);
|
||||
|
||||
// Depending on the operating system, one or both of these could fail if the system
|
||||
// is low on either physical ram or virtual memory.
|
||||
HostSys::MmapCommitPtr(blockptr, blocksbytes, m_prot_mode);
|
||||
// Depending on the operating system, this call could fail if the system is low on either
|
||||
// physical ram or virtual memory.
|
||||
if (!HostSys::MmapCommitPtr(blockptr, blocksbytes, m_prot_mode))
|
||||
{
|
||||
throw Exception::OutOfMemory(Name)
|
||||
.SetDiagMsg(pxsFmt("An additional %u blocks @ 0x%08x were requested, but could not be committed!", blocks, blockptr));
|
||||
}
|
||||
|
||||
u8* init = (u8*)blockptr;
|
||||
u8* endpos = init + blocksbytes;
|
||||
|
@ -189,28 +194,37 @@ void BaseVirtualMemoryReserve::OnPageFaultEvent(const PageFaultInfo& info, bool&
|
|||
sptr offset = (info.addr - (uptr)m_baseptr) / __pagesize;
|
||||
if ((offset < 0) || ((uptr)offset >= m_reserved)) return;
|
||||
|
||||
try {
|
||||
DoCommitAndProtect( offset );
|
||||
handled = true;
|
||||
}
|
||||
catch (Exception::OutOfMemory& ex)
|
||||
{
|
||||
handled = false;
|
||||
OnOutOfMemory( ex, (u8*)m_baseptr + (offset * __pagesize), handled );
|
||||
}
|
||||
#ifndef __WXMSW__
|
||||
// Linux Note! the SIGNAL handler is very limited in what it can do, and not only can't
|
||||
// we let the C++ exception try to unwind the stack, we may not be able to log it either.
|
||||
// (but we might as well try -- kernel/posix rules says not to do it, but Linux kernel
|
||||
// implementations seem to support it).
|
||||
// Note also that logging the exception and/or issuing an assertion dialog are always
|
||||
// possible if the thread handling the signal is not the main thread.
|
||||
|
||||
// In windows we can let exceptions bubble out of the page fault handler. SEH will more
|
||||
// or less handle them in a semi-expected way, and might even avoid a GPF long enough
|
||||
// for the system to log the error or something.
|
||||
|
||||
// In Linux, however, the SIGNAL handler is very limited in what it can do, and not only
|
||||
// can't we let the C++ exception try to unwind the stack, we can't really log it either.
|
||||
// We can't issue a proper assertion (requires user popup). We can't do jack or shit,
|
||||
// *unless* its attached to a debugger; then we can, at a bare minimum, trap it.
|
||||
|
||||
#ifndef __WXMSW__
|
||||
try {
|
||||
#endif
|
||||
throw Exception::OutOfMemory( L"Right Here" );
|
||||
DoCommitAndProtect( offset );
|
||||
handled = true;
|
||||
|
||||
#ifndef __WXMSW__
|
||||
}
|
||||
catch (Exception::BaseException& ex)
|
||||
{
|
||||
handled = false;
|
||||
wxTrap();
|
||||
if (!wxThread::IsMain())
|
||||
{
|
||||
pxFailRel( ex.FormatDiagnosticMessage() );
|
||||
}
|
||||
else
|
||||
{
|
||||
wxTrap();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -237,6 +251,33 @@ void SpatialArrayReserve::Reset()
|
|||
memzero_sse_a(m_blockbits.GetPtr(), _calcBlockBitArrayLength());
|
||||
}
|
||||
|
||||
// Important! The number of blocks of the array will be altered when using this method.
|
||||
//
|
||||
bool SpatialArrayReserve::TryResize( uint newsize )
|
||||
{
|
||||
uint newpages = (newsize + __pagesize - 1) / __pagesize;
|
||||
|
||||
// find the last allocated block -- we cannot be allowed to resize any smaller than that:
|
||||
|
||||
uint i;
|
||||
for (i=m_numblocks-1; i; --i)
|
||||
{
|
||||
uint bit = i & 7;
|
||||
if (m_blockbits[i / 8] & bit) break;
|
||||
}
|
||||
|
||||
uint pages_in_use = i * m_blocksize;
|
||||
if (newpages < pages_in_use) return false;
|
||||
|
||||
if (!__parent::TryResize( newsize )) return false;
|
||||
|
||||
// On success, we must re-calibrate the internal blockbits array.
|
||||
|
||||
m_blockbits.Resize( (m_numblocks + 7) / 8 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This method allows the programmer to specify the block size of the array as a function
|
||||
// of its reserved size. This function *must* be called *after* the reserve has been made,
|
||||
// and *before* the array contents have been accessed.
|
||||
|
@ -272,7 +313,7 @@ SpatialArrayReserve& SpatialArrayReserve::SetBlockSizeInPages( uint pages )
|
|||
return *this;
|
||||
}
|
||||
|
||||
// This method assigns the block size of the spatial array, in bytes. The actual size of
|
||||
// SetBlockSize assigns the block size of the spatial array, in bytes. The actual size of
|
||||
// each block will be rounded up to the nearest page size. The resulting size is returned.
|
||||
//
|
||||
// This method must be called prior to accessing or modifying the array contents. Calls to
|
||||
|
@ -295,11 +336,6 @@ void SpatialArrayReserve::OnCommittedBlock( void* block )
|
|||
m_commited += m_blocksize;
|
||||
}
|
||||
|
||||
void SpatialArrayReserve::OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// PageProtectionMode (implementations)
|
||||
|
|
|
@ -42,10 +42,26 @@ void* HostSys::MmapReservePtr(void* base, size_t size)
|
|||
return VirtualAlloc(base, size, MEM_RESERVE, PAGE_NOACCESS);
|
||||
}
|
||||
|
||||
void HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode)
|
||||
bool HostSys::MmapCommitPtr(void* base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
void* result = VirtualAlloc(base, size, MEM_COMMIT, ConvertToWinApi(mode));
|
||||
pxAssumeDev(result, L"VirtualAlloc COMMIT failed: " + Exception::WinApiError().GetMsgFromWindows());
|
||||
if (result) return true;
|
||||
|
||||
const DWORD errcode = GetLastError();
|
||||
if (errcode == ERROR_COMMITMENT_MINIMUM)
|
||||
{
|
||||
Console.Warning("(MmapCommit) Received windows error %u {Virtual Memory Minimum Too Low}.", ERROR_COMMITMENT_MINIMUM);
|
||||
Sleep(1000); // Cut windows some time to rework its memory...
|
||||
}
|
||||
else if (errcode != ERROR_NOT_ENOUGH_MEMORY && errcode != ERROR_OUTOFMEMORY)
|
||||
{
|
||||
pxFailDev(L"VirtualAlloc COMMIT failed: " + Exception::WinApiError().GetMsgFromWindows());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pxDoOutOfMemory) return false;
|
||||
pxDoOutOfMemory(size);
|
||||
return VirtualAlloc(base, size, MEM_COMMIT, ConvertToWinApi(mode)) != NULL;
|
||||
}
|
||||
|
||||
void HostSys::MmapResetPtr(void* base, size_t size)
|
||||
|
@ -59,9 +75,9 @@ void* HostSys::MmapReserve(uptr base, size_t size)
|
|||
return MmapReservePtr((void*)base, size);
|
||||
}
|
||||
|
||||
void HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode)
|
||||
bool HostSys::MmapCommit(uptr base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
MmapCommitPtr( (void*)base, size, mode );
|
||||
return MmapCommitPtr( (void*)base, size, mode );
|
||||
}
|
||||
|
||||
void HostSys::MmapReset(uptr base, size_t size)
|
||||
|
|
|
@ -472,16 +472,16 @@ int GetPS2ElfName( wxString& name )
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
catch (Exception::BadStream& ex)
|
||||
{
|
||||
Console.Error(ex.FormatDiagnosticMessage());
|
||||
return 0; // ISO error
|
||||
}
|
||||
catch( Exception::FileNotFound& )
|
||||
{
|
||||
//Console.Warning(ex.FormatDiagnosticMessage());
|
||||
return 0; // no SYSTEM.CNF, not a PS1/PS2 disc.
|
||||
}
|
||||
catch (Exception::BadStream& ex)
|
||||
{
|
||||
Console.Error(ex.FormatDiagnosticMessage());
|
||||
return 0; // ISO error
|
||||
}
|
||||
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
FILE *fp;
|
||||
|
|
|
@ -674,18 +674,34 @@ SysCorePlugins *g_plugins = NULL;
|
|||
// Plugin-related Exception Implementations
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
wxString Exception::SaveStateLoadError::FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("Savestate is corrupt or incomplete!");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
wxString Exception::SaveStateLoadError::FormatDisplayMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write(_("The savestate cannot be loaded, as it appears to be corrupt or incomplete."));
|
||||
_formatUserMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
Exception::PluginOpenError::PluginOpenError( PluginsEnum_t pid )
|
||||
{
|
||||
PluginId = pid;
|
||||
m_message_diag = L"%s plugin failed to open!";
|
||||
m_message_user = L"%s plugin failed to open. Your computer may have insufficient resources, or incompatible hardware/drivers.";
|
||||
m_message_user = _("%s plugin failed to open. Your computer may have insufficient resources, or incompatible hardware/drivers.");
|
||||
}
|
||||
|
||||
Exception::PluginInitError::PluginInitError( PluginsEnum_t pid )
|
||||
{
|
||||
PluginId = pid;
|
||||
m_message_diag = L"%s plugin initialization failed!";
|
||||
m_message_user = L"%s plugin failed to initialize. Your system may have insufficient memory or resources needed.";
|
||||
m_message_user = _("%s plugin failed to initialize. Your system may have insufficient memory or resources needed.");
|
||||
}
|
||||
|
||||
Exception::PluginLoadError::PluginLoadError( PluginsEnum_t pid )
|
||||
|
@ -695,29 +711,29 @@ Exception::PluginLoadError::PluginLoadError( PluginsEnum_t pid )
|
|||
|
||||
wxString Exception::PluginLoadError::FormatDiagnosticMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ) +
|
||||
return pxsFmt( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() ) +
|
||||
L"\n\n" + StreamName;
|
||||
}
|
||||
|
||||
wxString Exception::PluginLoadError::FormatDisplayMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ) +
|
||||
return pxsFmt( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() ) +
|
||||
L"\n\n" + StreamName;
|
||||
}
|
||||
|
||||
wxString Exception::PluginError::FormatDiagnosticMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() );
|
||||
return pxsFmt( m_message_diag, tbl_PluginInfo[PluginId].GetShortname().c_str() );
|
||||
}
|
||||
|
||||
wxString Exception::PluginError::FormatDisplayMessage() const
|
||||
{
|
||||
return wxsFormat( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() );
|
||||
return pxsFmt( m_message_user, tbl_PluginInfo[PluginId].GetShortname().c_str() );
|
||||
}
|
||||
|
||||
wxString Exception::FreezePluginFailure::FormatDiagnosticMessage() const
|
||||
{
|
||||
return wxsFormat(
|
||||
return pxsFmt(
|
||||
L"%s plugin returned an error while saving the state.\n\n",
|
||||
tbl_PluginInfo[PluginId].shortname
|
||||
);
|
||||
|
@ -731,7 +747,7 @@ wxString Exception::FreezePluginFailure::FormatDisplayMessage() const
|
|||
|
||||
wxString Exception::ThawPluginFailure::FormatDiagnosticMessage() const
|
||||
{
|
||||
return wxsFormat(
|
||||
return pxsFmt(
|
||||
L"%s plugin returned an error while loading the state.\n\n",
|
||||
tbl_PluginInfo[PluginId].shortname
|
||||
);
|
||||
|
|
|
@ -57,12 +57,15 @@ namespace Exception
|
|||
// Exception thrown when a corrupted or truncated savestate is encountered.
|
||||
class SaveStateLoadError : public BadStream
|
||||
{
|
||||
DEFINE_STREAM_EXCEPTION( SaveStateLoadError, BadStream, wxLt("The savestate appears to be corrupt or incomplete.") )
|
||||
DEFINE_STREAM_EXCEPTION( SaveStateLoadError, BadStream )
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
};
|
||||
|
||||
class PluginError : public RuntimeError
|
||||
{
|
||||
DEFINE_RUNTIME_EXCEPTION( PluginError, RuntimeError, L"Generic plugin error")
|
||||
DEFINE_RUNTIME_EXCEPTION( PluginError, RuntimeError, L"Generic plugin error!" )
|
||||
|
||||
public:
|
||||
PluginsEnum_t PluginId;
|
||||
|
|
|
@ -108,58 +108,40 @@ void RecompiledCodeReserve::OnCommittedBlock( void* block )
|
|||
}
|
||||
}
|
||||
|
||||
void RecompiledCodeReserve::ResetProcessReserves() const
|
||||
void SysOutOfMemory_EmergencyResponse(uptr blocksize)
|
||||
{
|
||||
Cpu->SetCacheReserve( (Cpu->GetCacheReserve() * 3) / 2 );
|
||||
Cpu->Reset();
|
||||
// An out of memory error occurred. All we can try to do in response is reset the various
|
||||
// recompiler caches (which can sometimes total over 120megs, so it can be quite helpful).
|
||||
// If the user is using interpreters, or if the memory allocation failure was on a very small
|
||||
// allocation, then this code could fail; but that's fine. We're already trying harder than
|
||||
// 99.995% of all programs ever written. -- air
|
||||
|
||||
CpuVU0->SetCacheReserve( (CpuVU0->GetCacheReserve() * 3) / 2 );
|
||||
CpuVU0->Reset();
|
||||
|
||||
CpuVU1->SetCacheReserve( (CpuVU1->GetCacheReserve() * 3) / 2 );
|
||||
CpuVU1->Reset();
|
||||
|
||||
psxCpu->SetCacheReserve( (psxCpu->GetCacheReserve() * 3) / 2 );
|
||||
psxCpu->Reset();
|
||||
}
|
||||
|
||||
|
||||
// Default behavior for out of memory: the
|
||||
void RecompiledCodeReserve::OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled )
|
||||
{
|
||||
// Since the recompiler is happy writing away to memory, we have to truncate the reserve
|
||||
// to include the page currently being accessed, and cannot go any smaller. This will
|
||||
// allow the rec to finish emitting the current block of instructions, detect that it has
|
||||
// exceeded the threshold buffer, and reset the buffer on its own.
|
||||
|
||||
// Note: We attempt to commit multiple pages first, since a single block of recompiled
|
||||
// code can pretty easily surpass 4k. We should have enough for this, since we just
|
||||
// cleared the other rec caches above -- but who knows what could happen if the user
|
||||
// has another process sucking up RAM or if the operating system is fickle. If even
|
||||
// that fails, give up and kill the process.
|
||||
|
||||
try
|
||||
if (Cpu)
|
||||
{
|
||||
// Truncate and reset reserves of all other in-use recompiler caches, as this should
|
||||
// help free up quite a bit of emergency memory.
|
||||
|
||||
ResetProcessReserves();
|
||||
|
||||
uint cusion = std::min<uint>( m_blocksize, 4 );
|
||||
HostSys::MmapCommitPtr((u8*)blockptr, cusion * __pagesize, m_prot_mode);
|
||||
|
||||
handled = true;
|
||||
Cpu->SetCacheReserve( (Cpu->GetCacheReserve() * 3) / 2 );
|
||||
Cpu->Reset();
|
||||
}
|
||||
catch (Exception::BaseException&)
|
||||
{
|
||||
// Fickle has become our reality. By setting handled to FALSE, the OS should kill
|
||||
// the process for us. No point trying to log anything; this is a super-awesomely
|
||||
// serious condition that likely means the system is hosed. ;)
|
||||
|
||||
handled = false;
|
||||
if (CpuVU0)
|
||||
{
|
||||
CpuVU0->SetCacheReserve( (CpuVU0->GetCacheReserve() * 3) / 2 );
|
||||
CpuVU0->Reset();
|
||||
}
|
||||
|
||||
if (CpuVU1)
|
||||
{
|
||||
CpuVU1->SetCacheReserve( (CpuVU1->GetCacheReserve() * 3) / 2 );
|
||||
CpuVU1->Reset();
|
||||
}
|
||||
|
||||
if (psxCpu)
|
||||
{
|
||||
psxCpu->SetCacheReserve( (psxCpu->GetCacheReserve() * 3) / 2 );
|
||||
psxCpu->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if _MSC_VER
|
||||
# include "svnrev.h"
|
||||
#endif
|
||||
|
|
|
@ -134,6 +134,7 @@ extern SysCpuProviderPack& GetCpuProviders();
|
|||
|
||||
extern void SysLogMachineCaps(); // Detects cpu type and fills cpuInfo structs.
|
||||
extern void SysClearExecutionCache(); // clears recompiled execution caches!
|
||||
extern void SysOutOfMemory_EmergencyResponse(uptr blocksize);
|
||||
|
||||
extern u8 *SysMmapEx(uptr base, u32 size, uptr bounds, const char *caller="Unnamed");
|
||||
extern void vSyncDebugStuff( uint frame );
|
||||
|
|
|
@ -21,9 +21,7 @@
|
|||
// RecompiledCodeReserve
|
||||
// --------------------------------------------------------------------------------------
|
||||
// A recompiled code reserve is a simple sequential-growth block of memory which is auto-
|
||||
// cleared to INT 3 (0xcc) as needed. When using this class, care should be take to re-
|
||||
// implement the provided OnOutOfMemory handler so that it clears other recompiled memory
|
||||
// reserves that are known to be attached to the process.
|
||||
// cleared to INT 3 (0xcc) as needed.
|
||||
//
|
||||
class RecompiledCodeReserve : public BaseVirtualMemoryReserve
|
||||
{
|
||||
|
@ -44,7 +42,6 @@ public:
|
|||
|
||||
virtual void* Reserve( uint size, uptr base=0, uptr upper_bounds=0 );
|
||||
virtual void OnCommittedBlock( void* block );
|
||||
virtual void OnOutOfMemory( const Exception::OutOfMemory& ex, void* blockptr, bool& handled );
|
||||
|
||||
virtual RecompiledCodeReserve& SetProfilerName( const wxString& shortname );
|
||||
virtual RecompiledCodeReserve& SetProfilerName( const char* shortname )
|
||||
|
|
|
@ -527,7 +527,9 @@ bool Pcsx2App::OnInit()
|
|||
|
||||
InitCPUTicks();
|
||||
|
||||
pxDoAssert = AppDoAssert;
|
||||
pxDoAssert = AppDoAssert;
|
||||
pxDoOutOfMemory = SysOutOfMemory_EmergencyResponse;
|
||||
|
||||
g_Conf = new AppConfig();
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
|
|
|
@ -59,7 +59,15 @@ namespace Exception
|
|||
class NotEnumerablePlugin : public BadStream
|
||||
{
|
||||
public:
|
||||
DEFINE_STREAM_EXCEPTION( NotEnumerablePlugin, BadStream, wxLt("File is not a PCSX2 plugin") );
|
||||
DEFINE_STREAM_EXCEPTION( NotEnumerablePlugin, BadStream );
|
||||
|
||||
wxString FormatDiagnosticMessage() const
|
||||
{
|
||||
FastFormatUnicode retval;
|
||||
retval.Write("File is not a PCSX2 plugin");
|
||||
_formatDiagMsg(retval);
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -62,21 +62,6 @@ static void SaveStateFile_ReadHeader( IStreamReader& thr )
|
|||
.SetUserMsg(_("Cannot load this savestate. The state is an unsupported version, likely created by a newer edition of PCSX2."));
|
||||
};
|
||||
|
||||
class gzError : public Exception::BadStream
|
||||
{
|
||||
DEFINE_STREAM_EXCEPTION( gzError, BadStream, wxLt("Invalid or corrupted gzip archive") )
|
||||
};
|
||||
|
||||
class gzReadError : public gzError
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
class gzWriteError : public gzError
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// gzipReader
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
@ -451,10 +451,6 @@
|
|||
RelativePath="..\..\NakedAsm.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\System\PageFaultSource.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\Plugins.h"
|
||||
>
|
||||
|
|
|
@ -46,7 +46,7 @@ void StreamException_ThrowLastError( const wxString& streamname, HANDLE result )
|
|||
|
||||
default:
|
||||
{
|
||||
throw Exception::Stream( streamname ).SetDiagMsg(wxsFormat( L"General Win32 File/stream error [GetLastError: %d]", error ));
|
||||
throw Exception::BadStream( streamname ).SetDiagMsg(pxsFmt( L"General Win32 File/stream error [GetLastError: %d]", error ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ bool StreamException_LogLastError( const wxString& streamname, const wxChar* act
|
|||
{
|
||||
StreamException_ThrowLastError( streamname, result );
|
||||
}
|
||||
catch( Exception::Stream& ex )
|
||||
catch( Exception::BadStream& ex )
|
||||
{
|
||||
Console.WriteLn( Color_Yellow, L"%s: %s", action, ex.FormatDiagnosticMessage().c_str() );
|
||||
return true;
|
||||
|
|
Loading…
Reference in New Issue