Browse Source

New crash reporting functionality tested and working
Debug log save to HTML
Reading strings from files now properly respects the encoding
StringUtil::format can now accept raw character arrays as a parameter

BearishSun 10 years ago
parent
commit
3fec99f712

+ 7 - 0
BansheeCore/Include/BsPlatform.h

@@ -420,6 +420,13 @@ namespace BansheeEngine
 		static bool openBrowseDialog(FileDialogType type, const Path& defaultPath, const WString& filterList,
 			Vector<Path>& paths);
 
+		/**
+		 * @brief	Terminates the current process.
+		 * 			
+		 * @param	force	True if the process should be forcefully terminated with no cleanup.
+		 */
+		static void terminate(bool force = false);
+
 		/**
 		 * @brief	Opens the provided file or folder using the default application for that file type, as specified
 		 * 			by the operating system.

+ 8 - 0
BansheeCore/Source/Win32/BsWin32BrowseDialogs.cpp

@@ -150,4 +150,12 @@ namespace BansheeEngine
 		CoUninitialize();
 		return finalResult;
 	}
+
+	void Platform::terminate(bool force)
+	{
+		if (!force)
+			PostQuitMessage(0);
+		else
+			TerminateProcess(GetCurrentProcess(), 0);
+	}
 }

+ 0 - 1
BansheeEditor/Source/BsGUIStatusBar.cpp

@@ -101,7 +101,6 @@ namespace BansheeEngine
 		UINT32 logChannel = entry.getChannel();
 		switch (logChannel)
 		{
-		case (UINT32)DebugChannel::Info:
 		case (UINT32)DebugChannel::Debug:
 			iconTexture = BuiltinEditorResources::instance().getLogMessageIcon(LogMessageIcon::Info);
 			break;

+ 2 - 2
BansheeEditorExec/BsEditorExec.cpp

@@ -3,7 +3,7 @@
 #include <fcntl.h>
 #include <io.h>
 #include "BsEditorApplication.h"
-#include "BsDebug.h"
+#include "BsPlatform.h"
 #include "BsCrashHandler.h"
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
@@ -78,7 +78,7 @@ int CALLBACK WinMain(
 	}
 	__except (gCrashHandler().reportCrash(GetExceptionInformation()))
 	{
-
+		Platform::terminate(true);
 	}
 
 #if BS_DEBUG_MODE

+ 6 - 6
BansheeUtility/BansheeUtility.vcxproj

@@ -131,7 +131,7 @@
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImportLibrary>..\lib\x86\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x86/Debug</AdditionalLibraryDirectories>
       <SubSystem>NotSet</SubSystem>
       <NoEntryPoint>false</NoEntryPoint>
@@ -152,7 +152,7 @@
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImportLibrary>..\lib\$(Platform)\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x64/Debug</AdditionalLibraryDirectories>
       <SubSystem>NotSet</SubSystem>
       <NoEntryPoint>false</NoEntryPoint>
@@ -179,7 +179,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <ImportLibrary>..\lib\x86\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x86/Release</AdditionalLibraryDirectories>
       <NoEntryPoint>false</NoEntryPoint>
     </Link>
@@ -203,7 +203,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <ImportLibrary>..\lib\x86\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x86/DebugRelease</AdditionalLibraryDirectories>
       <NoEntryPoint>false</NoEntryPoint>
     </Link>
@@ -229,7 +229,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <ImportLibrary>..\lib\$(Platform)\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x64/Release</AdditionalLibraryDirectories>
       <NoEntryPoint>false</NoEntryPoint>
     </Link>
@@ -253,7 +253,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <ImportLibrary>..\lib\$(Platform)\$(Configuration)\$(TargetName).lib</ImportLibrary>
-      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <AdditionalLibraryDirectories>../Dependencies/lib/x64/DebugRelease</AdditionalLibraryDirectories>
       <NoEntryPoint>false</NoEntryPoint>
     </Link>

+ 24 - 10
BansheeUtility/Include/BsCrashHandler.h

@@ -1,12 +1,5 @@
 #pragma once
 
-#include "BsPrerequisitesUtil.h"
-#include "BsModule.h"
-
-#if BS_PLATFORM == BS_PLATFORM_WIN32
-#include "windows.h"
-#endif
-
 #define BS_MAX_STACKTRACE_DEPTH 200
 #define BS_MAX_STACKTRACE_NAME_BYTES 1024
 
@@ -18,12 +11,27 @@ namespace BansheeEngine
 	// TODO - Crashes are reported in the same process as the main application. This can be a problem if the crash was caused
 	// by heap. Any further use of the heap by the reporting methods will cause a silent crash, failing to log it. A more appropriate
 	// way of doing it should be to resume another process to actually handle the crash.
-	class BS_UTILITY_EXPORT CrashHandler : public Module<CrashHandler>
+	class BS_UTILITY_EXPORT CrashHandler
 	{
 	public:
 		CrashHandler();
 		~CrashHandler();
 
+		/**
+		 * @brief	Constructs and starts the module.
+		 */
+		static void startUp() { _instance() = bs_new<CrashHandler>(); }
+
+		/**
+		 * @brief	Shuts down this module and frees any resources it is using.
+		 */
+		static void shutDown() { bs_delete(_instance()); }
+
+		/**
+		 * @brief	Returns a reference to the module instance.
+		 */
+		static CrashHandler& instance() { return *_instance(); }
+
 		/**
 		 * @brief	Records a crash with a custom error message.
 		 * 			
@@ -33,7 +41,8 @@ namespace BansheeEngine
 		 * @param	file		Optional name of the source code file in which the code that crashed the program exists.
 		 * @param	line		Optional source code line at which the crash was triggered at.
 		 */
