Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / Visual Basic
Alternative
Article

Powerful x86/x64 Mini Hook-Engine

Rate me:
Please Sign up or sign in to vote.
4.56/5 (5 votes)
8 Mar 2013CPOL3 min read 47.5K   670   17   37
This is an alternative for "Powerful x86/x64 Mini Hook-Engine"

Introduction

This code is both in VB.NET and C#. It allows developers who work exclusively with C# or VB.NET to fully implement the brilliant work of Daniel Pistelli's NtHookEngine.dll

It is an extremely powerful way to implement hooks or "detours" because the only source code you need to work with is VB.Net or C#. It does not require C++ or any unmanaged coding languages to implement them. I recommend reading Daniel Pistelli's original article if you wish to see just how brilliant his engine is designed and built. This code should help people utilize the powerful engine with obsurd effeciency.

Background

Working with C++ is like driving a classic muscle car that turns everyone's head. Of course, without a modern suspension system, air conditioner or any modern safety features. Sure I can take C++ out for a spin which would be never forgotten, but my back would ache, my glands would sweat, and I might not make it without crashing. 

 Using the Code   

Installation/Setup:

     Required Prerequisite: Make sure NtHookEngine.dll is in a folder on your path or the folder containing your compiled executable.  Optional: If you would like the source or more details about NtHookEngine then search for terms like (NtHookEngine "Mini Hook-Engine" By "Daniel Pistelli") April of 2008 visit the link to the original article. 

  1. The first thing note worthy is NtDetourEngine classes do not require and are not intended to be instantiated.  In other words, their variables and methods are constants/shared which makes them independent from any instance of the object they define.  In simpler words, you do not have to Create an instance of the object via calling the constructor in order to implement the Detours.
  2.  The detour method can also be a shared method, so you do not have to instantiate an object to implement the detour.  As one can see in the example, no objects are instantiated to fully implement a detour. 
  3. The class structure (as in the example) Win32.MessageBoxTimeoutW automatically uses dot net reflection information to set up the invoke method for MessageBoxTimeoutW in the Win32.dll ..........(reduces typing and duplicate source code)
  4. marshalled_Delegate is the only place marshal definitions are required..(reduces typing and duplicate source code) 
  5. Installing a detour/hook is a simple (as in the example) call to set the detour method to point to the "function address" for the detour. <span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;"> VB Example Code: </span>ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = AddressOf MyDetours.MyMessageBoxTimeoutDetour C# Example Code: ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = MyDetours.MyMessageBoxTimeoutDetour;
  6. Uninstalling a detour/hook is a simple (as in the example) call to set the the detour method to NULL/Nothing<span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;"> VB example: </span>ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = Nothing C# example: ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = null;
  7. Invoke is exactly as if you used Dllimport to declare the native method for the sake of making calls to it. The reason I implement Invoke in a class and not via the use of Dllimport is due to working with NtHookEngine and in the interest of (reducing typing and duplicate source code) especially marshaling; lets marshal once. 

