Click here to Skip to main content
13,768,005 members
Click here to Skip to main content
Add your own
alternative version


2 bookmarked
Posted 5 Aug 2018
Licenced CPOL

Omega Red PS2 emulator

, 8 Oct 2018
Rate this:
Please Sign up or sign in to vote.
Clone of PCSX2 emulator for Windows 10 on WPF/C# with "touch" control.


This article presents my new project for cloning and research of PCSX2 PlayStation 2 emulator for Windows 10 on WPF/C#. Purpose of this project is simplification of the original project for more friendly user interface.    


The main purpose of this project is redesign of the original PCSX2 for making more simple and friendly user interface for user without complex structure of control component. The original project has complext user interface for regular users (in my view). It has power tools for creating "patches" for game, but I as a gamer does not need all of them - I need only a couple buttons for launching my favorite game. Another purpose is to add a touch screen control for Windows 10. 

Brief PCSX2 review

Omega Red PS2 emulator is clone of the original PCSX2 project. It has a significant changing in graphic user interface, but it can be difficult understand all these have made changing without context of the original project. 

So, original project is a result of the long time working of the PCSX2 team. It emulates hardware part of PlayStation 2 game console on x86 CPU for Windows, Linux and MacOS. It includes Emotion Engine MIPS R5900 and I/O processor MIPS R3000 software translators which convert those sets of commands into Intel x86 set of commands. However, the difference between of the CPU architectures leads defining three main execution threads: "EE Core" for execution main set of MIPS R5900's and MIPS R3000's commands; "MTVU" for execution set of 3D vector operations from VU1 commands; "MTGS" for execution commands of the Graphics Synthesizer processor. This threading model allows use three and more core CPU with effective way.

On the level of code PCSX2 is based on frameworks wxWidgets and pthreads4w. wxWidgets is framework for creating easy ported GUI. pthreads4w is framework for defining Unix thread's interface in Windows OS for compatibleness with Unix code.

The last moment is an architect of application. PCSX2 has one the main execution application with the loading of plugins. As a result, schema can be presented by the next way:

My view of problem

In my view, the original project is too complex. It includes tools for debugging code, creating "patches", printout into logs, configuration of CPU translators, configuration of plugins and others. I think that more simple version in light style can make it easy for using. The other moment is controlling by "touch" schema - I really would like take my light Surface Pro and spend some time on playing in Final Fantasy X or Final Fantasy XII, but GUI in "Win95" style and the need to use an external game pad "kills game" (embedded touch keyboard is useless).


What I have done?

So, what I have done? I have decided to write clone version of PCSX2 on WPF/C# GUI. WPF is flexible framework and allows write code for correct interacting with "touch" screens. However, WPF framework is based on managed code and cannot be effective for direct using in translator code. It leads to the next solution - write the new C DLL library which will contains the minimum needed code for working of the emulator. It has become a significant problem - the original PCSX2 project is an application with the mixed architecture - it is very difficult to separate a "business" logic - emulator core from "thin" client - user interface.  This problem has become more worse because the original code uses classes and structures from wxWidgets in almost each source file - it binds wxWidgets with PCSX2 very tight. Removing dependency from wxWidgets needs rewriting almost all original source files - it is too much for hobby project and it will lost compatibleness with the original PCSX2 project on the level of the static linking - any modification of the original code needs to copy and modify manually into the code in the new emulator. 

Stub wxWidget

In my view, there is only one suitable decision - write simple framework which includes the same static linking interface as wxWidgets, but implements the needed functionality by C++ STD code. It looks like huge amount of work, but it does not. Removing dependency to wxWidgets from PCSX2 allows to mark code which is related with GUI part of project, and remove it from the code. As a result, "Framework" includes only 12 source files. Most of them are "stub" files - they define classes according interface of wxWidgets, but do not include executable code. The code of the emulator core is UNBOUNDED from wxWidgets framework.

Creating PCSX2Lib

I have wrote previously that the main part of this project was finding code of the original PCSX2 which can be named as "Core" or "Kernel". After some research I found files which can be named by this way - these files can work without specific context of the GUI and File System.

