Commits

Ryan Stecker committed da51e0c

Initial commit.

  • Participants

Comments (0)

Files changed (364)

Callback Logger/Callback Logger.vcproj

+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="9.00"
+	Name="Callback Logger"
+	ProjectGUID="{13A7E5A8-9F50-478A-B17B-34EC63721CFB}"
+	RootNamespace="CallbackLogger"
+	TargetFrameworkVersion="196613"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				AdditionalOptions="/MP8"
+				Optimization="0"
+				AdditionalIncludeDirectories="..\Open Steamworks\"
+				MinimalRebuild="true"
+				BasicRuntimeChecks="3"
+				RuntimeLibrary="3"
+				WarningLevel="3"
+				DebugInformationFormat="4"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				GenerateDebugInformation="true"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+			IntermediateDirectory="$(ConfigurationName)"
+			ConfigurationType="1"
+			CharacterSet="2"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				AdditionalOptions="/MP8"
+				Optimization="2"
+				EnableIntrinsicFunctions="true"
+				AdditionalIncludeDirectories="..\Open Steamworks\"
+				RuntimeLibrary="2"
+				EnableFunctionLevelLinking="true"
+				WarningLevel="3"
+				DebugInformationFormat="3"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				GenerateDebugInformation="true"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				TargetMachine="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Source Files"
+			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+			>
+			<File
+				RelativePath=".\main.cpp"
+				>
+			</File>
+		</Filter>
+	</Files>
+	<Globals>
+	</Globals>
+</VisualStudioProject>

Callback Logger/main.cpp

+#include "SteamclientAPI.h"
+#include "SteamAPI.h"
+
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+
+#pragma comment(lib, "..\\steamclient")
+
+ISteamClient008 *steamClient = NULL;
+
+HSteamPipe pipe = NULL;
+HSteamUser user = NULL;
+
+SYSTEMTIME time;
+
+
+int main()
+{
+	using namespace std;
+
+	Sys_LoadInterface( "steamclient", STEAMCLIENT_INTERFACE_VERSION_008, NULL, (void**)&steamClient );
+	if ( !steamClient )
+	{
+		cout << "Error: Unable to load steamclient.dll" << endl;
+		return EXIT_FAILURE;
+	}
+
+	pipe = steamClient->CreateSteamPipe();
+	while (!pipe) // keep trying to get the pipe
+	{
+		pipe = steamClient->CreateSteamPipe();
+		cout << ".";
+
+		Sleep(100);
+	}
+
+	user = steamClient->ConnectToGlobalUser( pipe );
+	while (!user)
+	{
+		user = steamClient->ConnectToGlobalUser( pipe);
+		cout << ".";
+
+		Sleep(100);
+	}
+
+	if ( !( pipe && user ) )
+	{
+		cout << "Unable to create steam pipe or user, please ensure steam is running and you are logged in." << endl;
+		return EXIT_FAILURE;
+	}
+
+	CallbackMsg_t callbackMsg;
+	HSteamCall steamCall;
+
+	GetLocalTime(&time);
+
+	char fileName[] = "log.txt";
+	fstream logFile( fileName, fstream::out | fstream::in | fstream::app );
+
+	while ( true )
+	{
+		if ( Steam_BGetCallback( pipe, &callbackMsg, &steamCall ) )
+		{
+			int32 callBack = callbackMsg.m_iCallback;
+			ECallbackType type = ( ECallbackType )( ( callBack / 100 ) * 100 );
+
+			cout << "Callback: " << callBack << ", Type: " << EnumString<ECallbackType>::From( type ) << ", Size: " << callbackMsg.m_cubParam << endl << "  ";
+			logFile << "Callback: " << callBack << ", Type: " << EnumString<ECallbackType>::From( type ) << ", Size: " << callbackMsg.m_cubParam << endl << "  ";
+
+			for ( int i = 0; i < callbackMsg.m_cubParam; i++ )
+			{
+				unsigned char value = callbackMsg.m_pubParam[ i ];
+			
+				cout << hex << setw( 2 ) << setfill( '0' ) << uppercase << (unsigned int)value;
+				cout << " ";
+
+				logFile << hex << setw( 2 ) << setfill( '0' ) << uppercase << (unsigned int)value;
+				logFile << " ";
+			}
+
+			// log to file
+
+			cout << resetiosflags( ios_base::hex | ios_base::uppercase ) << endl;
+			logFile << resetiosflags( ios_base::hex | ios_base::uppercase ) << endl;
+
+			cout << endl;
+			logFile << endl;
+
+			logFile.flush();
+
+			Steam_FreeLastCallback( pipe );
+		}
+
+		Sleep(10);
+	}
+
+	return EXIT_SUCCESS;
+}