QUICKSTART TO IMPLEMENT YOUR OWN DETOUR:

  1. Make sure NtHookEngine.dll is in a folder on your path or the folder containing your compiled executable.
  2. copy and rename: Public Class MessageBoxTimeoutW
  3. edit marshalled_Delegate for your new class for the parameters and types the call to the native dll expects 
  4. edit callback_Delegate for your new parameter names (no marshal attributes, use ByRef (VB.Net) ref (C#) so the detour can edit parameters) 
  5. debug your new detour/hook   

     Installing a detour/hook is a simple (as in the example) call to set the detour method to point to the "function address" for the detour.  

C#
ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = MyDetours.MyMessageBoxTimeoutDetour; 
VB.NET
ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = AddressOf MyDetours.MyMessageBoxTimeoutDetour  

Uninstaling a detour/hook is a simple (as in the example) call to set the the detour method to NULL/Nothing.

C#
ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = null; 
VB.NET
ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = Nothing 

     Notice how the code uses; <span style="color: black; font-size: 9pt; white-space: pre;"><code>System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate to obtain a pointer to a method.

      I will update the article to be more helpful based on comments so please comment if you have ideas but please be as specific as you can. 

The code in detail:

C#
public class ntDetourEngine
{
    public delegate int GenericDelegate(params object[] VarArg);
    [System.Runtime.InteropServices.DllImport("NtHookEngine.dll", 
      SetLastError = true, EntryPoint = "HookFunction")]

    public extern static int SetDetourPtr(System.IntPtr native_ptr, System.IntPtr detour_ptr);

    [System.Runtime.InteropServices.DllImport("NtHookEngine.dll", 
      SetLastError = true, EntryPoint = "UnhookFunction")]

    public extern static int ResetDetourPtr(System.IntPtr native_ptr);

    [System.Runtime.InteropServices.DllImport("NtHookEngine.dll", 
      SetLastError = true, EntryPoint = "GetOriginalFunction")]

    public extern static System.IntPtr GetOriginalMethodPtr(System.IntPtr detour_ptr);

    [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]

    [return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]

    internal static extern bool FreeLibrary(System.IntPtr hModule);

    [System.Runtime.InteropServices.DllImport("kernel32.dll", 
      CharSet = System.Runtime.InteropServices.CharSet.Unicode, SetLastError = true)]

    internal static extern System.IntPtr LoadLibraryW([System.Runtime.InteropServices.MarshalAs(
      System.Runtime.InteropServices.UnmanagedType.LPWStr)] string lpFileName);

    [System.Runtime.InteropServices.DllImport("Kernel32", EntryPoint = 
      "GetProcAddress", CharSet = System.Runtime.InteropServices.CharSet.Ansi, 
      CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall, SetLastError = true)]

    public extern static System.IntPtr GetProcAddress(System.IntPtr handle, 
      [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]string funcname);

    private static System.Collections.SortedList libraryHandles = new System.Collections.SortedList();

    private static System.Collections.SortedList methodHandles = new System.Collections.SortedList();

    private class SafeLibraryHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid
    {

        public SafeLibraryHandle(string libraryName)
            : base(true)
        {

            if (!libraryName.Equals(""))

                base.SetHandle(LoadLibraryW(libraryName));

        }

        protected override bool ReleaseHandle()
        {

            bool result = false;

            if (((!base.IsInvalid) && (!base.IsClosed)))
            {

                result = FreeLibrary(base.handle);

            }

            return result;

        }

    }

    private static System.IntPtr methodHandle(string libraryName, string methodName)
    {
        System.IntPtr result = System.IntPtr.Zero;
        System.IntPtr l = libraryHandle(libraryName);

        if (!l.Equals(System.IntPtr.Zero))
        {
            result = GetProcAddress(l, methodName);
        }
        return result;
    }



    private static System.IntPtr libraryHandle(string libraryName)
    {
        System.IntPtr result = System.IntPtr.Zero;
        string k = libraryName.ToUpper();
        if (!libraryHandles.ContainsKey(k))
        {
            SafeLibraryHandle l = new SafeLibraryHandle(libraryName);
            libraryHandles.Add(k, l);
        }

        if (libraryHandles.ContainsKey(k))
        {
            object o = libraryHandles[k];
            if (o is SafeLibraryHandle)
            {
                result = ((SafeLibraryHandle)o).DangerousGetHandle();
            }
        }
        return result;
    }

    public class Kernel
    {

        private static string libraryName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name + ".dll";

    }

    public class User32
    {

        private static string libraryName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name + ".dll";

        public class MessageBoxTimeoutW
        {

            public delegate bool callback_Delegate(object Sender, ref System.IntPtr hWnd, ref string lpText, 
              ref string lpCaption, ref int uType, ref ushort wLanguageId, ref int dwMilliseconds);

            private delegate int marshalled_Delegate(System.IntPtr hWnd, [System.Runtime.InteropServices.MarshalAs(
              System.Runtime.InteropServices.UnmanagedType.LPWStr)]string lpText, 
              [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
              string lpCaption, [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)]int 
              uType, [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U2)]ushort 
              wLanguageId, [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)]int dwMilliseconds);

            private static string methodName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;

            private static System.IntPtr methodPtr = methodHandle(libraryName, methodName);
            //private static System.IntPtr methodPtrInvoke = 
            //  System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(methodDelegate);

            private static System.Delegate methodDelegate = new marshalled_Delegate(Invoke);

            private static System.Delegate detourDelegate = new marshalled_Delegate(Detour);

            private static System.IntPtr detourPtrInvoke = 
              System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(detourDelegate);

            private static callback_Delegate fDetour = null;

            public static int Invoke(System.IntPtr hWnd, string lpText, string lpCaption, 
              int uType, ushort wLanguageId, int dwMilliseconds)
            {
                int result = 0;
                marshalled_Delegate MethodToInvoke;

                if (!(methodPtr.Equals(System.IntPtr.Zero)))
                {
                    MethodToInvoke = (marshalled_Delegate)System.Runtime.InteropServices.Marshal.
                      GetDelegateForFunctionPointer(methodPtr, typeof(marshalled_Delegate));
                    result = MethodToInvoke(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds);
                }
                return result;
            }

            private static int Detour(System.IntPtr hWnd, string lpText, _
              string lpCaption, int uType, ushort wLanguageId, int dwMilliseconds)
            {
                int result = 0;

                bool fSuppress = false;

                if ((((fDetour != null)) && (fDetour is callback_Delegate)))
                {
                    fDetour((object)System.Reflection.MethodBase.GetCurrentMethod(), ref hWnd, 
                      ref lpText, ref lpCaption, ref uType, ref wLanguageId, ref dwMilliseconds);

                    if (!fSuppress)
                    {
                        marshalled_Delegate MethodToInvoke;
                        MethodToInvoke = (marshalled_Delegate)_
                          System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(
                          GetOriginalMethodPtr(detourPtrInvoke), typeof(marshalled_Delegate));
                        result = MethodToInvoke(hWnd, lpText, lpCaption, uType, wLanguageId, dwMilliseconds);
                    }
                }
                return result;
            }

            public static callback_Delegate DetourMethod
            {
                get { return fDetour; }

                set
                {
                    if ((((fDetour != null)) || (value == null)))
                    {
                        ResetDetourPtr(methodPtr);
                    }

                    fDetour = value;

                    if ((fDetour != null))
                    {
                        SetDetourPtr(methodPtr, detourPtrInvoke);
                    }
                }
            }
        }
    }
}

