diff --git a/osu-framework b/osu-framework
index 21fd81a848..e776f6f272 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 21fd81a84860cf6854026458ac82905b6248f41d
+Subproject commit e776f6f2729bbe93206c4e8c089eeca57522fcf7
diff --git a/osu.Desktop.Tests/BenchmarkTest.cs b/osu.Desktop.Tests/BenchmarkTest.cs
new file mode 100644
index 0000000000..c55b98aa57
--- /dev/null
+++ b/osu.Desktop.Tests/BenchmarkTest.cs
@@ -0,0 +1,33 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Desktop.VisualTests;
+using osu.Framework.Desktop.Platform;
+using osu.Game.Modes;
+using osu.Game.Modes.Catch;
+using osu.Game.Modes.Mania;
+using osu.Game.Modes.Osu;
+using osu.Game.Modes.Taiko;
+
+namespace osu.Desktop.Tests
+{
+    [TestFixture]
+    public class BenchmarkTest
+    {
+        [Test]
+        public void TestBenchmark()
+        {
+            using (var host = new HeadlessGameHost())
+            {
+                Ruleset.Register(new OsuRuleset());
+                Ruleset.Register(new TaikoRuleset());
+                Ruleset.Register(new ManiaRuleset());
+                Ruleset.Register(new CatchRuleset());
+
+                host.Add(new Benchmark());
+                host.Run();
+            }
+        }
+    }
+}
diff --git a/osu.Desktop.Tests/app.config b/osu.Desktop.Tests/app.config
new file mode 100644
index 0000000000..44ccc4b77a
--- /dev/null
+++ b/osu.Desktop.Tests/app.config
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj
new file mode 100644
index 0000000000..2c88548c3e
--- /dev/null
+++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>osu.Desktop.Tests</RootNamespace>
+    <AssemblyName>osu.Desktop.Tests</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </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>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+  </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>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="mscorlib" />
+    <Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>$(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="SQLiteNetExtensions">
+      <HintPath>$(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll</HintPath>
+    </Reference>
+    <Reference Include="SQLite.Net.Platform.Win32">
+      <HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll</HintPath>
+    </Reference>
+    <Reference Include="SQLite.Net.Platform.Generic">
+      <HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll</HintPath>
+    </Reference>
+    <Reference Include="OpenTK">
+      <HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1339\lib\net45\OpenTK.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BenchmarkTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\osu-framework\osu.Framework.Desktop\osu.Framework.Desktop.csproj">
+      <Project>{65DC628F-A640-4111-AB35-3A5652BC1E17}</Project>
+      <Name>osu.Framework.Desktop</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
+      <Project>{C76BF5B3-985E-4D39-95FE-97C9C879B83A}</Project>
+      <Name>osu.Framework</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
+      <Project>{d9a367c9-4c1a-489f-9b05-a0cea2b53b58}</Project>
+      <Name>osu.Game.Resources</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Desktop.VisualTests\osu.Desktop.VisualTests.csproj">
+      <Project>{69051C69-12AE-4E7D-A3E6-460D2E282312}</Project>
+      <Name>osu.Desktop.VisualTests</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Game.Modes.Catch\osu.Game.Modes.Catch.csproj">
+      <Project>{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}</Project>
+      <Name>osu.Game.Modes.Catch</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Game.Modes.Mania\osu.Game.Modes.Mania.csproj">
+      <Project>{48F4582B-7687-4621-9CBE-5C24197CB536}</Project>
+      <Name>osu.Game.Modes.Mania</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Game.Modes.Osu\osu.Game.Modes.Osu.csproj">
+      <Project>{C92A607B-1FDD-4954-9F92-03FF547D9080}</Project>
+      <Name>osu.Game.Modes.Osu</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Game.Modes.Taiko\osu.Game.Modes.Taiko.csproj">
+      <Project>{F167E17A-7DE6-4AF5-B920-A5112296C695}</Project>
+      <Name>osu.Game.Modes.Taiko</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\osu.Game\osu.Game.csproj">
+      <Project>{0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D}</Project>
+      <Name>osu.Game</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Properties\" />
+  </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>
\ No newline at end of file
diff --git a/osu.Desktop.Tests/packages.config b/osu.Desktop.Tests/packages.config
new file mode 100644
index 0000000000..05b53c019c
--- /dev/null
+++ b/osu.Desktop.Tests/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
+  <package id="NUnit" version="3.5.0" targetFramework="net45" />
+  <package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
+  <package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
+  <package id="SQLiteNetExtensions" version="1.3.0" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/osu.Desktop.VisualTests/Benchmark.cs b/osu.Desktop.VisualTests/Benchmark.cs
new file mode 100644
index 0000000000..506788e93c
--- /dev/null
+++ b/osu.Desktop.VisualTests/Benchmark.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Desktop.Platform;
+using osu.Framework.GameModes.Testing;
+using osu.Game;
+using osu.Game.Modes;
+using osu.Game.Modes.Catch;
+using osu.Game.Modes.Mania;
+using osu.Game.Modes.Osu;
+using osu.Game.Modes.Taiko;
+
+namespace osu.Desktop.VisualTests
+{
+    public class Benchmark : OsuGameBase
+    {
+        private double timePerTest = 200;
+
+        [BackgroundDependencyLoader]
+        private void load(BaseGame game)
+        {
+            Host.MaximumDrawHz = int.MaxValue;
+            Host.MaximumUpdateHz = int.MaxValue;
+            Host.MaximumInactiveHz = int.MaxValue;
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            TestBrowser f = new TestBrowser();
+            Add(f);
+
+            Console.WriteLine($@"{Time}: Running {f.TestCount} tests for {timePerTest}ms each...");
+
+            for (int i = 1; i < f.TestCount; i++)
+            {
+                int loadableCase = i;
+                Scheduler.AddDelayed(delegate
+                {
+                    f.LoadTest(loadableCase);
+                    Console.WriteLine($@"{Time}: Switching to test #{loadableCase}");
+                }, loadableCase * timePerTest);
+            }
+
+            Scheduler.AddDelayed(Host.Exit, f.TestCount * timePerTest);
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/Program.cs b/osu.Desktop.VisualTests/Program.cs
index df54297812..b89c6bcf4d 100644
--- a/osu.Desktop.VisualTests/Program.cs
+++ b/osu.Desktop.VisualTests/Program.cs
@@ -18,6 +18,8 @@ namespace osu.Desktop.VisualTests
         [STAThread]
         public static void Main(string[] args)
         {
+            bool benchmark = args.Length > 0 && args[0] == @"-benchmark";
+
             using (BasicGameHost host = Host.GetSuitableHost(@"osu"))
             {
                 Ruleset.Register(new OsuRuleset());
@@ -25,7 +27,10 @@ namespace osu.Desktop.VisualTests
                 Ruleset.Register(new ManiaRuleset());
                 Ruleset.Register(new CatchRuleset());
 
-                host.Add(new VisualTestGame());
+                if (benchmark)
+                    host.Add(new Benchmark());
+                else
+                    host.Add(new VisualTestGame());
                 host.Run();
             }
         }
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs b/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs
new file mode 100644
index 0000000000..77b313f4ad
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs
@@ -0,0 +1,121 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.GameModes.Testing;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Overlays;
+using System.Linq;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Screens.Backgrounds;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    class TestCaseNotificationManager : TestCase
+    {
+        public override string Name => @"Notification Manager";
+        public override string Description => @"I handle notifications";
+
+        NotificationManager manager;
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            progressingNotifications.Clear();
+
+            AddInternal(new BackgroundModeDefault() { Depth = 10 });
+
+            Content.Add(manager = new NotificationManager
+            {
+                Anchor = Anchor.TopRight,
+                Origin = Anchor.TopRight,
+            });
+
+            AddToggle(@"show", manager.ToggleVisibility);
+
+            AddButton(@"simple #1", sendNotification1);
+            AddButton(@"simple #2", sendNotification2);
+            AddButton(@"progress #1", sendProgress1);
+            AddButton(@"progress #2", sendProgress2);
+            AddButton(@"barrage", () => sendBarrage());
+        }
+
+        private void sendBarrage(int remaining = 100)
+        {
+            switch (RNG.Next(0, 4))
+            {
+                case 0:
+                    sendNotification1();
+                    break;
+                case 1:
+                    sendNotification2();
+                    break;
+                case 2:
+                    sendProgress1();
+                    break;
+                case 3:
+                    sendProgress2();
+                    break;
+            }
+
+            if (remaining > 0)
+            {
+                Delay(80);
+                Schedule(() => sendBarrage(remaining - 1));
+            }
+        }
+
+        protected override void Update()
+        {
+            base.Update();
+
+            progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed);
+
+            while (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
+            {
+                var p = progressingNotifications.FirstOrDefault(n => n.IsLoaded && n.State == ProgressNotificationState.Queued);
+                if (p == null)
+                    break;
+
+                p.State = ProgressNotificationState.Active;
+            }
+
+            foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active))
+            {
+                if (n.Progress < 1)
+                    n.Progress += (float)(Time.Elapsed / 2000) * RNG.NextSingle();
+                else
+                    n.State = ProgressNotificationState.Completed;
+            }
+        }
+
+        private void sendProgress2()
+        {
+            var n = new ProgressNotification { Text = @"Downloading Haitai..." };
+            manager.Post(n);
+            progressingNotifications.Add(n);
+        }
+
+        List<ProgressNotification> progressingNotifications = new List<ProgressNotification>();
+
+        private void sendProgress1()
+        {
+            var n = new ProgressNotification { Text = @"Uploading to BSS..." };
+            manager.Post(n);
+            progressingNotifications.Add(n);
+        }
+
+        private void sendNotification2()
+        {
+            manager.Post(new SimpleNotification { Text = @"You are amazing" });
+        }
+
+        private void sendNotification1()
+        {
+            manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" });
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/VisualTestGame.cs b/osu.Desktop.VisualTests/VisualTestGame.cs
index 7489559c98..952de4ab26 100644
--- a/osu.Desktop.VisualTests/VisualTestGame.cs
+++ b/osu.Desktop.VisualTests/VisualTestGame.cs
@@ -10,7 +10,6 @@ using osu.Framework.Desktop.Platform;
 using System.Reflection;
 using System.IO;
 using System.Collections.Generic;
-using SQLiteNetExtensions.Extensions;
 using osu.Framework.Allocation;
 
 namespace osu.Desktop.VisualTests
diff --git a/osu.Desktop.VisualTests/app.config b/osu.Desktop.VisualTests/app.config
new file mode 100644
index 0000000000..9bad888bf3
--- /dev/null
+++ b/osu.Desktop.VisualTests/app.config
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
+Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+-->
+
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index 69df007013..cd899d4dd2 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -59,6 +59,7 @@
     <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
     <RunCodeAnalysis>false</RunCodeAnalysis>
     <Prefer32Bit>false</Prefer32Bit>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>none</DebugType>
@@ -73,6 +74,7 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
     <Prefer32Bit>false</Prefer32Bit>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup>
     <Win32Resource>
@@ -100,13 +102,12 @@
     <Reference Include="OpenTK">
       <HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1339\lib\net45\OpenTK.dll</HintPath>
     </Reference>
-    <Reference Include="System.Drawing" />
-    <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
     <None Include="..\osu.licenseheader">
       <Link>osu.licenseheader</Link>
     </None>
+    <None Include="app.config" />
     <None Include="packages.config" />
     <None Include="OpenTK.dll.config" />
   </ItemGroup>
@@ -172,10 +173,12 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Benchmark.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Tests\TestCaseChatDisplay.cs" />
     <Compile Include="Tests\TestCaseGamefield.cs" />
     <Compile Include="Tests\TestCaseMusicController.cs" />
+    <Compile Include="Tests\TestCaseNotificationManager.cs" />
     <Compile Include="Tests\TestCasePlayer.cs" />
     <Compile Include="Tests\TestCaseHitObjects.cs" />
     <Compile Include="Tests\TestCaseKeyCounter.cs" />
diff --git a/osu.Desktop.VisualTests/packages.config b/osu.Desktop.VisualTests/packages.config
index f39a585e49..75affafd35 100644
--- a/osu.Desktop.VisualTests/packages.config
+++ b/osu.Desktop.VisualTests/packages.config
@@ -1,13 +1,12 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
-Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
--->
-
-<packages>
-  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
-  <package id="ppy.OpenTK" version="2.0.50727.1339" targetFramework="net45" />
-  <package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
-  <package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
-  <package id="SQLiteNetExtensions" version="1.3.0" targetFramework="net45" />
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
+Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+-->
+<packages>
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
+  <package id="ppy.OpenTK" version="2.0.50727.1339" targetFramework="net45" />
+  <package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
+  <package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
+  <package id="SQLiteNetExtensions" version="1.3.0" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 5edeffd448..167d4644b7 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -11,6 +11,9 @@ using System.Windows.Forms;
 using osu.Framework.Platform;
 using osu.Framework.Desktop.Platform;
 using osu.Game.Database;
+using osu.Desktop.Overlays;
+using System.Reflection;
+using System.Drawing;
 
 namespace osu.Desktop
 {
@@ -22,12 +25,22 @@ namespace osu.Desktop
 
         }
 
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            (new VersionManager()).Preload(this, Add);
+        }
+
         public override void SetHost(BasicGameHost host)
         {
             base.SetHost(host);
             var desktopWindow = host.Window as DesktopGameWindow;
             if (desktopWindow != null)
             {
+                desktopWindow.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
+                desktopWindow.Title = @"osu!lazer";
+
                 desktopWindow.DragEnter += dragEnter;
                 desktopWindow.DragDrop += dragDrop;
             }
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
new file mode 100644
index 0000000000..a3648e1f12
--- /dev/null
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -0,0 +1,93 @@
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Notifications;
+using Squirrel;
+using System.Reflection;
+
+namespace osu.Desktop.Overlays
+{
+    public class VersionManager : OverlayContainer
+    {
+        private UpdateManager updateManager;
+        private NotificationManager notification;
+
+        [BackgroundDependencyLoader]
+        private void load(NotificationManager notification)
+        {
+            this.notification = notification;
+
+            AutoSizeAxes = Axes.Both;
+            Anchor = Anchor.BottomCentre;
+            Origin = Anchor.BottomCentre;
+
+            var asm = Assembly.GetEntryAssembly().GetName();
+            Add(new OsuSpriteText
+            {
+                Text = $@"osu!lazer v{asm.Version}"
+            });
+
+            updateChecker();
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            State = Visibility.Visible;
+        }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            base.Dispose(isDisposing);
+            updateManager?.Dispose();
+        }
+
+        private async void updateChecker()
+        {
+            updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
+            var info = await updateManager.CheckForUpdate();
+            if (info.ReleasesToApply.Count > 0)
+            {
+                ProgressNotification n = new UpdateProgressNotification
+                {
+                    Text = @"Downloading update..."
+                };
+                Schedule(() => notification.Post(n));
+                Schedule(() => n.State = ProgressNotificationState.Active);
+                await updateManager.DownloadReleases(info.ReleasesToApply, (int p) => Schedule(() => n.Progress = p / 100f));
+                Schedule(() => n.Text = @"Installing update...");
+                await updateManager.ApplyReleases(info, (int p) => Schedule(() => n.Progress = p / 100f));
+                Schedule(() => n.State = ProgressNotificationState.Completed);
+
+            }
+            else
+            {
+                //check again every 30 minutes.
+                Scheduler.AddDelayed(updateChecker, 60000 * 30);
+            }
+        }
+
+        protected override void PopIn()
+        {
+        }
+
+        protected override void PopOut()
+        {
+        }
+
+        class UpdateProgressNotification : ProgressNotification
+        {
+            protected override Notification CreateCompletionNotification() => new ProgressCompletionNotification(this)
+            {
+                Text = @"Update ready to install. Click to restart!",
+                Activated = () =>
+                {
+                    UpdateManager.RestartApp();
+                    return true;
+                }
+            };
+        }
+    }
+}
diff --git a/osu.Desktop/Properties/AssemblyInfo.cs b/osu.Desktop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..d7391080a6
--- /dev/null
+++ b/osu.Desktop/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 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("osu!lazer")]
+[assembly: AssemblyDescription("click the circles. to the beat.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
+[assembly: AssemblyProduct("osu!lazer")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("55e28cb2-7b6c-4595-8dcc-9871d8aad7e9")]
+
+[assembly: AssemblyVersion("0.0.3")]
+[assembly: AssemblyFileVersion("0.0.3")]
diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config
new file mode 100644
index 0000000000..44ccc4b77a
--- /dev/null
+++ b/osu.Desktop/app.config
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 9df4148ac0..2ab913d706 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -59,6 +59,7 @@
     <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
     <RunCodeAnalysis>false</RunCodeAnalysis>
     <Prefer32Bit>false</Prefer32Bit>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
     <Commandlineparameters>
     </Commandlineparameters>
   </PropertyGroup>
@@ -75,21 +76,76 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
     <Prefer32Bit>false</Prefer32Bit>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup>
-    <Win32Resource>osu!.res</Win32Resource>
+    <Win32Resource>
+    </Win32Resource>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>lazer.ico</ApplicationIcon>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>Properties\app.manifest</ApplicationManifest>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="DeltaCompressionDotNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1d14d6e5194e7f4a, processorArchitecture=MSIL">
+      <HintPath>..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="DeltaCompressionDotNet.MsDelta, Version=1.0.0.0, Culture=neutral, PublicKeyToken=46b2138a390abf55, processorArchitecture=MSIL">
+      <HintPath>..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.MsDelta.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="DeltaCompressionDotNet.PatchApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3e8888ee913ed789, processorArchitecture=MSIL">
+      <HintPath>..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.PatchApi.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\squirrel.windows.1.5.2\lib\Net45\ICSharpCode.SharpZipLib.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Mono.Cecil.Mdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Mdb.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Mono.Cecil.Pdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Pdb.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Mono.Cecil.Rocks, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="mscorlib" />
+    <Reference Include="NuGet.Squirrel, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\squirrel.windows.1.5.2\lib\Net45\NuGet.Squirrel.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4" />
+    <Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Squirrel, Version=1.5.2.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\squirrel.windows.1.5.2\lib\Net45\Squirrel.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Drawing" />
     <Reference Include="System.Windows.Forms" />
   </ItemGroup>
   <ItemGroup>
     <None Include="..\osu.licenseheader">
       <Link>osu.licenseheader</Link>
     </None>
+    <None Include="app.config" />
     <None Include="osu!.res" />
+    <None Include="packages.config" />
     <None Include="Properties\app.manifest" />
   </ItemGroup>
   <ItemGroup>
@@ -155,10 +211,14 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="OsuGameDesktop.cs" />
+    <Compile Include="Overlays\VersionManager.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Beatmaps\IO\LegacyFilesystemReader.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="lazer.ico" />
   </ItemGroup>
-  <ItemGroup />
   <ItemGroup />
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config
new file mode 100644
index 0000000000..8d1361bd0a
--- /dev/null
+++ b/osu.Desktop/packages.config
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="DeltaCompressionDotNet" version="1.0.0" targetFramework="net45" />
+  <package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" />
+  <package id="Splat" version="1.6.2" targetFramework="net45" />
+  <package id="squirrel.windows" version="1.5.2" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj
index e68203872e..8d32c23b9b 100644
--- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj
+++ b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj
@@ -20,6 +20,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
diff --git a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj
index 22b16c6a9a..7ff501c86d 100644
--- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj
+++ b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj
@@ -20,6 +20,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj
index a9a346f563..a659683c27 100644
--- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj
+++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj
@@ -21,6 +21,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -29,6 +30,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj
index 355f71d0df..47c71ce09b 100644
--- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj
+++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj
@@ -20,6 +20,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -28,6 +29,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 5e1ce553d8..c6f0c6fa55 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -18,6 +18,7 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <ConsolePause>false</ConsolePause>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <Optimize>true</Optimize>
@@ -25,6 +26,7 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <ConsolePause>false</ConsolePause>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 674ab76802..7f1cf236ac 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps
                 {
                     if (background != null) return background;
 
-                    if (BeatmapInfo.Metadata?.BackgroundFile == null) return null;
+                    if (BeatmapInfo?.Metadata?.BackgroundFile == null) return null;
 
                     try
                     {
diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs
index 1b678b2148..a81c1fe552 100644
--- a/osu.Game/Database/BeatmapDatabase.cs
+++ b/osu.Game/Database/BeatmapDatabase.cs
@@ -83,65 +83,67 @@ namespace osu.Game.Database
             connection.DeleteAll<BeatmapInfo>();
         }
 
-        public void Import(params string[] paths)
+        public void Import(IEnumerable<string> paths)
         {
             foreach (string p in paths)
+                Import(p);
+        }
+
+        public void Import(string path)
+        {
+            string hash = null;
+
+            BeatmapMetadata metadata;
+
+            using (var reader = ArchiveReader.GetReader(storage, path))
+                metadata = reader.ReadMetadata();
+
+            if (metadata.OnlineBeatmapSetID.HasValue &&
+                connection.Table<BeatmapSetInfo>().Count(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) != 0)
+                return; // TODO: Update this beatmap instead
+
+            if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
             {
-                var path = p;
-                string hash = null;
-
-                BeatmapMetadata metadata;
-
-                using (var reader = ArchiveReader.GetReader(storage, path))
-                    metadata = reader.ReadMetadata();
-
-                if (metadata.OnlineBeatmapSetID.HasValue &&
-                    connection.Table<BeatmapSetInfo>().Count(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) != 0)
-                    return; // TODO: Update this beatmap instead
-
-                if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
+                using (var md5 = MD5.Create())
+                using (var input = storage.GetStream(path))
                 {
-                    using (var md5 = MD5.Create())
-                    using (var input = storage.GetStream(path))
-                    {
-                        hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant();
-                        input.Seek(0, SeekOrigin.Begin);
-                        path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
-                        using (var output = storage.GetStream(path, FileAccess.Write))
-                            input.CopyTo(output);
-                    }
+                    hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant();
+                    input.Seek(0, SeekOrigin.Begin);
+                    path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
+                    using (var output = storage.GetStream(path, FileAccess.Write))
+                        input.CopyTo(output);
                 }
-                var beatmapSet = new BeatmapSetInfo
-                {
-                    OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
-                    Beatmaps = new List<BeatmapInfo>(),
-                    Path = path,
-                    Hash = hash,
-                    Metadata = metadata
-                };
+            }
+            var beatmapSet = new BeatmapSetInfo
+            {
+                OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
+                Beatmaps = new List<BeatmapInfo>(),
+                Path = path,
+                Hash = hash,
+                Metadata = metadata
+            };
 
-                using (var reader = ArchiveReader.GetReader(storage, path))
+            using (var reader = ArchiveReader.GetReader(storage, path))
+            {
+                string[] mapNames = reader.ReadBeatmaps();
+                foreach (var name in mapNames)
                 {
-                    string[] mapNames = reader.ReadBeatmaps();
-                    foreach (var name in mapNames)
+                    using (var stream = new StreamReader(reader.GetStream(name)))
                     {
-                        using (var stream = new StreamReader(reader.GetStream(name)))
-                        {
-                            var decoder = BeatmapDecoder.GetDecoder(stream);
-                            Beatmap beatmap = decoder.Decode(stream);
-                            beatmap.BeatmapInfo.Path = name;
+                        var decoder = BeatmapDecoder.GetDecoder(stream);
+                        Beatmap beatmap = decoder.Decode(stream);
+                        beatmap.BeatmapInfo.Path = name;
 
-                            // TODO: Diff beatmap metadata with set metadata and leave it here if necessary
-                            beatmap.BeatmapInfo.Metadata = null;
+                        // TODO: Diff beatmap metadata with set metadata and leave it here if necessary
+                        beatmap.BeatmapInfo.Metadata = null;
 
-                            beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
-                        }
+                        beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
                     }
                     beatmapSet.StoryboardFile = reader.ReadStoryboard();
                 }
-
-                Import(new[] { beatmapSet });
             }
+
+            Import(new[] { beatmapSet });
         }
 
         public void Import(IEnumerable<BeatmapSetInfo> beatmapSets)
diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs
index c5e53e14a5..fe3601a5f2 100644
--- a/osu.Game/Graphics/Containers/ParallaxContainer.cs
+++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs
@@ -45,7 +45,8 @@ namespace osu.Game.Graphics.Containers
         {
             base.Update();
 
-            content.MoveTo((ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
+            Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2;
+            content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
             content.Scale = new Vector2(1 + ParallaxAmount);
 
             firstUpdate = false;
diff --git a/osu.Game/Graphics/Cursor/OsuCursorContainer.cs b/osu.Game/Graphics/Cursor/OsuCursorContainer.cs
index 8df0777989..691fb701f1 100644
--- a/osu.Game/Graphics/Cursor/OsuCursorContainer.cs
+++ b/osu.Game/Graphics/Cursor/OsuCursorContainer.cs
@@ -67,6 +67,11 @@ namespace osu.Game.Graphics.Cursor
                         Masking = true,
                         BorderThickness = Size.X / 6,
                         BorderColour = Color4.White,
+                        EdgeEffect = new EdgeEffect {
+                            Type = EdgeEffectType.Shadow,
+                            Colour = Color4.Pink.Opacity(0.5f),
+                            Radius = 5,
+                        },
                         Children = new Drawable[]
                         {
                             new Box
diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
index 5bc76b74b4..f5749846be 100644
--- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
@@ -9,7 +9,7 @@ using OpenTK.Graphics;
 
 namespace osu.Game.Graphics.Sprites
 {
-    class OsuSpriteText : SpriteText
+    public class OsuSpriteText : SpriteText
     {
         public const float FONT_SIZE = 16;
 
diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs
index c8174ef546..61b59d6b51 100644
--- a/osu.Game/Graphics/UserInterface/Nub.cs
+++ b/osu.Game/Graphics/UserInterface/Nub.cs
@@ -13,7 +13,7 @@ using osu.Framework.Graphics.UserInterface;
 
 namespace osu.Game.Graphics.UserInterface
 {
-    class Nub : CircularContainer, IStateful<CheckBoxState>
+    public class Nub : CircularContainer, IStateful<CheckBoxState>
     {
         public const float COLLAPSED_SIZE = 20;
         public const float EXPANDED_SIZE = 40;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 97c9dbb2e0..9188fab355 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -24,6 +24,7 @@ using osu.Game.Screens.Menu;
 using OpenTK;
 using System.Linq;
 using osu.Framework.Graphics.Primitives;
+using System.Collections.Generic;
 
 namespace osu.Game
 {
@@ -35,6 +36,8 @@ namespace osu.Game
 
         private MusicController musicController;
 
+        private NotificationManager notificationManager;
+
         private MainMenu mainMenu => modeStack?.ChildGameMode as MainMenu;
         private Intro intro => modeStack as Intro;
 
@@ -65,14 +68,17 @@ namespace osu.Game
             }
 
             if (args?.Length > 0)
-                ImportBeatmaps(args);
+            {
+                var paths = args.Where(a => !a.StartsWith(@"-"));
+                ImportBeatmaps(paths);
+            }
 
             Dependencies.Cache(this);
 
             PlayMode = LocalConfig.GetBindable<PlayMode>(OsuConfig.PlayMode);
         }
 
-        public void ImportBeatmaps(params string[] paths)
+        public void ImportBeatmaps(IEnumerable<string> paths)
         {
             Schedule(delegate { Dependencies.Get<BeatmapDatabase>().Import(paths); });
         }
@@ -117,8 +123,16 @@ namespace osu.Game
                 Origin = Anchor.TopRight,
             }).Preload(this, overlayContent.Add);
 
+            (notificationManager = new NotificationManager
+            {
+                Depth = -2,
+                Anchor = Anchor.TopRight,
+                Origin = Anchor.TopRight,
+            }).Preload(this, overlayContent.Add);
+
             Dependencies.Cache(options);
             Dependencies.Cache(musicController);
+            Dependencies.Cache(notificationManager);
 
             (Toolbar = new Toolbar
             {
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index 457dc1e7e1..3b13c7bf88 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -240,7 +240,6 @@ namespace osu.Game.Overlays
 
             if (current?.TrackLoaded ?? false)
             {
-
                 progress.UpdatePosition((float)(current.Track.CurrentTime / current.Track.Length));
                 playButton.Icon = current.Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
 
diff --git a/osu.Game/Overlays/NotificationManager.cs b/osu.Game/Overlays/NotificationManager.cs
new file mode 100644
index 0000000000..ecfa3daa38
--- /dev/null
+++ b/osu.Game/Overlays/NotificationManager.cs
@@ -0,0 +1,113 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Transformations;
+using osu.Game.Graphics;
+using osu.Game.Overlays.Notifications;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays
+{
+    public class NotificationManager : FocusedOverlayContainer
+    {
+        private const float width = 320;
+
+        public const float TRANSITION_LENGTH = 600;
+
+        private ScrollContainer scrollContainer;
+        private FlowContainer<NotificationSection> sections;
+
+        [BackgroundDependencyLoader(permitNulls: true)]
+        private void load(OsuColour colours)
+        {
+            Width = width;
+            RelativeSizeAxes = Axes.Y;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                    Alpha = 0.6f,
+                },
+                scrollContainer = new ScrollContainer()
+                {
+                    Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT },
+                    Children = new[]
+                    {
+                        sections = new FlowContainer<NotificationSection>
+                        {
+                            Direction = FlowDirection.VerticalOnly,
+                            AutoSizeAxes = Axes.Y,
+                            RelativeSizeAxes = Axes.X,
+                            Children = new []
+                            {
+                                new NotificationSection
+                                {
+                                    Title = @"Notifications",
+                                    ClearText = @"Clear All",
+                                    AcceptTypes = new [] { typeof(SimpleNotification) },
+                                },
+                                new NotificationSection
+                                {
+                                    Title = @"Running Tasks",
+                                    ClearText = @"Cancel All",
+                                    AcceptTypes = new [] { typeof(ProgressNotification) },
+                                },
+                            }
+                        }
+                    }
+                }
+            };
+        }
+
+        int runningDepth = 0;
+
+        public void Post(Notification notification)
+        {
+            State = Visibility.Visible;
+
+            ++runningDepth;
+            notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
+
+            var hasCompletionTarget = notification as IHasCompletionTarget;
+            if (hasCompletionTarget != null)
+                hasCompletionTarget.CompletionTarget = Post;
+
+            var ourType = notification.GetType();
+            sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => ourType == accept || ourType.IsSubclassOf(accept)))?.Add(notification);
+        }
+
+        protected override void PopIn()
+        {
+            base.PopIn();
+
+            scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
+            MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
+            FadeTo(1, TRANSITION_LENGTH / 2);
+        }
+
+        private void markAllRead()
+        {
+            sections.Children.ForEach(s => s.MarkAllRead());
+        }
+
+        protected override void PopOut()
+        {
+            base.PopOut();
+
+            markAllRead();
+
+            MoveToX(width, TRANSITION_LENGTH, EasingTypes.OutQuint);
+            FadeTo(0, TRANSITION_LENGTH / 2);
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs
new file mode 100644
index 0000000000..7fba75c4fc
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public interface IHasCompletionTarget
+    {
+        Action<Notification> CompletionTarget { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs
new file mode 100644
index 0000000000..6faa79433e
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/Notification.cs
@@ -0,0 +1,284 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Transformations;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public abstract class Notification : Container
+    {
+        /// <summary>
+        /// Use requested close.
+        /// </summary>
+        public Action Closed;
+
+        /// <summary>
+        /// Run on user activating the notification. Return true to close.
+        /// </summary>
+        public Func<bool> Activated;
+
+        /// <summary>
+        /// Should we show at the top of our section on display?
+        /// </summary>
+        public virtual bool DisplayOnTop => true;
+
+        protected NotificationLight Light;
+        private CloseButton closeButton;
+        protected Container IconContent;
+        private Container content;
+
+        protected override Container<Drawable> Content => content;
+
+        protected Container NotificationContent;
+
+        private bool read;
+
+        public virtual bool Read
+        {
+            get { return read; }
+            set
+            {
+                read = value;
+            }
+        }
+
+        public Notification()
+        {
+            RelativeSizeAxes = Axes.X;
+            AutoSizeAxes = Axes.Y;
+
+            AddInternal(new Drawable[]
+            {
+                Light = new NotificationLight
+                {
+                    Margin = new MarginPadding { Right = 5 },
+                    Anchor = Anchor.CentreLeft,
+                    Origin = Anchor.CentreRight,
+                },
+                NotificationContent = new Container
+                {
+                    CornerRadius = 8,
+                    Masking = true,
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Children = new Drawable[]
+                    {
+                        new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Colour = Color4.White,
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            Padding = new MarginPadding(5),
+                            Height = 60,
+                            Children = new Drawable[]
+                            {
+                                IconContent = new Container
+                                {
+                                    Size = new Vector2(40),
+                                    Colour = Color4.DarkGray,
+                                    Masking = true,
+                                    CornerRadius = 5,
+                                },
+                                content = new Container
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Padding = new MarginPadding
+                                    {
+                                        Top = 5,
+                                        Left = 45,
+                                        Right = 30
+                                    },
+                                }
+                            }
+                        },
+                        closeButton = new CloseButton
+                        {
+                            Alpha = 0,
+                            Action = Close,
+                            Anchor = Anchor.CentreRight,
+                            Origin = Anchor.CentreRight,
+                            Margin = new MarginPadding
+                            {
+                                Right = 5
+                            },
+                        }
+                    }
+                }
+            });
+        }
+
+        protected override bool OnHover(InputState state)
+        {
+            closeButton.FadeIn(75);
+            return base.OnHover(state);
+        }
+
+        protected override void OnHoverLost(InputState state)
+        {
+            closeButton.FadeOut(75);
+            base.OnHoverLost(state);
+        }
+
+        protected override bool OnClick(InputState state)
+        {
+            if (Activated?.Invoke() ?? true)
+                Close();
+
+            return true;
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            FadeInFromZero(200);
+            NotificationContent.MoveToX(DrawSize.X);
+            NotificationContent.MoveToX(0, 500, EasingTypes.OutQuint);
+        }
+
+        private bool wasClosed;
+
+        public virtual void Close()
+        {
+            if (wasClosed) return;
+            wasClosed = true;
+
+            Closed?.Invoke();
+            FadeOut(100);
+            Expire();
+        }
+
+        class CloseButton : ClickableContainer
+        {
+            private Color4 hoverColour;
+
+            public CloseButton()
+            {
+                Colour = OsuColour.Gray(0.2f);
+                AutoSizeAxes = Axes.Both;
+
+                Children = new[]
+                {
+                    new TextAwesome
+                    {
+                        Anchor = Anchor.Centre,
+                        Icon = FontAwesome.fa_times_circle,
+                    }
+                };
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                hoverColour = colours.Yellow;
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                FadeColour(hoverColour, 200);
+                return base.OnHover(state);
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
+                FadeColour(OsuColour.Gray(0.2f), 200);
+                base.OnHoverLost(state);
+            }
+        }
+
+        public class NotificationLight : Container
+        {
+            private bool pulsate;
+            private Container pulsateLayer;
+
+            public bool Pulsate
+            {
+                get { return pulsate; }
+                set
+                {
+                    pulsate = value;
+
+                    pulsateLayer.ClearTransformations();
+                    pulsateLayer.Alpha = 1;
+
+                    if (pulsate)
+                    {
+                        const float length = 1000;
+                        pulsateLayer.Transforms.Add(new TransformAlpha
+                        {
+                            StartTime = Time.Current,
+                            EndTime = Time.Current + length,
+                            StartValue = 1,
+                            EndValue = 0.4f,
+                            Easing = EasingTypes.In
+                        });
+                        pulsateLayer.Transforms.Add(new TransformAlpha
+                        {
+                            StartTime = Time.Current + length,
+                            EndTime = Time.Current + length * 2,
+                            StartValue = 0.4f,
+                            EndValue = 1,
+                            Easing = EasingTypes.Out
+                        });
+
+                        //todo: figure why we can't add arbitrary delays at the end of loop.
+                        pulsateLayer.Loop(length * 2);
+                    }
+                }
+            }
+
+            public new SRGBColour Colour
+            {
+                set
+                {
+                    base.Colour = value;
+                    pulsateLayer.EdgeEffect = new EdgeEffect
+                    {
+                        Colour = ((Color4)value).Opacity(0.5f), //todo: avoid cast
+                        Type = EdgeEffectType.Glow,
+                        Radius = 12,
+                        Roundness = 12,
+                    };
+                }
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                Size = new Vector2(6, 15);
+
+                Children = new[]
+                {
+                    pulsateLayer = new CircularContainer
+                    {
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
+                        Masking = true,
+                        RelativeSizeAxes = Axes.Both,
+                        Children = new[]
+                        {
+                            new Box
+                            {
+                                RelativeSizeAxes = Axes.Both,
+                            },
+                        }
+                    }
+                };
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs
new file mode 100644
index 0000000000..ec286302b8
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/NotificationSection.cs
@@ -0,0 +1,162 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Transformations;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using OpenTK;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public class NotificationSection : FlowContainer
+    {
+        private OsuSpriteText titleText;
+        private OsuSpriteText countText;
+
+        private ClearAllButton clearButton;
+
+        private FlowContainer<Notification> notifications;
+
+        public void Add(Notification notification)
+        {
+            notifications.Add(notification);
+        }
+
+        public IEnumerable<Type> AcceptTypes;
+
+        private string clearText;
+        public string ClearText
+        {
+            get { return clearText; }
+            set
+            {
+                clearText = value;
+                if (clearButton != null) clearButton.Text = clearText;
+            }
+        }
+
+        private string title;
+
+        public string Title
+        {
+            get { return title; }
+            set
+            {
+                title = value;
+                if (titleText != null) titleText.Text = title.ToUpper();
+            }
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            RelativeSizeAxes = Axes.X;
+            AutoSizeAxes = Axes.Y;
+            Direction = FlowDirection.VerticalOnly;
+
+            Padding = new MarginPadding
+            {
+                Top = 10,
+                Bottom = 5,
+                Right = 20,
+                Left = 20,
+            };
+
+            AddInternal(new Drawable[]
+            {
+                new Container
+                {
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Children = new Drawable[]
+                    {
+                        clearButton = new ClearAllButton
+                        {
+                            Text = clearText,
+                            Anchor = Anchor.TopRight,
+                            Origin = Anchor.TopRight,
+                            Action = clearAll
+                        },
+                        new FlowContainer
+                        {
+                            Margin = new MarginPadding
+                            {
+                                Bottom = 5
+                            },
+                            Spacing = new Vector2(5, 0),
+                            AutoSizeAxes = Axes.Both,
+                            Children = new Drawable[]
+                            {
+                                titleText = new OsuSpriteText
+                                {
+                                    Text = title.ToUpper(),
+                                    Font = @"Exo2.0-Black",
+                                },
+                                countText = new OsuSpriteText
+                                {
+                                    Text = "3",
+                                    Colour = colours.Yellow,
+                                    Font = @"Exo2.0-Black",
+                                },
+                            }
+                        },
+                    },
+                },
+                notifications = new FlowContainer<Notification>
+                {
+                    AutoSizeAxes = Axes.Y,
+                    RelativeSizeAxes = Axes.X,
+                    LayoutDuration = 150,
+                    LayoutEasing = EasingTypes.OutQuart,
+                    Spacing = new Vector2(3),
+                }
+            });
+        }
+
+        private void clearAll()
+        {
+            notifications.Children.ForEach(c => c.Close());
+        }
+
+        protected override void Update()
+        {
+            base.Update();
+
+            countText.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString();
+        }
+
+        class ClearAllButton : ClickableContainer
+        {
+            private OsuSpriteText text;
+
+            public ClearAllButton()
+            {
+                AutoSizeAxes = Axes.Both;
+
+                Children = new[]
+                {
+                    text = new OsuSpriteText()
+                };
+            }
+
+            public string Text
+            {
+                get { return text.Text; }
+                set { text.Text = value.ToUpper(); }
+            }
+        }
+
+        public void MarkAllRead()
+        {
+            notifications.Children.ForEach(n => n.Read = true);
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs
new file mode 100644
index 0000000000..a5ec9a3545
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Graphics;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public class ProgressCompletionNotification : SimpleNotification
+    {
+        private ProgressNotification progressNotification;
+
+        public ProgressCompletionNotification(ProgressNotification progressNotification)
+        {
+            this.progressNotification = progressNotification;
+            Icon = FontAwesome.fa_check;
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs
new file mode 100644
index 0000000000..8b0fc27d36
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs
@@ -0,0 +1,231 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Diagnostics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Transformations;
+using osu.Game.Graphics;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public class ProgressNotification : Notification, IHasCompletionTarget
+    {
+        public string Text
+        {
+            get { return textDrawable.Text; }
+            set
+            {
+                textDrawable.Text = value;
+            }
+        }
+
+        public float Progress
+        {
+            get { return progressBar.Progress; }
+            set
+            {
+                progressBar.Progress = value;
+            }
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            //we may have received changes before we were displayed.
+            State = state;
+        }
+
+        public virtual ProgressNotificationState State
+        {
+            get { return state; }
+            set
+            {
+                bool stateChanged = state != value;
+                state = value;
+
+                if (IsLoaded)
+                {
+                    switch (state)
+                    {
+                        case ProgressNotificationState.Queued:
+                            Light.Colour = colourQueued;
+                            Light.Pulsate = false;
+                            progressBar.Active = false;
+                            break;
+                        case ProgressNotificationState.Active:
+                            Light.Colour = colourActive;
+                            Light.Pulsate = true;
+                            progressBar.Active = true;
+                            break;
+                        case ProgressNotificationState.Cancelled:
+                            Light.Colour = colourCancelled;
+                            Light.Pulsate = false;
+                            progressBar.Active = false;
+                            break;
+                    }
+                }
+
+                if (stateChanged)
+                {
+                    switch (state)
+                    {
+                        case ProgressNotificationState.Completed:
+                            NotificationContent.MoveToY(-DrawSize.Y / 2, 200, EasingTypes.OutQuint);
+                            FadeTo(0.01f, 200); //don't completely fade out or our scheduled task won't run.
+
+                            Delay(100);
+                            Schedule(Completed);
+                            break;
+                    }
+                }
+            }
+        }
+
+        private ProgressNotificationState state;
+
+        protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification(this)
+        {
+            Activated = CompletionClickAction,
+            Text = $"Task \"{Text}\" has completed!"
+        };
+
+        protected virtual void Completed()
+        {
+            Expire();
+            CompletionTarget?.Invoke(CreateCompletionNotification());
+        }
+
+        public override bool DisplayOnTop => false;
+
+        private ProgressBar progressBar;
+        private Color4 colourQueued;
+        private Color4 colourActive;
+        private Color4 colourCancelled;
+
+        private SpriteText textDrawable;
+
+        public ProgressNotification()
+        {
+            IconContent.Add(new Box
+            {
+                RelativeSizeAxes = Axes.Both,
+            });
+
+            Content.Add(textDrawable = new SpriteText
+            {
+                TextSize = 16,
+                Colour = OsuColour.Gray(128),
+                AutoSizeAxes = Axes.Y,
+                RelativeSizeAxes = Axes.X,
+            });
+
+            NotificationContent.Add(progressBar = new ProgressBar
+            {
+                Origin = Anchor.BottomLeft,
+                Anchor = Anchor.BottomLeft,
+                RelativeSizeAxes = Axes.X,
+            });
+
+            State = ProgressNotificationState.Queued;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            colourQueued = colours.YellowDark;
+            colourActive = colours.Blue;
+            colourCancelled = colours.Red;
+        }
+
+        public override void Close()
+        {
+            switch (State)
+            {
+                case ProgressNotificationState.Cancelled:
+                    base.Close();
+                    break;
+                case ProgressNotificationState.Active:
+                case ProgressNotificationState.Queued:
+                    State = ProgressNotificationState.Cancelled;
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// The function to post completion notifications back to.
+        /// </summary>
+        public Action<Notification> CompletionTarget { get; set; }
+
+        /// <summary>
+        /// An action to complete when the completion notification is clicked.
+        /// </summary>
+        public Func<bool> CompletionClickAction;
+
+        class ProgressBar : Container
+        {
+            private Box box;
+
+            private Color4 colourActive;
+            private Color4 colourInactive;
+
+            private float progress;
+            public float Progress
+            {
+                get { return progress; }
+                set
+                {
+                    if (progress == value) return;
+
+                    progress = value;
+                    box.ResizeTo(new Vector2(progress, 1), 100, EasingTypes.OutQuad);
+                }
+            }
+
+            private bool active;
+
+            public bool Active
+            {
+                get { return active; }
+                set
+                {
+                    active = value;
+                    FadeColour(active ? colourActive : colourInactive, 100);
+                }
+            }
+
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                colourActive = colours.Blue;
+                Colour = colourInactive = OsuColour.Gray(0.5f);
+
+                Height = 5;
+
+                Children = new[]
+                {
+                    box = new Box
+                    {
+                        RelativeSizeAxes = Axes.Both,
+                        Width = 0,
+                    }
+                };
+            }
+        }
+    }
+
+    public enum ProgressNotificationState
+    {
+        Queued,
+        Active,
+        Completed,
+        Cancelled
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs
new file mode 100644
index 0000000000..cf16978c3e
--- /dev/null
+++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs
@@ -0,0 +1,87 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+
+namespace osu.Game.Overlays.Notifications
+{
+    public class SimpleNotification : Notification
+    {
+        private string text;
+        public string Text
+        {
+            get { return text; }
+            set
+            {
+                text = value;
+                textDrawable.Text = text;
+            }
+        }
+
+        private FontAwesome icon = FontAwesome.fa_info_circle;
+        public FontAwesome Icon
+        {
+            get { return icon; }
+            set
+            {
+                icon = value;
+                iconDrawable.Icon = icon;
+            }
+        }
+
+        private SpriteText textDrawable;
+        private TextAwesome iconDrawable;
+
+        public SimpleNotification()
+        {
+            IconContent.Add(new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    ColourInfo = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.5f))
+                },
+                iconDrawable = new TextAwesome
+                {
+                    Anchor = Anchor.Centre,
+                    Icon = icon ,
+                }
+            });
+
+            Content.Add(textDrawable = new SpriteText
+            {
+                TextSize = 16,
+                Colour = OsuColour.Gray(128),
+                AutoSizeAxes = Axes.Y,
+                RelativeSizeAxes = Axes.X,
+                Text = text
+            });
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            Light.Colour = colours.Green;
+        }
+
+        public override bool Read
+        {
+            get
+            {
+                return base.Read;
+            }
+
+            set
+            {
+                if (base.Read = value)
+                    Light.FadeOut(100);
+                else
+                    Light.FadeIn(100);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Options/OptionDropDown.cs b/osu.Game/Overlays/Options/OptionDropDown.cs
index ee355d4016..b9d676109e 100644
--- a/osu.Game/Overlays/Options/OptionDropDown.cs
+++ b/osu.Game/Overlays/Options/OptionDropDown.cs
@@ -71,7 +71,14 @@ namespace osu.Game.Overlays.Options
             {
                 items = value;
                 if(dropdown != null)
+                {
                     dropdown.Items = value;
+
+                    // We need to refresh the dropdown because our items changed,
+                    // thus its selected value may be outdated.
+                    if (bindable != null)
+                        dropdown.SelectedValue = bindable.Value;
+                }
             }
         }
 
diff --git a/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs b/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs
index 370676ead6..06ddf584b2 100644
--- a/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs
+++ b/osu.Game/Overlays/Options/Sections/Audio/AudioDevicesOptions.cs
@@ -14,6 +14,7 @@ namespace osu.Game.Overlays.Options.Sections.Audio
         protected override string Header => "Devices";
 
         private AudioManager audio;
+        private OptionDropDown<string> dropdown;
 
         [BackgroundDependencyLoader]
         private void load(AudioManager audio)
@@ -21,21 +22,45 @@ namespace osu.Game.Overlays.Options.Sections.Audio
             this.audio = audio;
         }
 
+        protected override void Dispose(bool isDisposing)
+        {
+            base.Dispose(isDisposing);
+
+            audio.OnNewDevice -= onDeviceChanged;
+            audio.OnLostDevice -= onDeviceChanged;
+        }
+
+        private void updateItems()
+        {
+            var deviceItems = new List<KeyValuePair<string, string>>();
+            deviceItems.Add(new KeyValuePair<string, string>("Default", string.Empty));
+            deviceItems.AddRange(audio.AudioDeviceNames.Select(d => new KeyValuePair<string, string>(d, d)));
+
+            var preferredDeviceName = audio.AudioDevice.Value;
+            if (!deviceItems.Any(kv => kv.Value == preferredDeviceName))
+                deviceItems.Add(new KeyValuePair<string, string>(preferredDeviceName, preferredDeviceName));
+
+            dropdown.Items = deviceItems;
+        }
+
+        private void onDeviceChanged(string name) => updateItems();
+
         protected override void LoadComplete()
         {
             base.LoadComplete();
 
-            var deviceItems = new List<KeyValuePair<string, string>>();
-            deviceItems.Add(new KeyValuePair<string, string>("Default", string.Empty));
-            deviceItems.AddRange(audio.GetDeviceNames().Select(d => new KeyValuePair<string, string>(d, d)));
             Children = new Drawable[]
             {
-                new OptionDropDown<string>()
+                dropdown = new OptionDropDown<string>()
                 {
-                    Items = deviceItems,
                     Bindable = audio.AudioDevice
                 },
             };
+
+            updateItems();
+
+            audio.OnNewDevice += onDeviceChanged;
+            audio.OnLostDevice += onDeviceChanged;
         }
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Overlays/OptionsOverlay.cs b/osu.Game/Overlays/OptionsOverlay.cs
index 1fce450104..a93bbc10d3 100644
--- a/osu.Game/Overlays/OptionsOverlay.cs
+++ b/osu.Game/Overlays/OptionsOverlay.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Overlays
                                 {
                                     Text = "settings",
                                     TextSize = 40,
-                                    Margin = new MarginPadding { Left = CONTENT_MARGINS, Top = 30 },
+                                    Margin = new MarginPadding { Left = CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT },
                                 },
                                 new OsuSpriteText
                                 {
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 503a3e0bf5..dc8b8d1e9a 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Overlays.Toolbar
     public class Toolbar : OverlayContainer
     {
         public const float HEIGHT = 40;
+        public const float TOOLTIP_HEIGHT = 30;
 
         public Action OnHome;
         public Action<PlayMode> OnPlayModeChange;
@@ -73,10 +74,7 @@ namespace osu.Game.Overlays.Toolbar
                             Icon = FontAwesome.fa_search
                         },
                         userArea = new ToolbarUserArea(),
-                        new ToolbarButton
-                        {
-                            Icon = FontAwesome.fa_bars
-                        },
+                        new ToolbarNotificationButton(),
                     }
                 }
             };
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 160e4460d9..67b0039971 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -56,6 +56,8 @@ namespace osu.Game.Overlays.Toolbar
             }
         }
 
+        protected virtual Anchor TooltipAnchor => Anchor.TopLeft;
+
         public Action Action;
         protected TextAwesome DrawableIcon;
         protected SpriteText DrawableText;
@@ -107,19 +109,24 @@ namespace osu.Game.Overlays.Toolbar
                 {
                     Direction = FlowDirection.VerticalOnly,
                     RelativeSizeAxes = Axes.Both, //stops us being considered in parent's autosize
-                    Anchor = Anchor.BottomLeft,
-                    Position = new Vector2(5, 5),
+                    Anchor = (TooltipAnchor & Anchor.x0) > 0 ? Anchor.BottomLeft : Anchor.BottomRight,
+                    Origin = TooltipAnchor,
+                    Position = new Vector2((TooltipAnchor & Anchor.x0) > 0 ? 5 : -5, 5),
                     Alpha = 0,
                     Children = new[]
                     {
                         tooltip1 = new OsuSpriteText
                         {
+                            Anchor = TooltipAnchor,
+                            Origin = TooltipAnchor,
                             Shadow = true,
                             TextSize = 22,
                             Font = @"Exo2.0-Bold",
                         },
                         tooltip2 = new OsuSpriteText
                         {
+                            Anchor = TooltipAnchor,
+                            Origin = TooltipAnchor,
                             Shadow = true,
                             TextSize = 16
                         }
diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs
new file mode 100644
index 0000000000..973f9f2d8a
--- /dev/null
+++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Overlays.Toolbar
+{
+    class ToolbarNotificationButton : ToolbarOverlayToggleButton
+    {
+        protected override Anchor TooltipAnchor => Anchor.TopRight;
+
+        public ToolbarNotificationButton()
+        {
+            Icon = FontAwesome.fa_bars;
+            TooltipMain = "Notifications";
+            TooltipSub = "Waiting for 'ya";
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(NotificationManager notificationManager)
+        {
+            StateContainer = notificationManager;
+            Action = notificationManager.ToggleVisibility;
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs
index 61b3a89ca0..65b50542ce 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Screens.Backgrounds
             [BackgroundDependencyLoader]
             private void load()
             {
-                Sprite.Texture = beatmap.Background;
+                Sprite.Texture = beatmap?.Background;
             }
         }
     }
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index a76cdbe99b..6d720569df 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -119,11 +119,11 @@ namespace osu.Game.Screens.Menu
             buttonFlow.Add(buttonsTopLevel);
         }
 
-        [BackgroundDependencyLoader]
-        private void load(AudioManager audio, OsuGame game)
+        [BackgroundDependencyLoader(true)]
+        private void load(AudioManager audio, OsuGame game = null)
         {
             sampleOsuClick = audio.Sample.Get(@"Menu/menuhit");
-            toolbar = game.Toolbar;
+            toolbar = game?.Toolbar;
         }
 
         protected override void LoadComplete()
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 2ead6aad48..4cbc229596 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,6 +23,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -31,6 +32,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
+    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -100,6 +102,13 @@
     <Compile Include="Beatmaps\Timing\SampleChange.cs" />
     <Compile Include="Beatmaps\Timing\TimingChange.cs" />
     <Compile Include="Configuration\OsuConfigManager.cs" />
+    <Compile Include="Overlays\Notifications\IHasCompletionTarget.cs" />
+    <Compile Include="Overlays\Notifications\Notification.cs" />
+    <Compile Include="Overlays\NotificationManager.cs" />
+    <Compile Include="Overlays\Notifications\NotificationSection.cs" />
+    <Compile Include="Overlays\Notifications\ProgressCompletionNotification.cs" />
+    <Compile Include="Overlays\Notifications\ProgressNotification.cs" />
+    <Compile Include="Overlays\Notifications\SimpleNotification.cs" />
     <Compile Include="Overlays\Options\OptionDropDown.cs" />
     <Compile Include="Overlays\Options\OptionLabel.cs" />
     <Compile Include="Graphics\UserInterface\OsuDropDownHeader.cs" />
@@ -107,6 +116,7 @@
     <Compile Include="Graphics\UserInterface\OsuDropDownMenuItem.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarHomeButton.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarMusicButton.cs" />
+    <Compile Include="Overlays\Toolbar\ToolbarNotificationButton.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarSettingsButton.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarOverlayToggleButton.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarUserArea.cs" />
diff --git a/osu.sln b/osu.sln
index f3736c16c0..588cabf6b6 100644
--- a/osu.sln
+++ b/osu.sln
@@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Taiko", "osu
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Mania", "osu.Game.Modes.Mania\osu.Game.Modes.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop.Tests", "osu.Desktop.Tests\osu.Desktop.Tests.csproj", "{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -79,6 +81,10 @@ Global
 		{48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU
+		{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -95,6 +101,7 @@ Global
 		{58F6C80C-1253-4A0E-A465-B8C85EBEADF3} = {0D37A2AD-80A4-464F-A1DE-1560B70F1CE3}
 		{F167E17A-7DE6-4AF5-B920-A5112296C695} = {0D37A2AD-80A4-464F-A1DE-1560B70F1CE3}
 		{48F4582B-7687-4621-9CBE-5C24197CB536} = {0D37A2AD-80A4-464F-A1DE-1560B70F1CE3}
+		{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D} = {0D37A2AD-80A4-464F-A1DE-1560B70F1CE3}
 	EndGlobalSection
 	GlobalSection(MonoDevelopProperties) = preSolution
 		Policies = $0