Of course, this "Core" cannot work by itself (it is DLL), but it can be used by any Framework in any programming language which CAN LOAD AND LINK C DYNAMIC LIBRARIES. C# and WPF is suitable framework for this purpose and I have faced with problem to define correct C interface for PCSX2Lib.dll. Code of PCSX2Lib.dll is very difficult for understanding, and I think that for the most readers of this article it can be more interesting review C interface of this library and understand the purpose each of the exported functions.


;    Init functions

;    EE Core thread controls

;    MTGS thread controls
;    MTVU thread controls
;    Plugin managment
;    Patches managment
;    BIOS managment
;    Saving managment
;    Loading managment
;    Elf file managment

Init functions:

  • DetectCpuAndUserModeFunc - function for detecting of the multimedia support of the CPU. 
  • AllocateCoreStuffsFunc - function for allocating memory and initializing inner variables and translators.
  • ApplySettingsFunc - function for set the new values for inner variables. 

EE Core thread controls:

  • SysThreadBase_ResumeFunc - function for resuming EE Core thread (init state is not started and resume command start this thread).
  • SysThreadBase_SuspendFunc - function for suspending EE Core thread.
  • SysThreadBase_ResetFunc - function for stopping EE Core thread and releasing related memory.
  •  SysThreadBase_CancelFunc - function for inactivating EE Core thread.

MTGS thread controls:

  • MTGS_ResumeFunc - function for resuming Graphics Synthesizer (GS) thread (init state is not started and resume command start this thread).
  • MTGS_WaitForOpenFunc - function for waiting of initializing of the graphic context of GS.
  • MTGS_IsSelfFunc - function for checking of the GS thread for preventing of mutex deadlock.
  • MTGS_SuspendFunc - function for suspending GS thread.
  • MTGS_CancelFunc - function for canceling GS thread.
  • MTGS_FreezeFunc - function for saving of the graphic context from GS thread.
  • MTGS_WaitGSFunc - function for waiting to finish the current graphic context.

MTVU thread controls:

  • MTVU_CancelFunc - function for canceling of the 3D Vector Unit Thread.
  • vu1Thread_WaitVUFunc - function for waiting of the starting of the 3D Vector Unit Thread.

Plugin management:

  • openPlugin_SPU2Func - function for opening AudioOutput context. 
  • openPlugin_DEV9Func - function for opening DEV9 context. 
  • openPlugin_USBFunc - function for opening USB context. 
  • openPlugin_FWFunc - function for opening FW context.   
  • setPluginsInitCallback - function for setting init callback C function for initializing from EE Core thread.   
  • setPluginsCloseCallback - function for setting close callback C function for closing from EE Core thread. 
  • setPluginsShutdownCallback - function for setting release callback C function for releasing plugins' resources from EE Core thread. 
  • setPluginsOpenCallback - function for setting open callback C function for opening from EE Core thread. 
  • setPluginsAreLoadedCallback - function for setting check loading status callback C function for checking status from EE Core thread. 
  • resetCallbacksFunc - function for releasing all callback pointers.
  • setGS - function for setting of the C pointer on GS external module.         
  • setPAD - function for setting of the C pointer on PAD external module.        
  • setSPU2 - function for setting of the C pointer on AudioOutput external module.    
  • setCDVD - function for setting of the C pointer on CDVD external module.  
  • setMcd - function for setting of the C pointer on Memory Card external module.   
  • setUSB - function for setting of the C pointer on USB external module.          
  • setFW - function for setting of the C pointer on FW external module.    
  • setDEV9 - function for setting of the C pointer on DEV9 external module.

Patches management:

  • setUI_EnableSysActionsCallback - function for setting UI update callback from EE Core thread.
  • ForgetLoadedPatchesFunc - function for releasing all current patches in EE Core.
  • inifile_commandFunc - function for settings text patches to EE Core.
  • setLoadAllPatchesAndStuffCallback - function for setting load patches callback from EE Core thread. 
  • setSioSetGameSerialFunc - function for setting serial number of the game disk. 
  • getGameStartedFunc - function for getting of EE Core gaming status. 
  • getGameLoadingFunc - function for getting of EE Core game loading status. 
  • getElfCRCFunc - function for getting of ElfFile CRC check sum. 
  • VTLB_Alloc_PpmapFinc - function for reallocating memory for patches.
  • releaseWCHARStringFunc - function for releasing of the allocated memory for wchar_t string.
  • getSysGetBiosDiscIDFunc - function for getting of the Bios Disc ID.
  • gsUpdateFrequencyCallFunc - function for updating GS context after patching. 
  • getSysGetDiscIDFunc - function for getting of the Disc ID.