public class MyDetours
{
    public static bool MyMessageBoxTimeoutDetour(object Sender, ref System.IntPtr hWnd, 
      ref string lpText, ref string lpCaption, ref int uType, ref ushort wLanguageId, ref int dwMilliseconds)
    {
        lpText = "My Detour Changed Text!!! " + (char)13 + (char)10 + 
          "The original text was:" + (char)13 + (char)10 + lpText;

        return false;
    }
}

namespace ConsoleApplicationNtHookEngine
{
    class Program
    {
        static void Main(string[] args)
        {
            {
                ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(System.IntPtr.Zero, 
                  "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0);

                ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = MyDetours.MyMessageBoxTimeoutDetour; //Turning on the detour/hook

                ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(System.IntPtr.Zero, 
                  "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0);

                ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = null; //Turning off the detour/hook

                ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(System.IntPtr.Zero, 
                  "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0);

                System.Console.ReadLine();
            }
        }
    }
}
VB.NET
Public Class ntDetourEngine

    Public Delegate Function GenericDelegate(ByRef VarArg() As Object) As Boolean

    Private Declare Unicode Function SetDetourPtr Lib "NtHookEngine.dll" _
      Alias "HookFunction" (ByVal native_ptr As IntPtr, ByVal detour_pointer As IntPtr) As Integer

    Private Declare Unicode Function ResetDetourPtr Lib "NtHookEngine.dll" _
      Alias "UnhookFunction" (ByVal native_ptr As IntPtr) As Integer

    Private Declare Unicode Function GetOriginalMethodPtr Lib "NtHookEngine.dll" _
      Alias "GetOriginalFunction" (ByVal detour_ptr As IntPtr) As IntPtr

    Private Declare Unicode Function LoadLibraryW Lib "Kernel32.dll" _
      (ByVal libFileName As String) As IntPtr ' Note that LoadLibrary returns 0 on failure

    Private Declare Ansi Function GetProcAddress Lib "Kernel32.dll" (ByVal hModule As IntPtr, ByVal procname As String) As IntPtr

    Private Declare Auto Function FreeLibrary Lib "Kernel32.dll" (ByVal hModule As IntPtr) _
      As <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)> Boolean

    Private Shared libraryHandles As New System.Collections.SortedList

    Private Shared methodHandles As New System.Collections.SortedList

    Private Class SafeLibraryHandle

        Inherits Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid

        Public Sub New(ByVal libraryName As String)