-		void reportCrash(const char* type, const char* description, const char* function = nullptr, const char* file = nullptr, UINT32 line = 0);
+		void reportCrash(const String& type, const String& description, const String& function = StringUtil::BLANK,
+			const String& file = StringUtil::BLANK, UINT32 line = 0);
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 		/**
@@ -43,7 +52,7 @@ namespace BansheeEngine
 		 * 							
 		 * @returns	Code that signals the __except exception handler on how to proceed.
 		 */
-		int reportCrash(EXCEPTION_POINTERS* exceptionData);
+		int reportCrash(void* exceptionData);
 #endif
 
 		/**
@@ -59,6 +68,11 @@ namespace BansheeEngine
 		 */
 		Path getCrashFolder();
 
+		/**
+		 * @brief	Returns a singleton instance of this module. 
+		 */
+		static CrashHandler*& _instance() { static CrashHandler* inst = nullptr; return inst; }
+
 		static const wchar_t* CrashReportFolder;
 		static const wchar_t* CrashLogName;
 

+ 36 - 4
BansheeUtility/Include/BsDataStream.h

@@ -5,6 +5,15 @@
 
 namespace BansheeEngine 
 {
+	/**
+	 * @brief	Supported encoding types for strings.
+	 */
+	enum class StringEncoding
+	{
+		UTF8 = 1,
+		UTF16 = 2
+	};
+
 	/**
 	 * @brief	General purpose class used for encapsulating the reading and writing of data from
 	 *			and to various sources using a common interface.
@@ -70,11 +79,32 @@ namespace BansheeEngine
 		 */
 		virtual size_t write(const void* buf, size_t count) { return 0; }
 
+		/**
+		 * @brief	Writes the provided narrow string to the steam. String is convered to the required encoding before 
+		 * 			being written.
+		 * 			
+		 * @param	string		String containing narrow characters to write, encoded as UTF8.
+		 * @param	encoding	Encoding to convert the string to before writing.
+		 */
+		virtual void writeString(const String& string, StringEncoding encoding = StringEncoding::UTF8);
+
+		/**
+		 * @brief	Writes the provided wide string to the steam. String is convered to the required encoding before 
+		 * 			being written.
+		 * 			
+		 * @param	string		String containing wide characters to write, encoded as specified by platform for 
+		 * 						wide characters.
+		 * @param	encoding	Encoding to convert the string to before writing.
+		 */
+		virtual void writeString(const WString& string, StringEncoding encoding = StringEncoding::UTF16);
+
 	    /**
 	     * @brief	Returns a string containing the entire stream.
 	     *
-		 * @note	 This is a convenience method for text streams only, allowing you to
+		 * @note	This is a convenience method for text streams only, allowing you to
 		 *			retrieve a String object containing all the data in the stream.
+		 *			
+		 * @returns	String data encoded as UTF-8. 
 	     */
 	    virtual String getAsString();
 
@@ -83,6 +113,8 @@ namespace BansheeEngine
 	     *
 		 * @note	This is a convenience method for text streams only, allowing you to
 		 *			retrieve a WString object containing all the data in the stream.
+		 *			
+		 * @returns	Wide string encoded as specified by current platform.
 	     */
 	    virtual WString getAsWString();
 
@@ -191,17 +223,17 @@ namespace BansheeEngine
         /** 
 		 * @copydoc DataStream::tell
          */
-		size_t tell(void) const override;
+		size_t tell() const override;
 
         /** 
 		 * @copydoc DataStream::eof
          */
-		bool eof(void) const override;
+		bool eof() const override;
 
         /** 
 		 * @copydoc DataStream::close
          */
-		void close(void) override;
+		void close() override;
 
 	protected:
 		UINT8* mData;

+ 7 - 7
BansheeUtility/Include/BsDebug.h

@@ -12,7 +12,7 @@ namespace BansheeEngine
 	 */
 	enum class DebugChannel
 	{
-		Debug, Info, Warning, Error
+		Debug, Warning, Error
 	};
 
 	/**
@@ -26,11 +26,6 @@ namespace BansheeEngine
 		 */
 		void logDebug(const String& msg);
 
-		/**
-		 * @brief	Adds a log entry in the "Info" channel.
-		 */
-		void logInfo(const String& msg);
-
 		/**
 		 * @brief	Adds a log entry in the "Warning" channel.
 		 */
@@ -56,6 +51,12 @@ namespace BansheeEngine
 		 */
 		void writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath, bool overwrite = true) const;
 