Chat Log/Chat Log.csproj

+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.30729</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{AD738C92-B479-45C4-96B7-AF778AD4B749}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ChatLog</RootNamespace>
+    <AssemblyName>Chat Log</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ApplicationIcon>Resources/logger.ico</ApplicationIcon>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Events\LogFailureEventArgs.cs" />
+    <Compile Include="LogManager.cs" />
+    <Compile Include="Data\LogMessage.cs" />
+    <Compile Include="UI\BrowseTextBox.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="UI\BrowseTextBox.Designer.cs">
+      <DependentUpon>BrowseTextBox.cs</DependentUpon>
+    </Compile>
+    <Compile Include="UI\PreviewTextBox.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="UI\PreviewTextBox.Designer.cs">
+      <DependentUpon>PreviewTextBox.cs</DependentUpon>
+    </Compile>
+    <Compile Include="UI\Notification.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="UI\BrowseTextBox.resx">
+      <DependentUpon>BrowseTextBox.cs</DependentUpon>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <EmbeddedResource Include="UI\Notification.resx">
+      <DependentUpon>Notification.cs</DependentUpon>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <EmbeddedResource Include="UI\PreviewTextBox.resx">
+      <DependentUpon>PreviewTextBox.cs</DependentUpon>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <EmbeddedResource Include="UI\SettingsForm.resx">
+      <DependentUpon>SettingsForm.cs</DependentUpon>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
+    </Compile>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <Compile Include="Utils\Serializable.cs" />
+    <Compile Include="Data\Settings.cs" />
+    <Compile Include="UI\SettingsForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="UI\SettingsForm.Designer.cs">
+      <DependentUpon>SettingsForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Utils\Util.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\dotnetworks\dotnetworks.vcproj">
+      <Project>{2B6BB86F-539E-4A08-A760-4328D3F76F64}</Project>
+      <Name>dotnetworks</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Resources\logger.ico" />
+    <None Include="license.txt">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="readme.txt">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

Chat Log/Data/LogMessage.cs

+
+namespace ChatLog
+{
+    using System;
+    using dotnetworks;
+
+    class LogMessage
+    {
+        public SteamID Sender;
+        public string SenderName;
+
+        public SteamID Reciever;
+        public string RecieverName;
+
+        public string Message;
+
+        public DateTime MessageTime;
+
+        public FriendMsgType MessageType;
+    }
+}

Chat Log/Data/Settings.cs

+
+namespace ChatLog
+{
+    using System;
+
+    public class Settings : Serializable<Settings>
+    {
+        public const string BackingFile = "settings.xml";
+
+        public string LogDirectory;
+        public string Filename;
+
+        public string LogFormat;
+        public string EmoteFormat;
+
+        public string InvalidReplacement;
+
+        public string DateFormat;
+        public string TimeFormat;
+
+        public Settings()
+        {
+            Filename = "{Name}.txt";
+
+            LogFormat = "[{Time}] {Name}: {Message}";
+            EmoteFormat = "[{Time}] * {Name} {Message}";
+
+            InvalidReplacement = "_";
+
+            DateFormat = "d";
+            TimeFormat = "t";
+        }
+
+
+        public bool Save()
+        {
+            return base.Save( BackingFile );
+        }
+    }
+}

Chat Log/Events/LogFailureEventArgs.cs

+
+namespace ChatLog
+{
+    using System;
+
+    class LogFailureEventArgs : EventArgs
+    {
+        public string Reason { get; private set; }
+
+        public LogFailureEventArgs( string reason )
+        {
+            this.Reason = reason;
+        }
+    }
+}