BIOS management:    

  • setLoadBIOSCallbackCallback - function for setting loading BIOS callback from EE Core thread.      
  • setCDVDNVMCallback - function for setting saving and loading BIOS Configuration callback from EE Core thread.    
  • setCDVDGetMechaVerCallback - function for setting loading DVD hardware serial number callback from EE Core thread. 

Saving management:     

  • getFreezeInternalsFunc - function for getting internal variables as a byte array.    
  • getEmotionMemoryFunc - function for getting EE Core memory as a byte array.       
  • getIopMemoryFunc - function for getting I/O Processor memory as a byte array.    
  • getHwRegsFunc - function for getting EE Core Hardware Registers as a byte array.     
  • getIopHwRegsFunc - function for getting I/O Processor Hardware Registers as a byte array.       
  • getScratchpadFunc - function for getting Scratchpad (Buffer) memory as a byte array.     
  • getVU0memFunc - function for getting Vector Unit 0 memory as a byte array.     
  • getVU1memFunc - function for getting Vector Unit 1 memory as a byte array.     
  • getVU0progFunc - function for getting Vector Unit 0 program as a byte array.     
  • getVU1progFunc - function for getting Vector Unit 1 program as a byte array.     
  • getFreezeOutFunc - function for getting plugin memory as a byte array.     
  • setDoFreezeCallback - function for setting saving plugin memory callback.

 Loading management     

  • setFreezeInFunc - function for setting plugin memory as a byte array.       
  • setFreezeInternalsFunc - function for setting internal variables as a byte array.         
  • setEmotionMemoryFunc - function for setting EE Core memory as a byte array.  
  • setIopMemoryFunc - function for setting I/O Processor memory as a byte array.     
  • setHwRegsFunc - function for setting EE Core Hardware Registers as a byte array.       
  • setIopHwRegsFunc - function for setting I/O Processor Hardware Registers as a byte array.     
  • setScratchpadFunc - function for setting Scratchpad (Buffer) memory as a byte array.     
  • setVU0memFunc - function for setting Vector Unit 0 memory as a byte array.     
  • setVU1memFunc - function for setting Vector Unit 1 memory as a byte array.          
  • setVU0progFunc - function for setting Vector Unit 0 program as a byte array.       
  • setVU1progFunc - function for setting Vector Unit 1 program as a byte array.

Elf file management:

  • PCSX2_Hle_SetElfPathFunc - function for setting Elf file (unworkable, but it is needed for static linking).  

These functions have specific orders of calling and external framework must execute them and invoke code in callback functions by specific algorithms. 

In C# code launching emulator begin from initializing:




Binding callback functions:

private void Bind()
      foreach (var l_Module in ModuleManager.Instance.Modules)

      PCSX2LibNative.Instance.setPluginsInitCallback = delegate()

      PCSX2LibNative.Instance.setPluginsCloseCallback = delegate()

      PCSX2LibNative.Instance.setPluginsShutdownCallback = delegate()

      PCSX2LibNative.Instance.setPluginsOpenCallback = delegate()

      PCSX2LibNative.Instance.setPluginsAreLoadedCallback = delegate()
          return ModuleControl.Instance.areLoaded();
      PCSX2LibNative.Instance.setUI_EnableSysActionsCallback = delegate()
          if (!PCSX2Controller.Instance.innerCall())

      PCSX2LibNative.Instance.setLoadAllPatchesAndStuffCallback = delegate(uint a_FirstArg)

      PCSX2LibNative.Instance.setLoadBIOSCallbackCallback = delegate(IntPtr a_FirstArg, Int32 a_SecondArg)
           Omega_Red.Tools.BiosControl.LoadBIOS(a_FirstArg, a_SecondArg);

      PCSX2LibNative.Instance.setCDVDNVMCallback = delegate(IntPtr buffer, Int32 offset, Int32 bytes, Boolean read)
          Omega_Red.Tools.BiosControl.NVMFile(buffer, offset, bytes, read);

      PCSX2LibNative.Instance.setCDVDGetMechaVerCallback = delegate(IntPtr buffer)

      PCSX2LibNative.Instance.setDoFreezeCallback = delegate(IntPtr a_FirstArg, Int32 a_mode, Int32 a_ModuleCode)
          return ModuleControl.Instance.doFreeze(a_FirstArg, a_mode, a_ModuleCode);

 And resuming EE Core thread:


Omega Red project is complex and reviewing of whole code needs ever a real book. However, I think that it can be important to pay attention on architect of the programing solutions.

Modules - Plugins 

Original PCSX2 project defines group of an external plugins for interacting with emulator - video, audio, game pad, memory card. Omega Red project has the same solutions which are named modules - they DOES NOT exist as an real external files, but they are contained in execution file as resources. At the moment of launching of application they unpacked, and at the moment of exit from application they are deleted. Another moment is defining module's interface - each module has own set of functions which is used by PCSX2Lib, but for an external framework it is useless - so, in this project there is own common interface for interacting with modules:

PCSX2_EXPORT_C_(void*) getAPI()
    return (void*)&CDVDapi_Iso;

PCSX2_EXPORT_C execute(const wchar_t* a_command, wchar_t** a_result)
    g_CDVD.execute(a_command, a_result);

PCSX2_EXPORT_C releaseString(wchar_t* a_string)
    if (a_string != nullptr)
        delete[] a_string;

Function getAPI returns pointer on PCSX2Lib API, function execute executes command from an external framework and return result in form of the XML document, function releaseString releases the returned result string memory. 

Video Renderer

Omega Red has module for rendering by DirectX11, but there is one problem - WPF supports only DirectX9 texture in D3DImage. This problem is resolved by the next way: 

  1. Create DirectX texture with the "Shared" handle - Direct3D9Ex supports creating DirectX9 texture with DXGI shared handler.
  2. Create the shared DirectX11 texture from the DXGI shared handler of the DirectX9 texture:
    // Create shared texture 

    CComPtr<ID3D11Resource> l_Resource;

    hr = m_dev->OpenSharedResource(sharedhandle, IID_PPV_ARGS(&l_Resource));

    if (FAILED(hr)) return false;

    hr = l_Resource->QueryInterface(IID_PPV_ARGS(&m_SharedTexture));

Touch pad

Omega Red has integrated touch pad:

It is implemented according XInput specification by defining C# structures for copying button status and analog sticks axises into the native memory:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct XINPUT_GAMEPAD {
            public UInt16 wButtons;
          public byte bLeftTrigger;
          public byte bRightTrigger;
          public Int16 sThumbLX;
          public Int16 sThumbLY;
          public Int16 sThumbRX;
          public Int16 sThumbRY;
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct XINPUT_STATE {
            public UInt32 dwPacketNumber;
            public XINPUT_GAMEPAD Gamepad;

Screen and Video Capture

Original PCSX2 has some functionality for taking the rendered game image and record gameplay video, but such functionality is not easy accessible and is not optimized for such purpose. I has decided to include more flexible solutions. It uses WPF functionality for capturing screen into the JPEG format image file by - it allows copy DirectX texture from back buffer to C# BitmapSource and encode it by JpegBitmapEncoder:

public byte[] takeScreenshot()
    byte[] l_result = null;

    var l_D3D9Image = imageSource as D3D9Image;

    if(l_D3D9Image != null)
        BitmapSource l_bitmap = l_D3D9Image.getBackBuffer();

        JpegBitmapEncoder l_encoder = new JpegBitmapEncoder();

        l_encoder.QualityLevel = 75;


        using (var outputStream = new MemoryStream())

            l_result = outputStream.ToArray();

    return l_result;

For capturing of gameplay video I have used CaptureManager SDK - it is a simple SDK for capturing video and audio from many sources. For purpose of this project CaptureManager SDK has the next advantages:

1. Simple and flexible C# interface - it allows integrate video capture code to any WPF/C# project.

2. Defining source by XML document - address of pointer on the capture DirectX11 texture is wrote as numbers in the text format:

            string lPresentationDescriptor = "<?xml version='1.0' encoding='UTF-8'?>" +
            "<PresentationDescriptor StreamCount='1'>" +
                "<PresentationDescriptor.Attributes Title='Attributes of Presentation'>" +
                    "<Attribute Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK' GUID='{58F0AAD8-22BF-4F8A-BB3D-D2C4978C6E2F}' Title='The symbolic link for a video capture driver.' Description='Contains the unique symbolic link for a video capture driver.'>" +
                        "<SingleValue Value='ImageCaptureProcessor' />" +
                    "</Attribute>" +
                    "<Attribute Name='MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME' GUID='{60D0E559-52F8-4FA2-BBCE-ACDB34A8EC01}' Title='The display name for a device.' Description='The display name is a human-readable string, suitable for display in a user interface.'>" + 
                        "<SingleValue Value='Image Capture Processor' />" +
                    "</Attribute>" +
                "</PresentationDescriptor.Attributes>" + 
                "<StreamDescriptor Index='0' MajorType='MFMediaType_Video' MajorTypeGUID='{73646976-0000-0010-8000-00AA00389B71}'>" + 
                    "<MediaTypes TypeCount='1'>" + 
                        "<MediaType Index='0'>" +
                            "<MediaTypeItem Name='MF_MT_FRAME_RATE' GUID='{C459A2E8-3D2C-4E44-B132-FEE5156C7BB0}' Title='Frame rate.' Description='Frame rate of a video media type, in frames per second.'>" + 
                                "<RatioValue Value='30.0'>" + 
                                    "<Value.ValueParts>" + 
                                        "<ValuePart Title='Numerator'  Value='30' />" +  
                                        "<ValuePart Title='Denominator'  Value='1' />" +  
                                    "</Value.ValueParts>" + 
                                "</RatioValue>" + 
                            "</MediaTypeItem>" +
                            "<MediaTypeItem Name='CM_DirectX11_Capture_Texture' GUID='{179B7A05-496A-4C9F-B8C6-15F04E669595}'>" +
                                "<SingleValue Value='{Temp_Capture_Texture}' />" +
                            "</MediaTypeItem>" +
                        "</MediaType>" +
                    "</MediaTypes>" +
                "</StreamDescriptor>" +

where {Temp_Capture_Texture} is preplaced on text representation of pointer on DirectX11 render target texture which is used as source.

lPresentationDescriptor = lPresentationDescriptor.Replace("{Temp_Capture_Texture}", a_PtrDirectX11Source.ToInt32().ToString()); 

Audio stream is recorded by "Audio Loopback" - from inner system speaker output.

The CaptureManager SDK uses different video and audio encoders for recording of game play video.



Omega Red is based on PCSX2 code and for correct linking of the original code at the process of compiling it is placed in the root folder:

The result binary executable file is placed into the "bin" folder: 


Source code of this project is stored in GitHub in Omega_Red repository:


Finish result

The result is presented in form of the ONE executable file - it includes all needed code in form of the one file. So, prorgam is designed for "touch" control and it has the next features:

1. Full screen size - prgram has only one window size - maximum which takea whole screen area.

2. Minimum configuration  - default configuration is enough for the most games.

3. It allows only one player game configuration.  

The next sub sections describe the result program more informative, but full screen images have poor quality in small HTML images. 



User Interface

Omega Red is a project for making an original PCSX2 more friendly and easy using. These targets are reflected by design of the user interface with the features which can be got from WPF GUI framework.

Original user interface of PCSX2 in "Win95" style:

is replaced on the new style with "Touch - Tile" design for supporting of the screen touch controlling with minimum needs to use classic "mouse" HID:


Resource management

Much time had been spent on design of the simple schema for easy controlling and management of the main game playing resources: BIOSs, ISO game's discs, Memory cards, PADs, Savings.

BIOSs are important part of the working process of the emulator. Omega Red supports reading data from binary file or from ZIP archive:

ISO disc image files are the only way for launching game on this emulator. These files are registered in collection of Game Discs and are recognized - the original PCSX2 emulator reads and checks ISO file at the time of launching of the game, but this new emulator checks type of image disc, supporting it by PS2, region of game disc without launching of emulator:

Loading and saving states are significantly changed. The original PCSX2 emulator has limitation in 10-th file-slots on each game, WITHOUT defining date of saving and progress of gameplay:

Omega Red allows create upto 100-th file-slots on each game at the game process. Each file-slot saves date of saving, duration of the current game session and captured image of the game process: 

In addition, at the moment of stopping of the emulator or at the moment of closing of Omega Red application current game process is saved into the special file "Autosave" - it allows continue game in case if gamers has forgotten to save last game session. Also, the sequence of loading the saved state has been changed - while the original PCSX2 needs load BIOS, load game disc, check it and only then it needs click on the needed slot, Omega Red loads the saved game state by one click. As a result, the time of loading of the saved game is decreased from 20 seconds to 3-5 seconds.   

This project allows manage PS2 memory cards by more "flexible" way:  

Memory cards can be created at the gameplay time, and emulator can be switched from one memory card to another one at the gameplay for saving and loading data in games. Name of the created memory card file is based on name of the game and unique serial ID of the game disc - it allows display only "own" game memory card files. 



Omega Red allows to switch game control from "Touch" PAD on "Game" Pad and back: 



Functionality for capturing of images and videos in the original PCSX2 has some limitation and it needs make some research of configuration for finding these command. In this project I have redesigned it and have added the needed control buttons on the top of display:

Quality of the captured image is fixed (75%). Quality of the "Live" gameplay video can be changed in range from 10% to 99%. Names of the created images and video files are generated by the friendly name of game and current date.  The separated panel allows view captured images and videos at the pause time:


General config

"General config" panel allows change none-game configurations: "Display mode", "Control mode", "Set Topmost", "Quality of video compression", "Turn off wide screen format", "Current language".

"Display mode" allows to switch displaying area from full screen to region and back.

"Control mode" allows switch general control from "Button" to "Touch" schema and back.

"Set Topmost" sets application window on top other applications. "Quality of video compression" allows set quality compression for video capturing functionality.  "Turn off wide screen format" - by default, Omega Red sets available patches for changing proportion of screen to 16/9, but this options allows to disable these patches. "Current language:" allows change current language.

"Current colour schema:" allows change current colour schema.



Omega Red has been developed since a simple idea to improve the original PCSX2 emulator and add some new features. It is more user friendly solution and can be useful for PCSX2 development society.  


Points of Interest

At the time of development C# part of project I had faced with problem of stack overflow - at the moment of the processing data from DVD image programm throw out exception of stack overflow type. After some research I found that the original PCSX2 project generate code of MIPS translators by combination of C/C++ macroses. It allows fast generate effective code, but this code has recursive structure which consume STACK of process. For native C/C++ it not problem, but C# projects has limited size of STACK which IS NOT enogh for PCSX2 code. How it has been resoved? - very easy. There is a special program "editbin.exe" in Visual Studio pack which allows edit the compiled program - argument "/STACK:" allows change STACK of program process. Next command is executed in "Post-build event command line":

"$(DevEnvDir)..\..\VC\bin\editbin.exe" /STACK:6000000 "$(TargetPath)"

It expand STACK of C# program up to 6 Million Bytes.



The first update on 09/10/2018:

Add "Current colour schema:" option for changing of the colour of application's elements.



The first update on 09/10/2018


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


About the Author

Evgeny Pereguda
Software Developer
Australia Australia
No Biography provided

You may also be interested in...

Comments and Discussions

SuggestionMessage Closed Pin
9-Oct-18 18:56
memberJeniffer zwick9-Oct-18 18:56 
QuestionVirus warning Pin
wvd_vegt9-Oct-18 4:17
professionalwvd_vegt9-Oct-18 4:17 
AnswerRe: Virus warning Pin
Evgeny Pereguda9-Oct-18 14:21
memberEvgeny Pereguda9-Oct-18 14:21 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04-2016 | 2.8.181116.1 | Last Updated 8 Oct 2018
Article Copyright 2018 by Evgeny Pereguda
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid