Introduction
This tool allows you to display an overlay (which is over everything, including DirectX and your desktop (which means you can't make a screenshot of it directly)). By changing various settings, you can disable and enable several plugins and change their behavior.
Developers may create their own plugins to display their own stuff. Integrated in this tool are only some general plugins or plugins that I use.
Note: nVidia hardware only supports fragments of the DirectDraw overlay technique - Alpha won't work there (trying to set it will cause an error). If you have a nvidia card, I recommend using a smaller overlay with a light grey background color, or try using the FakeAlpha option instead, which will Blit the screen below it on the overlay. But please keep in mind that this is quite slow and needs a high render rate to be OK - This option is probably more useful if you put the overlay in a place where the background doesn't change often (e.g. over a GUI part).
Usage
Just start the OverlayTools.exe, and double click on the gear-tray-icon to display the options. Put your own settings there.
Standard Shortcuts:
- F12 - Hide/Show
- F11 - Switch overlay page
Background
I always wanted to see what others write to me in ICQ and mIRC while playing some fullscreen applications. It was annoying to hear the message-sound, but not know what was written. As overlays are a rather difficult and not a supported part of windows/DirectX, I tried creating something useful several times.
DirectDraw Overlay
s always had some problems with nVidia hardware, as nVidia hardware only supports YUV Overlay
s on which you cannot draw using GDI or anything else. Therefore I created my own blitting function for converting a RGB Bitmap (which works on every hardware) to a YUV surface.
1. Overlay Lib
1.1. Overlay Class
The overlay
library offers the basic functions of displaying an overlay
. To use it, just create an instance of the Overlay
Class, set your Size and Position, Add your RenderDelegate
, Initialise
and Update()
the overlay
.
The first big problem here was the case of finding a surface pixelformat which would work on every hardware. ATI cards support mostly RGB, but also YUV; nVidia hardware only supports YUV. But as you cannot draw on YUV surfaces, it's quite useless. So I just try to find out which format works on the card and then use it.
do
{
try
{
desc.PixelFormatStructure = GetPixelFormat(m_PixelFormat);
m_Buffer = new Surface(desc, m_Device);
}
catch (DirectXException)
{
m_PixelFormat++;
}
}
while (m_Buffer == null
&& Enum.IsDefined(typeof(ePixelFormat), m_PixelFormat));
For rendering, I used a simple Bitmap as a render target as this would work everywhere, and then just blit it on the specific surface.
if (m_Renderer != null)
{
Graphics g = Graphics.FromImage(m_RenderTarget);
g.Clear(Color.Black);
m_Renderer(g);
Blit(m_RenderTarget, m_BackBuffer);
}
The blitting function just calls the sub-blitting function for the appropriate format:
public void Blit(Bitmap src, Surface dest)
{
switch (m_PixelFormat)
{
case ePixelFormat.RGB32:
BlitRGB32(src, dest);
break;
case ePixelFormat.YUY2:
BlitYUY2(src, dest);
break;
}
}
The general way of blitting something from the source to the target is locking both data, then proceeding through it pixel by pixel (two-pixel by two-pixel in the YUY2 case)
BitmapData ds = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
LockedData dd = dest.Lock(LockFlags.WriteOnly);
int ps = ds.Stride - (ds.Width*3);
byte[] pd = new byte[dd.Pitch - (dd.Width*2)];
byte* ptr = (byte*) ds.Scan0;
for (int h = 0; h < ds.Height; h++)
{
for (int w = 0; w < ds.Width; w += 2)
{
...
ptr += 3;
...
}
ptr += ps;
pd.Write(pd, 0, pd.Length);
}
...
For RGB24 -> RGB32
blitting, I just lock the bitmap and copy the values, leaving alpha empty (as it is not available anyway, but a 24-bit surface won't create).
For RGB24 -> YUY2
blitting, I looked up the format on fourcc.org and tried to convert it properly. But as these formulas are not fully correct and I had to modify them a bit myself -the resulting colors may not be 100% identical.
1.2. KeyboardHook Class
For the shortcuts, I used the Win32 SetWindowHookEx
method to hook up the WH_KEYBOARD_LL
event. More information about that is available in the Windows Platform SDK.
...
hk = SetWindowsHookEx(13, hookprc, Marshal.GetHINSTANCE(GetType().Module), 0);
...
public IntPtr OnHook(int code, int wParam, int lParam)
{
IntPtr res = CallNextHookEx(hook, code, wParam, lParam);
if (code >= 0 && m_Event != null && wParam == WM_KEYDOWN)
{
KeyStruct s = (KeyStruct) Marshal.PtrToStructure(
new IntPtr(lParam), typeof (KeyStruct));
Keys key = (Keys) s.vkCode;
m_Event(key);
}
return res;
}
...
2. Overlay Tools
For displaying the custom overlays, I wrote this application. It handles the plugin management. Each plugin can render its own part to the overlay, so that in the end you get your fully customised overlay.
2.1. Loading the Plugins
The plugins are loaded via reflection. The code is searched and an instance is created for every inheritor of IPlugin
.
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in a.GetTypes())
{
if (t.IsAbstract || t.IsInterface
|| t.GetInterface(typeof (IPlugin).FullName) == null)
{
continue;
}
IPlugin p = (IPlugin) a.CreateInstance(t.FullName);
...
}
}
Every plugin may contain data properties for saving custom values (e.g. Colors, Formats, ..). These values are signed by the [Save]
Attribute, which is processed after creating the instance of the plugins. If a property possesses this attribute, its content is loaded from the .ini (using INIStreamer
, one of my oldest C# classes) and then the string is converted to the appropriate type:
foreach (PropertyInfo i in t.GetProperties())
{
if (i.GetCustomAttributes(typeof (SaveAttribute), true).Length == 0)
{
continue;
}
if (!tpc.Items.ContainsKey(i.Name))
{
continue;
}
object val;
if (i.PropertyType.IsEnum)
{
val = Enum.Parse(i.PropertyType, tpc.Items[i.Name].ToString());
}
else
{
val = Convert.ChangeType(tpc.Items[i.Name], i.PropertyType);
}
i.SetValue(p, val, null);
}
Nearly the same is done when saving the plugin data into the .ini file.
After loading the plugins, an options tab is generated for each plugin by adding the PluginPanel
control (just a control with a propertygrid
in fact) to the tab control.
private static void AddToTab(IPlugin p)
{
TabPage page = new TabPage();
page.Text = p.Name;
page.Name = "tab_plugin_" + p.BaseName;
PluginPanel pp = new PluginPanel(p);
pp.Dock = DockStyle.Fill;
page.Controls.Add(pp);
MainForm.tabs.TabPages.Add(page);
}
2.2. Managing the Plugins
All plugins have a OnTick()
and OnRender(Graphics g)
method. They are called by a timer for every active plugin, and so every plugin has a chance to fulfill its job. For the rendering, a TranslateTransform()
Matrix-Operation is applied, so that the plugins can begin their rendering at (0|0).
public static void OnRender(Graphics g)
{
g.Clear(General.BackgroundColor);
if (General.FakeAlpha)
{
g.CopyFromScreen(Overlay.Boundings.Location, Point.Empty,
Overlay.Boundings.Size, CopyPixelOperation.SourceCopy);
}
foreach (IPlugin p in Plugins.Values)
{
if (p.Active && (p.Page == General.Page || p.Page == -1))
{
g.ResetTransform();
g.TranslateTransform(p.X, p.Y, MatrixOrder.Append);
p.Render(g);
}
}
}
The plugins then just draw whatever they want (and maybe they use the Draw helper class
, which has some fonts and draw strings with a shadow).
The general data like Position
and Size
of the overlay are also stored in a plugin: The General Plugin stores all important data in a fake/null-plugin (it has only an empty onTick
/onRender
method), which is saved in the same way normal plugins are saved.
2.3. List of Plugins
These are the plugins currently added to the program:
- Time Plugin - Displays the time
- CPU/Memory Plugins - Display the CPU Usage/Free memory
- Color Test Plugin - Just displays some different colors
- Message Plugin - Listens to an UDP Socket and displays messages if received
- ATITool Plugin - Reads the ATI Tool log file and displays the GPU Temperature
..and some smaller plugins which are not worth mentioning.
Creating Custom Plugins
If you want to create your own plugin, just add your class to the project file and put your stuff there. It's quite simple. I haven't added dynamic compilation/loading plugin assemblies as I think there is no need for this, but feel free to do so.