Chat Log/LogManager.cs

+namespace ChatLog
+{
+    using System;
+    using System.Globalization;
+    using System.IO;
+    using dotnetworks;
+
+    class LogManager
+    {
+        Settings sets;
+
+        SteamClient008 steamClient;
+        SteamFriends001 steamFriends;
+
+        SteamPipeHandle pipe;
+        SteamUserHandle user;
+
+        public event EventHandler<LogFailureEventArgs> LogFailure;
+
+        public bool Initialized { get; set; }
+
+        public LogManager( Settings settings )
+        {
+            sets = settings;
+
+            Initialized = true;
+
+            try
+            {
+                InitSteamworks();
+            }
+            catch ( Exception ex )
+            {
+                Util.ShowFatalError( null, "Unable to initialize steamworks: " + ex.Message + "\n\nPlease ensure that steamclient.dll, vstdlib.dll, and tier0_s.dll are present in the program's directory." );
+                Initialized = false;
+                return;
+            }
+        }
+
+        protected virtual void OnLogFailure( LogFailureEventArgs e )
+        {
+            if ( LogFailure != null )
+                LogFailure( this, e );
+        }
+
+        private void InitSteamworks()
+        {
+            int error;
+            steamClient = ( SteamClient008 )Steamworks.CreateInterface( SteamClient008.InterfaceVersion, out error );
+
+            if ( error > 0 || steamClient == null )
+            {
+                Util.ShowFatalError( null, "Unable get SteamClient interface." );
+                Initialized = false;
+                return;
+            }
+
+            pipe = steamClient.CreateSteamPipe();
+            if ( pipe == SteamPipeHandle.InvalidHandle )
+            {
+                Util.ShowFatalError( null, "Unable to create steam pipe.\n\nPlease ensure steam is running." );
+                Initialized = false;
+                return;
+            }
+
+            user = steamClient.ConnectToGlobalUser( pipe );
+            if ( user == new SteamUserHandle( 0 ) )
+            {
+                Util.ShowFatalError( null, "Unable to connect to global user.\n\nPlease ensure you are logged into steam." );
+                Initialized = false;
+                return;
+            }
+
+            steamFriends = ( SteamFriends001 )steamClient.GetISteamFriends( user, pipe, SteamFriends001.InterfaceVersion );
+            if ( steamFriends == null )
+            {
+                Util.ShowFatalError( null, "Unable to get SteamFriends interface." );
+                Initialized = false;
+                return;
+            }
+        }
+
+        private void AddLog( LogMessage log )
+        {
+            string directoryName = sets.LogDirectory;
+
+            if ( string.IsNullOrEmpty( directoryName ) )
+            {
+                OnLogFailure( new LogFailureEventArgs( "Log directory not configured" ) );
+                return;
+            }
+
+            directoryName = directoryName.FormatWith(
+                new
+                {
+                    SteamID = log.Reciever.Render().Replace( ":", "_" ),
+                    Name = Util.StripInvalidChars( log.RecieverName, sets.InvalidReplacement ),
+                    Date = Util.StripInvalidChars( DateTime.Now.ToString( "d", CultureInfo.CurrentCulture ), sets.InvalidReplacement ),
+                    Time = Util.StripInvalidChars( DateTime.Now.ToString( "t", CultureInfo.CurrentCulture ), sets.InvalidReplacement ),
+                }
+            );
+
+            if ( !Directory.Exists( directoryName ) )
+            {
+                try
+                {
+                    Directory.CreateDirectory( directoryName );
+                }
+                catch
+                {
+                    OnLogFailure( new LogFailureEventArgs( "Log directory not properly configured" ) );
+                    return;
+                }
+            }
+
+
+            string fileName = sets.Filename;
+
+            if ( string.IsNullOrEmpty( fileName ) )
+            {
+                OnLogFailure( new LogFailureEventArgs( "Log filename not configured" ) );
+                return;
+            }
+
+            fileName = fileName.FormatWith(
+                new
+                {
+                    SteamID = log.Reciever.Render().Replace( ":", "_" ),
+                    Name = Util.StripInvalidChars( log.RecieverName, sets.InvalidReplacement ),
+                    Date = Util.StripInvalidChars( DateTime.Now.ToString( "d", CultureInfo.CurrentCulture ), sets.InvalidReplacement ),
+                    Time = Util.StripInvalidChars( DateTime.Now.ToString( "t", CultureInfo.CurrentCulture ), sets.InvalidReplacement ),
+                }
+            );
+
+            string logMessage = string.Empty;
+
+            string dateStr = string.Empty;
+            try
+            {
+                dateStr = DateTime.Now.ToString( sets.DateFormat, CultureInfo.CurrentCulture );
+            }
+            catch
+            {
+                dateStr = "Bad Date Format";
+            }
+
+            string timeStr = string.Empty;
+            try
+            {
+                timeStr = DateTime.Now.ToString( sets.TimeFormat, CultureInfo.CurrentCulture );
+            }
+            catch
+            {
+                timeStr = "Bad Time Format";
+            }
+
+            var ReplaceTable = new
+            {
+                Name = log.SenderName,
+                SteamID = log.Sender.Render(),
+
+                Message = log.Message,
+
+                MyName = log.RecieverName,
+                MySteamID = log.Reciever.Render(),
+
+                Date = dateStr,
+                Time = timeStr,
+                UnixTime = ( DateTime.UtcNow - new DateTime( 1970, 1, 1, 0, 0, 0 ) ).TotalSeconds,
+
+                NewLine = Environment.NewLine,
+                Tab = "\t",
+
+            };
+
+            if ( log.MessageType == FriendMsgType.Chat )
+            {
+                logMessage = sets.LogFormat;
+
+                if ( string.IsNullOrEmpty( logMessage ) )
+                {
+                    OnLogFailure( new LogFailureEventArgs( "Log format not configured" ) );
+                    return;
+                }
+
+                try
+                {
+                    logMessage = logMessage.FormatWith( ReplaceTable );
+                }
+                catch
+                {
+                    OnLogFailure( new LogFailureEventArgs( "Log format was invalid" ) );
+                    return;
+                }
+            }
+            else if ( log.MessageType == FriendMsgType.ChatSent ) // these are emotes for the newer interface versions
+            {
+                logMessage = sets.EmoteFormat;
+
+                if ( string.IsNullOrEmpty( logMessage ) )
+                {
+                    OnLogFailure( new LogFailureEventArgs( "Log emote format not configured" ) );
+                    return;
+                }
+
+                try
+                {
+                    logMessage = logMessage.FormatWith( ReplaceTable );
+                }
+                catch
+                {
+                    OnLogFailure( new LogFailureEventArgs( "Log emote format was invalid" ) );
+                    return;
+                }
+            }
+
+            if ( string.IsNullOrEmpty( logMessage ) )
+                return;
+
+            try
+            {
+                File.AppendAllText( Path.Combine( directoryName, fileName ), logMessage + Environment.NewLine );
+            }
+            catch (Exception ex)
+            {
+                OnLogFailure( new LogFailureEventArgs( ex.Message ) );
+                return;
+            }
+        }
+
+        public void Update()
+        {
+            Callback callback;
+            SteamCallHandle steamCall;
+
+            if ( Steamworks.Steam_BGetCallback( pipe, out callback, out steamCall ) )
+            {
+                if ( callback.CallbackNum == FriendChatMsg.Callback )
+                {
+                    FriendChatMsg chatMsg = null;
+
+                    try
+                    {
+                        chatMsg = ( FriendChatMsg )callback.CallbackObject;
+                    }
+                    catch
+                    {
+                        Steamworks.Steam_FreeLastCallback( pipe );
+                        OnLogFailure( new LogFailureEventArgs( "Recieved callback was not in the correct format, call a programmer!" ) );
+                        return;
+                    }
+
+                    string message = string.Empty;
+                    FriendMsgType type;
+
+                    SteamID reciever = new SteamID( chatMsg.Reciever );
+
+                    steamFriends.GetChatMessage( reciever, ( int )chatMsg.ChatID, out message, 1024 * 4, out type );
+
+                    LogMessage log = new LogMessage();
+
+                    log.Sender = new SteamID( chatMsg.Sender );
+                    log.SenderName = steamFriends.GetFriendPersonaName( log.Sender );
+
+                    log.Reciever = reciever;
+                    log.RecieverName = steamFriends.GetFriendPersonaName( log.Reciever );
+
+                    log.Message = message;
+                    log.MessageTime = DateTime.Now;
+                    log.MessageType = type;
+
+                    AddLog( log );
+                }
+
+                Steamworks.Steam_FreeLastCallback( pipe );
+            }
+        }
+
+    }
+}