+		/**
+		 * @brief	Saves a log about the current state of the application to the specified location.
+		 * 
+		 * @param	path	Absolute path to the log filename.
+		 */
+		void saveLog(const Path& path);
 	private:
 		Log mLog;
 	};
@@ -63,7 +64,6 @@ namespace BansheeEngine
 	BS_UTILITY_EXPORT Debug& gDebug();
 
 #define LOGDBG(x) BansheeEngine::gDebug().logDebug((x));
-#define LOGINFO(x) BansheeEngine::gDebug().logInfo((x));
 #define LOGWRN(x) BansheeEngine::gDebug().logWarning((x));
 #define LOGERR(x) BansheeEngine::gDebug().logError((x));
 }

+ 5 - 2
BansheeUtility/Include/BsException.h

@@ -56,7 +56,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Overriden std::exception::what. Returns the same value as "getFullDescription".
 		 */
-		const char* what() const throw() { return getFullDescription().c_str(); }
+		const char* what() const override { return getFullDescription().c_str(); }
 
 	protected:
 		long mLine;
@@ -151,12 +151,15 @@ namespace BansheeEngine
 	};
 
 	/**
-	 * @brief Macro for throwing exceptions that will automatically fill out function name, file name and line number of the exception.
+	 * @brief	Macro for throwing exceptions that will automatically fill out function name, file name and line number of the exception.
 	 */
+	// The exception thrown at the end isn't actually ever getting executed, it is just to notify the compiler that execution
+	// won't continue past this point (e.g. if a function needs to return a value otherwise).
 #ifndef BS_EXCEPT
 #define BS_EXCEPT(type, desc)	\
 	{                           \
 	static_assert((std::is_base_of<BansheeEngine::Exception, type##>::value), "Invalid exception type (" #type ") for BS_EXCEPT macro. It needs to derive from BansheeEngine::Exception."); \
+	gCrashHandler().reportCrash(#type, desc, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
 	throw type##(desc, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
 	}
 #endif

+ 2 - 5
BansheeUtility/Include/BsLog.h

@@ -47,12 +47,9 @@ namespace BansheeEngine
 		 */
 		void clear();
 
-		/**
-		 * @brief	Saves the log file to disk.
-		 */
-		void saveToFile(const Path& path);
-
 	private:
+		friend class Debug;
+
 		Vector<LogEntry*> mEntries;
 		BS_RECURSIVE_MUTEX(mMutex);
 

+ 10 - 1
BansheeUtility/Include/BsMemoryAllocator.h

@@ -198,7 +198,16 @@ namespace BansheeEngine
 	}
 
 	/**
-	 * @brief	Creates and constructs an array of "count" elements.
+	 * @brief	Allocates enough bytes to hold an array of \p count elements the specified type, but doesn't construct them.
+	 */
+	template<class T> 
+	inline T* bs_allocN(UINT32 count)
+	{
+		return (T*)MemoryAllocator<GenAlloc>::allocate(count * sizeof(T));
+	}
+
+	/**
+	 * @brief	Creates and constructs an array of \p count elements.
 	 */
 	template<class T> 
 	inline T* bs_newN(UINT32 count)

+ 1 - 2
BansheeUtility/Include/BsModule.h

@@ -144,8 +144,7 @@ namespace BansheeEngine
 		virtual void onShutDown() {}
 
 		/**
-		 * @brief	Returns a singleton instance of this module. Throws an exception
-		 *			if module is not yet initialized.
+		 * @brief	Returns a singleton instance of this module. 
 		 */
 		static T*& _instance()
 		{

+ 2 - 1
BansheeUtility/Include/BsPrerequisitesUtil.h

@@ -65,4 +65,5 @@
 #include "BsUtil.h"
 #include "BsPath.h"
 #include "BsStringID.h"
-#include "BsEvent.h"
+#include "BsEvent.h"
+#include "BsCrashHandler.h"

+ 32 - 2
BansheeUtility/Include/BsStringFormat.h

@@ -190,7 +190,7 @@ namespace BansheeEngine
 		template<class T> static std::string toString(const T& param) { return std::to_string(param); }
 
 		/**
-		 * @brief	Helper method that "converts" a narrow string to a narrow string (simply a pass through)
+		 * @brief	Helper method that "converts" a narrow string to a narrow string (simply a pass through).
 		 */
 		template<> static std::string toString<std::string>(const std::string& param) { return param; }
 
@@ -202,13 +202,28 @@ namespace BansheeEngine
 			return std::string(param.c_str());
 		}
 
+		/**
+		 * @brief	Helper method that converts a narrow character array to a narrow string.
+		 */
+		template<class T> static std::string toString(T* param) { static_assert("Invalid pointer type."); }
+
+		/**
+		 * @brief	Helper method that converts a narrow character array to a narrow string.
+		 */
+		template<> static std::string toString<const char>(const char* param) { return std::string(param); }
+
+		/**
+		 * @brief	Helper method that converts a narrow character array to a narrow string.
+		 */
+		template<> static std::string toString<char>(char* param) { return std::string(param); }
+
 		/**
 		 * @brief	Helper method for converting any data type to a wide string.
 		 */
 		template<class T> static std::wstring toWString(const T& param) { return std::to_wstring(param); }
 
 		/**
-		 * @brief	Helper method that "converts" a wide string to a wide string (simply a pass through)
+		 * @brief	Helper method that "converts" a wide string to a wide string (simply a pass through).
 		 */
 		template<> static std::wstring toWString<std::wstring>(const std::wstring& param) { return param; }
 
@@ -220,6 +235,21 @@ namespace BansheeEngine
 			return std::wstring(param.c_str());
 		}
 
+		/**
+		 * @brief	Helper method that converts a wide character array to a wide string.
+		 */
+		template<class T> static std::wstring toWString(T* param) { static_assert("Invalid pointer type."); }
+
+		/**
+		 * @brief	Helper method that converts a wide character array to a wide string.
+		 */
+		template<> static std::wstring toWString<const wchar_t>(const wchar_t* param) { return std::wstring(param); }
+
+		/**
+		* @brief	Helper method that converts a wide character array to a wide string.
+		*/
+		template<> static std::wstring toWString<wchar_t>(wchar_t* param) { return std::wstring(param); }
+
 		/**
 		 * @brief	Converts all the provided parameters into string representations and populates the provided
 		 *			"parameter" array.

+ 46 - 9
BansheeUtility/Source/BsDataStream.cpp

@@ -58,6 +58,45 @@ namespace BansheeEngine
         return *this;
     }
 
+	void DataStream::writeString(const String& string, StringEncoding encoding)
+	{
+		if (encoding == StringEncoding::UTF16)
+		{
+			const std::codecvt_mode convMode = (std::codecvt_mode)(std::generate_header);
+			typedef std::codecvt_utf8_utf16<char, 1114111, convMode> UTF8ToUTF16Conv;
+			std::wstring_convert<UTF8ToUTF16Conv, char> conversion("?");
+
+			std::string encodedString = conversion.from_bytes(string.c_str());
+			write(encodedString.data(), encodedString.length());
+		}
+		else
+		{
+			write(string.data(), string.length());
+		}
+	}
+
+	void DataStream::writeString(const WString& string, StringEncoding encoding)
+	{
+		if (encoding == StringEncoding::UTF16)
+		{
+			const std::codecvt_mode convMode = (std::codecvt_mode)(std::generate_header | std::little_endian);
+			typedef std::codecvt_utf16<wchar_t, 1114111, convMode> WCharToUTF16Conv;
+			std::wstring_convert<WCharToUTF16Conv, wchar_t> conversion("?");
+
+			std::string encodedString = conversion.to_bytes(string.c_str());
+			write(encodedString.data(), encodedString.length());
+		}
+		else
+		{
+			const std::codecvt_mode convMode = (std::codecvt_mode)(std::generate_header);
+			typedef std::codecvt_utf8<wchar_t, 1114111, convMode> WCharToUTF8Conv;
+			std::wstring_convert<WCharToUTF8Conv, wchar_t> conversion("?");
+
+			std::string encodedString = conversion.to_bytes(string.c_str());
+			write(encodedString.data(), encodedString.length());
+		}
+	}
+
 	String DataStream::getAsString()
 	{
 		// Read the entire buffer - ideally in one read, but if the size of
@@ -118,18 +157,17 @@ namespace BansheeEngine
 			if (isUTF16LE((UINT8*)string.data()))
 			{
 				const std::codecvt_mode convMode = (std::codecvt_mode)(std::little_endian);
-				typedef std::codecvt_utf8<char16_t, 1114111, convMode> utf8utf16;
+				typedef std::codecvt_utf8_utf16<char16_t, 1114111, convMode> utf8utf16;
 
 				std::wstring_convert<utf8utf16, char16_t> conversion("?");
-				char16_t* start = (char16_t*)(string.data() + 2); // Bug?: std::consume_header seems to be ignored so I manually remove the header
-				char16_t* end = (start + (string.size() - 1) / 2);
+				char16_t* start = (char16_t*)(string.c_str() + 2); // Bug?: std::consume_header seems to be ignored so I manually remove the header
 
-				return conversion.to_bytes(start, end).c_str();
+				return conversion.to_bytes(start).c_str();
 			}
 			else if (isUTF16BE((UINT8*)string.data()))
 			{
 				const std::codecvt_mode convMode = (std::codecvt_mode)(0);
-				typedef std::codecvt_utf8<char16_t, 1114111, convMode> utf8utf16;
+				typedef std::codecvt_utf8_utf16<char16_t, 1114111, convMode> utf8utf16;
 
 				// Bug?: Regardless of not providing the std::little_endian flag it seems that is how the data is read
 				// so I manually flip it
@@ -138,10 +176,9 @@ namespace BansheeEngine
 					std::swap(string[i * 2 + 0], string[i * 2 + 1]);
 
 				std::wstring_convert<utf8utf16, char16_t> conversion("?");
-				char16_t* start = (char16_t*)(string.data() + 2); // Bug?: std::consume_header seems to be ignored so I manually remove the header
-				char16_t* end = (start + (string.size() - 1) / 2);
+				char16_t* start = (char16_t*)(string.c_str() + 2); // Bug?: std::consume_header seems to be ignored so I manually remove the header
 
-				return conversion.to_bytes(start, end).c_str();
+				return conversion.to_bytes(start).c_str();
 			}
 		}
 
@@ -198,7 +235,7 @@ namespace BansheeEngine
 			if (isUTF16LE((UINT8*)string.data()))
 			{
 				const std::codecvt_mode convMode = (std::codecvt_mode)(std::consume_header | std::little_endian);
-				typedef std::codecvt_utf16 <wchar_t, 1114111, convMode> wcharutf16;
+				typedef std::codecvt_utf16<wchar_t, 1114111, convMode> wcharutf16;
 
 				std::wstring_convert<wcharutf16> conversion("?");
 				return conversion.from_bytes(string).c_str();

+ 167 - 6
BansheeUtility/Source/BsDebug.cpp

@@ -32,12 +32,6 @@ namespace BansheeEngine
 		logToIDEConsole(msg);
 	}
 
-	void Debug::logInfo(const String& msg)
-	{
-		mLog.logMsg(msg, (UINT32)DebugChannel::Info);
-		logToIDEConsole(msg);
-	}
-
 	void Debug::logWarning(const String& msg)
 	{
 		mLog.logMsg(msg, (UINT32)DebugChannel::Warning);
@@ -79,6 +73,173 @@ namespace BansheeEngine
 		bs_deleteN(bmpBuffer, bmpDataSize);
 	}
 
+	void Debug::saveLog(const Path& path)
+	{
+		static const char* style =
+			R"(html {
+  font-family: sans-serif;
+} 
+            
+table
+{
+    border-collapse: collapse;
+    border-spacing: 0;
+    empty-cells: show;
+    border: 1px solid #cbcbcb;
+  	width:100%;
+  	table-layout:fixed;
+}
+
+table caption 
+{
+    color: #000;
+    font: italic 85%/1 arial, sans-serif;
+    padding: 1em 0;
+    text-align: center;
+}
+
+table td,
+table th 
+{
+    border-left: 1px solid #cbcbcb;/*  inner column border */
+    border-width: 0 0 0 1px;
+    font-size: inherit;
+    margin: 0;
+    overflow: visible; /*to make ths where the title is really long work*/
+    padding: 0.5em 1em; /* cell padding */
+}
+
+table td:first-child,
+table th:first-child 
+{
+    border-left-width: 0;
+}
+
+table thead 
+{
+    background-color: #e0e0e0;
+    color: #000;
+    text-align: left;
+    vertical-align: bottom;
+}
+
+table td 
+{
+    background-color: transparent;
+  	word-wrap:break-word;
+  	vertical-align: top;
+  	color: #7D7D7D;
+}
+
+.debug-row td {
+    background-color: #FFFFFF;
+}
+
+.debug-alt-row td {
+    background-color: #f2f2f2;
+}
+
+.warn-row td {
+    background-color: #ffc016;
+    color: #5F5F5F;
+}
+
+.warn-alt-row td {
+    background-color: #fdcb41;
+    color: #5F5F5F;
+}
+
+.error-row td {
+    background-color: #9f1621;
+    color: #9F9F9F;
+}
+
+.error-alt-row td {
+    background-color: #ae1621;
+    color: #9F9F9F;
+}
+)";
+
+		static const char* htmlPreStyleHeader =
+			R"(<!DOCTYPE html>
+<html lang="en">
+<head>
+<style type="text/css">
+)";
+
+		static const char* htmlPostStyleHeader =
+			R"(</style>
+<title>Banshee Engine Log</title>
+</head>
+<body>
+<h1>Banshee Engine Log</h1>
+<table border="1" cellpadding="1" cellspacing="1">
+	<thead>
+		<tr>
+			<th scope="col" style="width:60px">Type</th>
+			<th scope="col">Description</th>
+		</tr>
+	</thead>
+	<tbody>
+)";
+
+		static const char* htmlFooter =
+			R"(   </tbody>
+</table>
+</body>
+</html>)";
+
+		StringStream stream;
+		stream << htmlPreStyleHeader;
+		stream << style;
+		stream << htmlPostStyleHeader;
+
+		bool alternate = false;
+		for (auto& entry : mLog.mEntries)
+		{
+			String channelName;
+			if (entry->getChannel() == (UINT32)DebugChannel::Error)
+			{
+				if (!alternate)
+					stream << R"(		<tr class="error-row">)" << std::endl;
+				else
+					stream << R"(		<tr class="error-alt-row">)" << std::endl;
+
+				stream << R"(			<td>Error</td>)" << std::endl;
+			}
+			else if (entry->getChannel() == (UINT32)DebugChannel::Warning)
+			{
+				if (!alternate)
+					stream << R"(		<tr class="warn-row">)" << std::endl;
+				else
+					stream << R"(		<tr class="warn-alt-row">)" << std::endl;
+
+				stream << R"(			<td>Warning</td>)" << std::endl;
+			}
+			else
+			{
+				if (!alternate)
+					stream << R"(		<tr class="debug-row">)" << std::endl;
+				else
+					stream << R"(		<tr class="debug-alt-row">)" << std::endl;
+
+				stream << R"(			<td>Debug</td>)" << std::endl;
+			}
+
+			String parsedMessage = StringUtil::replaceAll(entry->getMessage(), "\n", "<br>\n");
+
+			stream << R"(			<td>)" << parsedMessage << "</td>" << std::endl;
+			stream << R"(		</tr>)" << std::endl;
+
+			alternate = !alternate;
+		}
+
+		stream << htmlFooter;
+
+		DataStreamPtr fileStream = FileSystem::createAndOpenFile(path);
+		fileStream->writeString(stream.str());
+	}
+
 	BS_UTILITY_EXPORT Debug& gDebug()
 	{
 		static Debug debug;

+ 0 - 6
BansheeUtility/Source/BsLog.cpp

@@ -39,12 +39,6 @@ namespace BansheeEngine
 		mEntries.clear();
 	}
 
-	void Log::saveToFile(const Path& path)
-	{
-		// TODO - Save the log as HTML
-		BS_EXCEPT(NotImplementedException, "Log save to file not yet implemented.");
-	}
-
 	void Log::doOnEntryAdded(const LogEntry& entry)
 	{
 		onEntryAdded(entry);

+ 38 - 19
BansheeUtility/Source/Win32/BsWin32CrashHandler.cpp

@@ -1,7 +1,8 @@
-#include "BsCrashHandler.h"
+#include "BsPrerequisitesUtil.h"
 #include "BsDebug.h"
 #include "BsDynLib.h"
 #include "BsFileSystem.h"
+#include "windows.h"
 #include "DbgHelp.h"
 #include <psapi.h>
 
@@ -50,7 +51,6 @@ namespace BansheeEngine
 			if (!StackWalk64(machineType, hProcess, hThread, &stackFrame, &context, nullptr,
 				SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
 			{
-				LOGERR("Stack walk failed.");
 				break;
 			}
 
@@ -75,7 +75,7 @@ namespace BansheeEngine
 	 * 						
 	 * @returns	String containing the call stack with each function on its own line.
 	 */
-	String win32_getStackTrace(CONTEXT context, int skip = 0)
+	String win32_getStackTrace(CONTEXT context, UINT32 skip = 0)
 	{
 		UINT64 rawStackTrace[BS_MAX_STACKTRACE_DEPTH];
 		UINT32 numEntries = win32_getRawStackTrace(context, rawStackTrace);
@@ -113,8 +113,10 @@ namespace BansheeEngine
 			DWORD column;
 			if (SymGetLineFromAddr64(hProcess, funcAddress, &column, &lineData))
 			{
+				Path filePath = lineData.FileName;
+
 				outputStream << StringUtil::format("0x{0} File[{1}:{2} ({3})]", addressString, 
-					lineData.FileName, lineData.LineNumber, column);
+					filePath.getFilename(), lineData.LineNumber, column);
 			}
 			else
 			{
@@ -126,7 +128,11 @@ namespace BansheeEngine
 			moduleData.SizeOfStruct = sizeof(moduleData);
 
 			if (SymGetModuleInfo64(hProcess, funcAddress, &moduleData))
-				outputStream << StringUtil::format(" Module[{0}]", moduleData.ImageName);
+			{
+				Path filePath = moduleData.ImageName;
+
+				outputStream << StringUtil::format(" Module[{0}]", filePath.getFilename());
+			}
 		}
 
 		bs_free(buffer);
@@ -396,8 +402,8 @@ namespace BansheeEngine
 		bs_delete(m);
 	}
 
-	void CrashHandler::reportCrash(const char* type, const char* description, const char* function,
-		const char* file, UINT32 line)
+	void CrashHandler::reportCrash(const String& type, const String& description, const String& function,
+		const String& file, UINT32 line)
 	{
 		// Win32 debug methods are not thread safe
 		Lock<>(m->mutex);
@@ -405,7 +411,7 @@ namespace BansheeEngine
 		String stackTrace = getStackTrace();
 
 		StringStream errorMessageStream;
-		errorMessageStream << "Fatal error occurred in Banshee Engine and the program has to terminate!" << std::endl;
+		errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
 		errorMessageStream << "\t\t" << type << " - " << description << std::endl;
 		errorMessageStream << "\t\t in " << function << " [" << file << ":" << line << "]" << std::endl;
 		errorMessageStream << std::endl;
@@ -418,16 +424,21 @@ namespace BansheeEngine
 		Path crashFolder = getCrashFolder();
 		FileSystem::createDir(crashFolder);
 
-		gDebug().getLog().saveToFile(crashFolder + toWString(CrashLogName));
-		win32_writeMiniDump(crashFolder + toWString(gMiniDumpName), nullptr);
+		gDebug().saveLog(crashFolder + WString(CrashLogName));
+		win32_writeMiniDump(crashFolder + WString(gMiniDumpName), nullptr);
+
+		WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
+			L"\n\nFor more information check the crash report located at:\n " + crashFolder.toWString();
 
-		MessageBoxA(nullptr, errorMessage.c_str(), "Banshee fatal error!", MB_OK);
+		MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
 
 		// Note: Potentially also log Windows Error Report and/or send crash data to server
 	}
 
-	int CrashHandler::reportCrash(EXCEPTION_POINTERS* exceptionData)
+	int CrashHandler::reportCrash(void* exceptionDataPtr)
 	{
+		EXCEPTION_POINTERS* exceptionData = (EXCEPTION_POINTERS*)exceptionDataPtr;
+
 		// Win32 debug methods are not thread safe
 		Lock<>(m->mutex);
 
@@ -436,8 +447,8 @@ namespace BansheeEngine
 		String stackTrace = win32_getStackTrace(*exceptionData->ContextRecord, 0);
 
 		StringStream errorMessageStream;
-		errorMessageStream << "Fatal error occurred in Banshee Engine and the program has to terminate!" << std::endl;
-		errorMessageStream << "\t\t" << win32_getExceptionMessage(exceptionData->ExceptionRecord);
+		errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
+		errorMessageStream << "\t\t" << win32_getExceptionMessage(exceptionData->ExceptionRecord) << std::endl;;
 		errorMessageStream << std::endl;
 		errorMessageStream << "Stack trace: " << std::endl;
 		errorMessageStream << stackTrace;
@@ -448,10 +459,13 @@ namespace BansheeEngine
 		Path crashFolder = getCrashFolder();
 		FileSystem::createDir(crashFolder);
 
-		gDebug().getLog().saveToFile(crashFolder + toWString(CrashLogName));
-		win32_writeMiniDump(crashFolder + toWString(gMiniDumpName), exceptionData);
+		gDebug().saveLog(crashFolder + WString(CrashLogName));
+		win32_writeMiniDump(crashFolder + WString(gMiniDumpName), exceptionData);
+
+		WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
+			L"\n\nFor more information check the crash report located at:\n" + crashFolder.toWString();
 
-		MessageBoxA(nullptr, errorMessage.c_str(), "Banshee fatal error!", MB_OK);
+		MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
 
 		// Note: Potentially also log Windows Error Report and/or send crash data to server
 
@@ -463,7 +477,7 @@ namespace BansheeEngine
 		SYSTEMTIME systemTime;
 		GetLocalTime(&systemTime);
 
-		WString timeStamp = L"{0}-{1}-{2}_{3}:{4}";
+		WString timeStamp = L"{0}-{1}-{2}_{3}-{4}";
 		timeStamp = StringUtil::format(timeStamp, systemTime.wYear, systemTime.wMonth, systemTime.wDay, 
 			systemTime.wHour, systemTime.wMinute);
 
@@ -479,6 +493,11 @@ namespace BansheeEngine
 
 		win32_initPSAPI();
 		win32_loadSymbols();
-		return win32_getStackTrace(context, 0);
+		return win32_getStackTrace(context, 2);
+	}
+
+	CrashHandler& gCrashHandler()
+	{
+		return CrashHandler::instance();
 	}
 }

+ 1 - 1
SBansheeEngine/Source/BsScriptDebug.cpp

@@ -19,7 +19,7 @@ namespace BansheeEngine
 
 	void ScriptDebug::internal_log(MonoString* message)
 	{
-		gDebug().logInfo(MonoUtil::monoToString(message));
+		gDebug().logDebug(MonoUtil::monoToString(message));
 	}
 
 	void ScriptDebug::internal_logWarning(MonoString* message)