            MyBase.New(True)

            If Not libraryName.Equals("") Then MyBase.SetHandle(LoadLibraryW(libraryName))

        End Sub

        Protected Overrides Function ReleaseHandle() As Boolean

            Dim result As Boolean = False

            If ((Not MyBase.IsInvalid) AndAlso (Not MyBase.IsClosed)) Then

                result = FreeLibrary(MyBase.handle)

            End If

            Return result

        End Function

    End Class

    Private Shared Function methodHandle(ByVal libraryName As String, ByVal methodName As String) As IntPtr

        Dim result As IntPtr = IntPtr.Zero

        Dim l As IntPtr = libraryHandle(libraryName)

        If Not l.Equals(IntPtr.Zero) Then

            result = GetProcAddress(l, methodName)

        End If

        Return result

    End Function

    Private Shared ReadOnly Property libraryHandle(ByVal libraryName As String) As IntPtr

        Get

            Dim result As IntPtr = IntPtr.Zero

            Dim k As String = libraryName.ToUpper

            If Not libraryHandles.ContainsKey(k) Then

                Dim l As New SafeLibraryHandle(libraryName)

                libraryHandles.Add(k, l)

            End If

            If libraryHandles.ContainsKey(k) Then

                Dim o As Object = libraryHandles(k)

                If TypeOf o Is SafeLibraryHandle Then

                    result = CType(o, SafeLibraryHandle).DangerousGetHandle

                End If

            End If

            Return result

        End Get

    End Property

    Public Class [Kernel]

        Private Shared libraryName As String = _
          System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name & ".dll"

    End Class

    Public Class [User32]

        Private Shared libraryName As String = _
          System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name & ".dll"

        Public Class MessageBoxTimeoutW

            Public Delegate Function callback_Delegate(ByVal Sender As System.Reflection.MethodBase, _
              ByRef hWnd As IntPtr, ByRef lpText As String, ByRef lpCaption As String, _
              ByRef uType As Integer, ByRef wLanguageId As UInt16, ByRef dwMilliseconds As Integer) As Boolean

            Private Delegate Function marshalled_Delegate(ByVal hWnd As IntPtr, _
              <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
              ByVal lpText As String, <System.Runtime.InteropServices.MarshalAs(_
              System.Runtime.InteropServices.UnmanagedType.LPWStr)> ByVal lpCaption As String, _
              <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)> _
              ByVal uType As Integer, <System.Runtime.InteropServices.MarshalAs(_
              System.Runtime.InteropServices.UnmanagedType.U2)> ByVal wLanguageId As UInt16, _
              <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)> _
              ByVal dwMilliseconds As Integer) As Integer

            Private Shared methodName As String = _
              System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name 'Me.GetType.Name '"MessageBoxTimeOutW"

            Private Shared methodPtr As IntPtr = methodHandle(libraryName, methodName)

            Private Shared methodDelegate As [Delegate] = CType(New marshalled_Delegate(AddressOf Invoke), [Delegate])
            'Private Shared methodPtrInvoke As IntPtr = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(methodDelegate)

            Private Shared detourDelegate As [Delegate] = CType(New marshalled_Delegate(AddressOf Detour), [Delegate])

            Private Shared detourPtrInvoke As IntPtr = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(detourDelegate)

            Private Shared fDetour As callback_Delegate = Nothing

            Public Shared Function Invoke(ByVal ParamArray VarArg() As Object) As Integer

                Return CType(System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(methodPtr, _
                  GetType(marshalled_Delegate)), marshalled_Delegate)(VarArg(0), _
                  VarArg(1), VarArg(2), VarArg(3), VarArg(4), VarArg(5))

            End Function

            Private Shared Function Detour(ByVal ParamArray VarArg() As Object)

                Dim fSuppress As Boolean = False

                If ((Not fDetour Is Nothing) AndAlso (TypeOf fDetour Is callback_Delegate)) Then
                    fSuppress = fDetour(System.Reflection.MethodBase.GetCurrentMethod(), _
                      VarArg(0), VarArg(1), VarArg(2), VarArg(3), VarArg(4), VarArg(5))
                End If

                Dim result As Integer = 0

                If Not fSuppress Then result = CType(System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(_
                  GetOriginalMethodPtr(detourPtrInvoke), GetType(marshalled_Delegate)), _
                  marshalled_Delegate)(VarArg(0), VarArg(1), VarArg(2), VarArg(3), VarArg(4), VarArg(5))

                Return result

            End Function

            Public Shared Property DetourMethod() As callback_Delegate

                Get
                    Return fDetour
                End Get

                Set(ByVal value As callback_Delegate)
                    If ((Not fDetour Is Nothing) OrElse (value Is Nothing)) Then
                        ResetDetourPtr(methodPtr)
                    End If

                    fDetour = value

                    If Not fDetour Is Nothing Then
                        SetDetourPtr(methodPtr, detourPtrInvoke)
                    End If
                End Set
            End Property
        End Class
    End Class
End Class

Public Class MyDetours

    Public Shared Function MyMessageBoxTimeoutDetour(ByVal Sender As System.Reflection.MethodBase, _
      ByRef hWnd As IntPtr, ByRef lpText As String, ByRef lpCaption As String, _
      ByRef uType As Integer, ByRef wLanguageId As UInt16, ByRef dwMilliseconds As Integer) As Boolean

        lpText = "My Detour Changed Text!!! " & Chr(13) & Chr(10) & Sender.ReflectedType.Name & Chr(13) & _
          Chr(10) & "The original text was:" & Chr(13) & Chr(10) & lpText

        Return False

    End Function

End Class

Module Module1

    Sub Main()

        ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(IntPtr.Zero, _
          "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0)

        ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = _
          AddressOf MyDetours.MyMessageBoxTimeoutDetour 'Turning on the detour/hook

        ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(IntPtr.Zero, _
          "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0)

        ntDetourEngine.User32.MessageBoxTimeoutW.DetourMethod = Nothing 'Turning off the detour/hook

        ntDetourEngine.User32.MessageBoxTimeoutW.Invoke(IntPtr.Zero, _
          "Invoking the original MessageBoxTimeoutW", "This is the title.", 64, 0, 0)

        Console.ReadLine()

    End Sub

End Module 

 

 

 

 

 

 

 


 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: My vote of 1 Pin
Michael Haephrati8-Mar-13 6:03
professionalMichael Haephrati8-Mar-13 6:03 
GeneralRe: My vote of 1 Pin
joejames1118-Mar-13 6:23
joejames1118-Mar-13 6:23 
GeneralRe: My vote of 1 Pin
Michael Haephrati8-Mar-13 6:25
professionalMichael Haephrati8-Mar-13 6:25 
GeneralRe: My vote of 1 Pin
joejames1118-Mar-13 6:51
joejames1118-Mar-13 6:51 
GeneralRe: My vote of 1 Pin
Michael Haephrati8-Mar-13 6:56
professionalMichael Haephrati8-Mar-13 6:56 
GeneralRe: My vote of 1 Pin
joejames1118-Mar-13 9:12
joejames1118-Mar-13 9:12 
SuggestionNeed explanation Pin
thatraja4-May-12 20:34
professionalthatraja4-May-12 20:34 
GeneralRe: Need explanation Pin
joejames1112-Mar-13 15:59
joejames1112-Mar-13 15:59 
I have explained the code to the best of my ability. Please ask specific questions and I will answer them directly.
QuestionThis code is both in VB.Net and C#. Pin
Dewey26-Apr-12 10:15
Dewey26-Apr-12 10:15 
AnswerRe: This code is both in VB.Net and C#. Pin
joejames11126-Apr-12 14:34
joejames11126-Apr-12 14:34 
GeneralRe: This code is both in VB.Net and C#. Pin
Dewey27-Apr-12 13:48
Dewey27-Apr-12 13:48 
GeneralRe: This code is both in VB.Net and C#. Pin
joejames1112-Mar-13 15:58
joejames1112-Mar-13 15:58 
QuestionDownloadable code? Pin
Brisingr Aerowing26-Apr-12 8:04
professionalBrisingr Aerowing26-Apr-12 8:04 
AnswerRe: Downloadable code? Pin
joejames11126-Apr-12 14:31
joejames11126-Apr-12 14:31 
GeneralRe: Downloadable code? Pin
sabsoft1231-Aug-12 23:43
sabsoft1231-Aug-12 23:43 
GeneralRe: Downloadable code? Pin
joejames1118-Mar-13 6:28
joejames1118-Mar-13 6:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.