Chat Log/Program.cs

+/*
+Copyright (c) 2009 Ryan Stecker
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+namespace ChatLog
+{
+    using System;
+    using System.IO;
+    using System.Threading;
+    using System.Windows.Forms;
+
+    static class Program
+    {
+        static Settings sets;
+
+        static Notification notifyIcon;
+        static LogManager logManager;
+
+        static SettingsForm setsForm;
+
+        static bool running = true;
+
+        [STAThread]
+        static void Main()
+        {
+            bool firstProcess;
+            Mutex singleMutex = new Mutex
+            (
+                true,
+                "SteamChatLogger_4d8g8hisih39", // this is hopefully a unique mutex string not present on the current system
+                out firstProcess
+            );
+
+            if ( !firstProcess )
+            {
+                // process already exists, so we exit
+                Util.ShowWarning( null, "Steam chat logger is already running. You may control it through context menus in the task bar icon." );
+                return;
+            }
+
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault( false );
+
+            // load our settings
+            try
+            {
+                sets = Settings.Load( Settings.BackingFile );
+            }
+            catch ( FileNotFoundException )
+            {
+                // if the file isn't found, no biggie
+                sets = new Settings();
+                sets.Save();
+            }
+            catch ( Exception ex )
+            {
+                Util.ShowError( null, "Unable to load settings: " + ex.Message + "\n\nResetting to defaults." );
+                sets = new Settings();
+                sets.Save();
+            }
+
+            setsForm = new SettingsForm( sets );
+
+            // initialize the notification icon context
+            notifyIcon = new Notification();
+            notifyIcon.Exit += new EventHandler( notifyIcon_Exit );
+            notifyIcon.ShowSettings += new EventHandler( notifyIcon_ShowSettings );
+
+
+            // initialize logging
+            logManager = new LogManager( sets );
+            logManager.LogFailure += new EventHandler<LogFailureEventArgs>( logManager_LogFailure );
+
+            if ( !logManager.Initialized )
+            {
+                notifyIcon.Visible = false;
+                return;
+            }
+
+
+            while ( running )
+            {
+                Application.DoEvents();
+
+                logManager.Update();
+
+                Thread.Sleep( 10 );
+            }
+        }
+
+        static void logManager_LogFailure( object sender, LogFailureEventArgs e )
+        {
+            notifyIcon.ShowError( "Unable to log message: " + e.Reason + "\n\nPlease check settings." );
+        }
+
+        static void notifyIcon_ShowSettings( object sender, EventArgs e )
+        {
+            if ( setsForm == null )
+                return;
+
+            setsForm.Show();
+        }
+
+        static void notifyIcon_Exit( object sender, EventArgs e )
+        {
+            running = false;
+
+            notifyIcon.Visible = false;
+            setsForm.Close(); // this saves settings and hides the form
+        }
+    }
+}

Chat Log/Properties/AssemblyInfo.cs

+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System;
+using System.Resources;
+using System.Diagnostics.CodeAnalysis;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle( "Steam Chat Logger" )]
+[assembly: AssemblyDescription( "Steam Chat Logger" )]
+[assembly: AssemblyConfiguration( "" )]
+[assembly: AssemblyCompany( "VoiDeD" )]
+[assembly: AssemblyProduct( "Steam Chat Logger" )]
+[assembly: AssemblyCopyright( "Copyright © Ryan Stecker 2009" )]
+[assembly: AssemblyTrademark( "" )]
+
+[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
+
+[assembly: ComVisible( false )]
+[assembly: CLSCompliant( true )]
+
+
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion( "0.3.*" )]

Chat Log/Properties/Resources.Designer.cs

+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:2.0.50727.3603
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ChatLog.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChatLog.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        internal static System.Drawing.Icon logger {
+            get {
+                object obj = ResourceManager.GetObject("logger", resourceCulture);
+                return ((System.Drawing.Icon)(obj));
+            }
+        }
+    }
+}

Chat Log/Properties/Resources.resx

+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="logger" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\logger.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+</root>

Chat Log/Properties/Settings.Designer.cs

+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:2.0.50727.3603
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ChatLog.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}

Chat Log/Properties/Settings.settings

+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

Chat Log/Resources/logger.ico

Added
New image

Chat Log/UI/BrowseTextBox.Designer.cs

+namespace ChatLog
+{
+    partial class BrowseTextBox
+    {
+        /// <summary> 
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose( bool disposing )
+        {
+            if ( disposing && ( components != null ) )
+            {
+                components.Dispose();
+            }
+            base.Dispose( disposing );
+        }
+
+        #region Component Designer generated code
+
+        /// <summary> 
+        /// Required method for Designer support - do not modify 
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.browseButton = new System.Windows.Forms.Button();
+            this.textBox = new System.Windows.Forms.TextBox();
+            this.SuspendLayout();
+            // 
+            // browseButton
+            // 
+            this.browseButton.Anchor = ( ( System.Windows.Forms.AnchorStyles )( ( System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right ) ) );
+            this.browseButton.Location = new System.Drawing.Point( 324, 3 );
+            this.browseButton.Name = "browseButton";
+            this.browseButton.Size = new System.Drawing.Size( 75, 23 );
+            this.browseButton.TabIndex = 0;
+            this.browseButton.Text = "Browse";
+            this.browseButton.UseVisualStyleBackColor = true;
+            this.browseButton.Click += new System.EventHandler( this.browseButton_Click );
+            // 
+            // textBox
+            // 
+            this.textBox.Anchor = ( ( System.Windows.Forms.AnchorStyles )( ( ( ( System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom )
+                        | System.Windows.Forms.AnchorStyles.Left )
+                        | System.Windows.Forms.AnchorStyles.Right ) ) );
+            this.textBox.Location = new System.Drawing.Point( 3, 5 );
+            this.textBox.Name = "textBox";
+            this.textBox.Size = new System.Drawing.Size( 315, 20 );
+            this.textBox.TabIndex = 1;
+            // 
+            // BrowseTextBox
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add( this.textBox );
+            this.Controls.Add( this.browseButton );
+            this.Name = "BrowseTextBox";
+            this.Size = new System.Drawing.Size( 402, 29 );
+            this.ResumeLayout( false );
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Button browseButton;
+        private System.Windows.Forms.TextBox textBox;
+    }
+}

Chat Log/UI/BrowseTextBox.cs

+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.IO;
+
+namespace ChatLog
+{
+    public partial class BrowseTextBox : UserControl
+    {
+        public override string Text
+        {
+            get { return textBox.Text; }
+            set {textBox.Text = value; }
+        }
+
+        public BrowseTextBox()
+        {
+            InitializeComponent();
+        }
+
+        private void browseButton_Click( object sender, EventArgs e )
+        {
+            FolderBrowserDialog fbd = new FolderBrowserDialog();
+
+            fbd.Description = "Please select the folder where chatlogs will be stored.";
+            fbd.ShowNewFolderButton = true;
+
+            if ( fbd.ShowDialog() != DialogResult.OK )
+                return;
+
+            if ( !Directory.Exists( fbd.SelectedPath ) )
+            {
+                try
+                {
+                    Directory.CreateDirectory( fbd.SelectedPath );
+                }
+                catch ( Exception ex )
+                {
+                    Util.ShowError( this, "Unable to create logging directory: " + ex.Message );
+                    return;
+                }
+            }
+
+            textBox.Text = fbd.SelectedPath;
+        }
+    }
+}

Chat Log/UI/BrowseTextBox.resx

+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

Chat Log/UI/Notification.cs

+
+namespace ChatLog
+{
+    using System;
+    using System.ComponentModel;
+    using System.Windows.Forms;
+
+    class Notification : Component
+    {
+        private ContextMenuStrip contextMenuStrip1;
+        private IContainer components;
+        private ToolStripMenuItem showSettingsItem;
+        private ToolStripSeparator toolStripSeparator1;
+        private NotifyIcon notifyIcon1;
+        private ToolStripMenuItem exitItem;
+
+
+        public event EventHandler ShowSettings;
+        public event EventHandler Exit;
+
+        public bool Visible
+        {
+            get { return notifyIcon1.Visible; }
+            set { notifyIcon1.Visible = value; }
+        }
+
+
+        public Notification()
+        {
+            InitializeComponent();
+
+            notifyIcon1.Icon = Properties.Resources.logger;
+
+            showSettingsItem.Click += new EventHandler( showSettingsItem_Click );
+            exitItem.Click += new EventHandler( exitItem_Click );
+        }
+
+        public void ShowError( string message )
+        {
+            notifyIcon1.ShowBalloonTip( 10 * 1000, "Error", message, ToolTipIcon.Error );
+        }
+
+        void exitItem_Click( object sender, EventArgs e )
+        {
+            OnExit( e );
+        }
+        void showSettingsItem_Click( object sender, EventArgs e )
+        {
+            OnShowSettings( e );
+        }
+
+        protected virtual void OnShowSettings( EventArgs e )
+        {
+            if ( ShowSettings != null )
+                ShowSettings( this, e );
+        }
+        protected virtual void OnExit( EventArgs e )
+        {
+            if ( Exit != null )
+                Exit( this, e );
+        }
+
+        private void InitializeComponent()
+        {
+            this.components = new System.ComponentModel.Container();
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager( typeof( Notification ) );
+            this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip( this.components );
+            this.showSettingsItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+            this.exitItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.notifyIcon1 = new System.Windows.Forms.NotifyIcon( this.components );
+            this.contextMenuStrip1.SuspendLayout();
+            // 
+            // contextMenuStrip1
+            // 
+            this.contextMenuStrip1.Items.AddRange( new System.Windows.Forms.ToolStripItem[] {
+            this.showSettingsItem,
+            this.toolStripSeparator1,
+            this.exitItem} );
+            this.contextMenuStrip1.Name = "contextMenuStrip1";
+            this.contextMenuStrip1.Size = new System.Drawing.Size( 143, 54 );
+            // 
+            // showSettingsItem
+            // 
+            this.showSettingsItem.Name = "showSettingsItem";
+            this.showSettingsItem.Size = new System.Drawing.Size( 142, 22 );
+            this.showSettingsItem.Text = "Show Settings";
+            // 
+            // toolStripSeparator1
+            // 
+            this.toolStripSeparator1.Name = "toolStripSeparator1";
+            this.toolStripSeparator1.Size = new System.Drawing.Size( 139, 6 );
+            // 
+            // exitItem
+            // 
+            this.exitItem.Name = "exitItem";
+            this.exitItem.Size = new System.Drawing.Size( 142, 22 );
+            this.exitItem.Text = "Exit";
+            // 
+            // notifyIcon1
+            // 
+            this.notifyIcon1.ContextMenuStrip = this.contextMenuStrip1;
+            this.notifyIcon1.Icon = ( ( System.Drawing.Icon )( resources.GetObject( "notifyIcon1.Icon" ) ) );
+            this.notifyIcon1.Text = "Steam Chat Logger";
+            this.notifyIcon1.Visible = true;
+            this.notifyIcon1.BalloonTipClicked += new System.EventHandler( this.notifyIcon1_BalloonTipClicked );
+            this.notifyIcon1.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler( this.notifyIcon1_MouseDoubleClick );
+            this.contextMenuStrip1.ResumeLayout( false );
+
+        }
+
+        private void notifyIcon1_BalloonTipClicked( object sender, EventArgs e )
+        {
+            OnShowSettings( e );