Recent Posts

Pages: [1] 2 3 ... 10
1
WebmCap / WebmCap
« Last post by Patrice Terrier on September 16, 2026, 08:48:18 am »
WebmCap - Tiny native WebM screen recorder/player

WebmCap is the successor to FFplay/FFcapture. It no longer uses FFmpeg.exe, thus drastically reducing the binary size.
It is designed primarily for creating tutorial videos.

It captures a selected desktop window in real time and creates a WebM video using VP9 for video and Opus for audio.



The complete executable is currently only:

1213 KB

Despite its small size, WebmCap includes the complete recording and WebM muxing engine.

Main features

  • Native Windows x64 C/C++ application.
  • Desktop/window video capture.
  • VP9 video encoding using libvpx.
  • Opus audio encoding.
  • Native WebM muxing using libwebm.
  • 48 kHz stereo audio capture.
  • Correct real-time audio/video synchronization.
  • Built-in video player using the WinLIFT WV2B/WebView2 browser control.
  • Drag and drop a video file onto WebmCap to play it.
  • Playback is not artificially restricted to WebM: any video format supported by WV2B/WebView2 can be used.
  • Press ESC to close the player and return to capture mode.

Why WebM / VP9 / Opus?

The original goal was to avoid relying on a large external multimedia application such as FFmpeg for a relatively simple screen-recording task.

WebmCap therefore uses only the components it actually needs.

libvpx was reduced to the VP9 encoding functionality required by WebmCap, Opus is statically linked, and the required libwebm muxer sources are compiled directly into the application.

The project also uses TCLib to minimize conventional CRT overhead.

The result is a complete recorder/player of approximately 1.2 MB.

Recording quality

A demanding test was made using an ObjReader 3D animation (Helmet.orb).

For the same 71-second animation:

Code: [Select]
FFCapture : 91.0 MB / 71 seconds
WebmCap   : 58.4 MB / 71 seconds

The WebmCap VP9 recording retains excellent visual quality while producing a substantially smaller file.

ObjReader was rendering the animation at approximately 239/240 FPS while WebmCap recorded it at 30 FPS.

Playback

WebmCap is primarily a WebM recorder, but it can also be used as a lightweight video player.

Simply drag and drop an existing video file onto the WebmCap window.

The video is displayed using WV2B/WebView2. Press ESC to close the player and immediately return to the capture interface.

There is deliberately no hard-coded list of supported video extensions: the file is passed directly to WV2B/WebView2, which determines whether it can be played.

Implementation

WebmCap is written in native procedural C/C++ for Windows x64.

It uses:

  • Win32 API
  • GDI desktop capture
  • WaveIn audio capture
  • libvpx / VP9
  • Opus
  • libwebm
  • WinLIFT/GDImage
  • WV2B / WebView2 for playback
  • TCLib

WebmCap is another example of what can still be achieved on Windows by staying close to the native API and including only the code actually required.
2
Eye Candies / ShaderPlay new version
« Last post by Patrice Terrier on September 12, 2026, 08:57:14 am »
ShaderPlay version 2.00

ShaderPlay has been completely reworked and can now capture and play Shadertoy projects directly from their Web URL.

ShaderPlay is a tiny native Windows Shadertoy player written in procedural C/C++ using the Win32 API and OpenGL. It supports complex multipass shaders, textures, cubemaps, video, music and Sound shaders, keyboard and mouse input, as well as audio-reactive visualizations.

The complete Visual Studio 2022 C/C++ project is attached to this post, including the source code and a selection of ready-to-use .shc examples.

The resulting ShaderPlay executable:




Capturing a Shadertoy project

Importing a shader from the Shadertoy website is very simple.

Open the shader you want on Shadertoy in your usual Web browser and copy its URL from the browser address bar to the Windows clipboard.

For example:

Code: [Select]
https://www.shadertoy.com/view/XXXXXXXX
When ShaderPlay detects a valid Shadertoy URL in the clipboard, a "Paste from clipboard" button automatically appears.

Click this button to start the capture.

ShaderPlay then connects to Shadertoy, analyzes the project and retrieves everything required to reproduce it locally. This includes the GLSL code and, depending on the shader, its multipass information, textures, cubemaps, video and music resources.

Once the capture is complete, the shader can be played immediately and is saved in the local shader folder beside ShaderPlay.exe as a single .shc (Shader Compound) file.

The workflow is therefore:

Open the shader on Shadertoy -> copy its URL -> the "Paste from clipboard" button appears -> click it -> ShaderPlay captures the project and creates the standalone .shc file.


The .shc Shader Compound format

An .shc file packages the Shadertoy project and all the resources required to play it into a single standalone file.

Depending on the original project, this can include the shader data, multipass information, textures, cubemaps, video and music.

Once the .shc has been created, the original Shadertoy URL is no longer required to play the shader. The file can simply be kept in the local shader folder, copied to another ShaderPlay installation or exchanged with another user.

Each .shc also contains its own PNG thumbnail, allowing Windows Explorer to display a visual preview of the shader.

This makes .shc more than a simple cache: it is the portable ShaderPlay document format.


BBPlugin support

ShaderPlay also retains support for the existing native BBPlugin DLL visualizations.

When a BBPlugin folder is present beside the executable, ShaderPlay automatically detects the available plugins.

In that configuration, the interface provides two combo boxes: one for selecting the native BBPlugin visualizations and another for selecting the .shc Shadertoy shaders.

ShaderPlay can therefore be used both as the original BBPlugin visualization host and as a native Shadertoy player.


Audio

ShaderPlay can work with several different audio sources.

When a Shadertoy project includes its own music/audio track, that track is retrieved during capture and embedded directly into the .shc file. It is restored automatically when the shader is played, so no external audio file is required.

You can also drag and drop your own audio file onto ShaderPlay.

In this case, the dropped audio takes priority over the audio track built into the shader. This makes it possible to play an audio-reactive shader using your own music instead of its original track.

When the dropped audio has finished, ShaderPlay can return to the audio supplied by the shader itself.

Shadertoy Sound shaders are different again. They do not use a prerecorded audio track: the sound itself is generated in real time by the GLSL shader.

The same audio system can also drive the native BBPlugin visualizations.


A note about GPU performance

ShaderPlay executes the original Shadertoy GLSL code directly on the GPU.

It deliberately does not simplify or rewrite shaders to accommodate slower hardware.

Many shaders will run perfectly well on modest hardware, but some Shadertoy projects are extraordinarily demanding, particularly complex multipass shaders and shaders performing intensive real-time Sound calculations.

For the most demanding shaders, a dedicated GPU is highly recommended.

Integrated graphics may run many shaders correctly, but particularly intensive projects can result in lower frame rates, sluggish interaction or occasional short stalls.

ShaderPlay's objective is to reproduce the original Shadertoy project as faithfully as possible rather than modify it to accommodate slower graphics hardware.


Source code

The attachment contains the complete C/C++ Visual Studio 2022 project, not just the compiled executable.

This is native Windows code using the Win32 API and OpenGL, so anyone interested in how the Shadertoy environment, multipass rendering, resources, Sound shaders, audio and .shc packaging are implemented can examine and modify the actual source code.

A selection of ready-to-use .shc files is included, so ShaderPlay can be tested immediately without first capturing shaders from the Web.

You can then visit Shadertoy, find shaders you like, copy their Web URL and let ShaderPlay capture them to build your own local collection of standalone .shc files.

And, ShaderPlay.exe is only 79 KB.
3
The concept / WinLIFT 64-bit 8.00 (is attached to this post)
« Last post by Patrice Terrier on August 03, 2026, 05:22:16 pm »
WinLIFT 8.00 - Major update

WinLIFT 8.00 is a major evolution of my native Win32 skinning framework.

BBRTV (BassBox Radio/TV) was used as the development laboratory for this release and is provided as a working example showing how to use the new WinLIFT 8.00 API in a real-world application.

What's new in WinLIFT 8.00

  • New Grid control
    A native WinLIFT Grid control with headers, multiple columns, text, images, edit/combo/button support, hidden columns, selection management, scrolling and resizable columns.

  • New image compositor
    WinLIFT can compose the complete application window into an image, including the non-client area, child controls, scrollbars and GDImage content.

  • Tighter GDImage integration
    WinLIFT and GDImage now cooperate more closely, especially for WM_PRINT rendering and image composition.

  • DWM popup overlay support
    The compositor can integrate GDImage DWM-composited popup overlays used by applications such as MBox64 and ObjReader64.

  • Integrated snapshot
    CTRL+SCREENSHOT can be used at any time to create a snapshot of the fully composed application window. The picture is saved beside the executable.

  • New window animation engine
    skShowWindow() can display a window using one of the new WinLIFT effects:

        SK_EFFECT_FALLZOOM
        SK_EFFECT_TRANSLUCENTSPIRAL
        SK_EFFECT_CURL_FROM_BOTTOMLEFT
        SK_EFFECT_CURL_FROM_BOTTOMRIGHT
        SK_EFFECT_CURL_FROM_TOPRIGHT
        SK_EFFECT_CURL_FROM_TOPLEFT
        SK_EFFECT_LIQUIDFILL
        SK_EFFECT_BLURFOCUS

    The Curl effects are directional and the new effects are generated entirely at runtime from the composed window image.

  • WebView2 integration
    WinLIFT provides a compact native API for embedding WebView2 into a skinned application.

  • Embedded DLL memory loader
    A DLL stored inside the WinLIFT RCDATA resources can be loaded directly from memory, without extracting it to disk.

    This is used to keep the WebView2/WRL implementation isolated from the main WinLIFT runtime while preserving single-DLL deployment.

  • DPI scaling helpers
    WinLIFT itself does not claim Windows DPI awareness.

    It provides built-in DPI scaling helpers instead. The host Windows DPI is used by default and the .sks USE_DPI property can provide an explicit scaling value when required.

    The image compositor is designed to remain compatible with the DPI environment of the host computer.

  • zVector
    Internal STL vector usage has been replaced by the lightweight zVector implementation.

  • Windows 10/11 improvements
    Several rendering and compatibility issues have been addressed, including TreeView focus, SYSHEADER rendering, scrollbar overlap and control redraw behavior.

  • Smaller runtime
    Despite the new Grid, compositor, animation engine, WebView2 support and memory loader, considerable work has been done to reduce the WinLIFT64.dll footprint and external runtime dependencies.
    The overall binary size is only 223 KB.
.
 
BBRTV - WinLIFT 8.00 example

BBRTV is not the subject of this release; it is the application I used to develop, test and demonstrate the new WinLIFT 8.00 functionality.

Its source code provides practical examples of the new Grid API, WebView2 integration, compositor, snapshot facility and window effects.
(A different effect is selected at each startup in loop mode).

The current C/C++ BBRTV executable is only: 67 KB

WinLIFT 8.00 remains a native 64-bit Unicode Win32 SDK framework, without MFC or .NET, designed for applications where full control of the Windows interface and a small native footprint are important.
4
64-bit SDK programming / Re: Load DLL from memory (using RCDATA)
« Last post by Patrice Terrier on August 03, 2026, 08:18:41 am »
Here is the 64-bit version, based on PB's code.
This one is used by WinLIFT 8.00, to load my WV2B.dll directly in memory from RCDATA without disk access.
It allows me to provide only one single WinLIFT64.dll, rather than two (easier for code distribution).

Code: [Select]
//+--------------------------------------------------------------------------+
//|                                                                          |
//|                          (LoadDLLfromMemory)                             |
//|                                                                          |
//|                         Author Patrice TERRIER                           |
//|                         copyright(c) 2007-2026                           |
//|                           www.zapsolution.com                            |
//|                        pterrier@zapsolution.com                          |
//|                                                                          |
//+--------------------------------------------------------------------------+
//|                  Project started on : 00-06-2007 (MM-DD-YYYY)            |
//|                        Last revised : 08-02-2026 (MM-DD-YYYY)            |
//+--------------------------------------------------------------------------+

#pragma once

static HMODULE Load_DLL(IN WCHAR* lpName) {
    typedef BOOL (WINAPI *DLLENTRYPROC)(HINSTANCE, DWORD, LPVOID);

    HMODULE hInstance = 0;
    HMODULE hModule = 0;
    HRSRC hResource = 0;
    HGLOBAL hGlobal = 0;
    BYTE* pRawDll = 0;
    BYTE* pImage = 0;
    DWORD RawDllSize = 0;
    DWORD HeadersSize = 0;
    DWORD OldProtect = 0;
    DWORD K = 0;
    LONG FunctionTableAdded = 0;

    IMAGE_DOS_HEADER* pSrcDos = 0;
    IMAGE_NT_HEADERS64* pSrcNt = 0;
    IMAGE_SECTION_HEADER* pSrcSection = 0;
    IMAGE_DOS_HEADER* pDstDos = 0;
    IMAGE_NT_HEADERS64* pDstNt = 0;
    IMAGE_SECTION_HEADER* pDstSection = 0;
    RUNTIME_FUNCTION* pFunctionTable = 0;

    if (!lpName) return 0;

    if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                           (LPCWSTR) (LONG_PTR) &Load_DLL, &hInstance)) return 0;

    hResource = FindResource(hInstance, lpName, RT_RCDATA);
    if (!hResource) return 0;

    RawDllSize = SizeofResource(hInstance, hResource);
    if (!RawDllSize) return 0;

    hGlobal = LoadResource(hInstance, hResource);
    if (!hGlobal) return 0;

    pRawDll = (BYTE*) LockResource(hGlobal);
    if (!pRawDll) return 0;

    if (RawDllSize < sizeof(IMAGE_DOS_HEADER)) return 0;

    pSrcDos = (IMAGE_DOS_HEADER*) pRawDll;
    if (pSrcDos->e_magic != IMAGE_DOS_SIGNATURE) return 0;
    if (pSrcDos->e_lfanew < 0) return 0;
    if ((QWORD) pSrcDos->e_lfanew + sizeof(IMAGE_NT_HEADERS64) > RawDllSize) return 0;

    pSrcNt = (IMAGE_NT_HEADERS64*) (pRawDll + pSrcDos->e_lfanew);
    if (pSrcNt->Signature != IMAGE_NT_SIGNATURE) return 0;
    if (pSrcNt->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) return 0;
    if (pSrcNt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) return 0;
    if (pSrcNt->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER64)) return 0;
    if (!pSrcNt->FileHeader.NumberOfSections) return 0;
    if (!pSrcNt->OptionalHeader.SizeOfImage) return 0;

    pSrcSection = IMAGE_FIRST_SECTION(pSrcNt);
    HeadersSize = (DWORD) ((BYTE*) (pSrcSection + pSrcNt->FileHeader.NumberOfSections) - pRawDll);
    if (HeadersSize > RawDllSize) return 0;
    if (pSrcNt->OptionalHeader.SizeOfHeaders < HeadersSize) return 0;
    if (pSrcNt->OptionalHeader.SizeOfHeaders > RawDllSize) return 0;

    pImage = (BYTE*) VirtualAlloc((LPVOID) (LONG_PTR) pSrcNt->OptionalHeader.ImageBase,
                                  pSrcNt->OptionalHeader.SizeOfImage,
                                  MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (!pImage) {
        pImage = (BYTE*) VirtualAlloc(0, pSrcNt->OptionalHeader.SizeOfImage,
                                      MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    }
    if (!pImage) return 0;

    memcpy(pImage, pRawDll, pSrcNt->OptionalHeader.SizeOfHeaders);

    pDstDos = (IMAGE_DOS_HEADER*) pImage;
    pDstNt = (IMAGE_NT_HEADERS64*) (pImage + pDstDos->e_lfanew);
    pDstSection = IMAGE_FIRST_SECTION(pDstNt);

    for (K = 0; K < pDstNt->FileHeader.NumberOfSections; K++) {
        DWORD RawSize = pDstSection[K].SizeOfRawData;
        DWORD RawOffset = pDstSection[K].PointerToRawData;
        DWORD VirtualAddress = pDstSection[K].VirtualAddress;
        DWORD VirtualSize = pDstSection[K].Misc.VirtualSize;

        if ((QWORD) VirtualAddress + max(RawSize, VirtualSize) > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

        if (RawSize) {
            if ((QWORD) RawOffset + RawSize > RawDllSize) goto BailOut;
            memcpy(pImage + VirtualAddress, pRawDll + RawOffset, RawSize);
        }

        if (VirtualSize > RawSize) {
            ClearMemory(pImage + VirtualAddress + RawSize, VirtualSize - RawSize);
        }
    }

    {
        QWORD Delta = (QWORD) pImage - pDstNt->OptionalHeader.ImageBase;
        IMAGE_DATA_DIRECTORY RelocDir = pDstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

        if (Delta) {
            if (!RelocDir.VirtualAddress || !RelocDir.Size) goto BailOut;
            if ((QWORD) RelocDir.VirtualAddress + RelocDir.Size > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

            IMAGE_BASE_RELOCATION* pReloc = (IMAGE_BASE_RELOCATION*) (pImage + RelocDir.VirtualAddress);
            BYTE* pRelocEnd = (BYTE*) pReloc + RelocDir.Size;

            while ((BYTE*) pReloc < pRelocEnd && pReloc->SizeOfBlock) {
                if (pReloc->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION)) goto BailOut;
                if ((BYTE*) pReloc + pReloc->SizeOfBlock > pRelocEnd) goto BailOut;

                WORD* pTypeOffset = (WORD*) ((BYTE*) pReloc + sizeof(IMAGE_BASE_RELOCATION));
                DWORD Count = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

                for (DWORD N = 0; N < Count; N++) {
                    WORD Type = pTypeOffset[N] >> 12;
                    WORD Offset = pTypeOffset[N] & 0x0FFF;
                    QWORD PatchRva = (QWORD) pReloc->VirtualAddress + Offset;

                    if (Type == IMAGE_REL_BASED_DIR64) {
                        if (PatchRva + sizeof(QWORD) > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;
                        *(QWORD*) (pImage + PatchRva) += Delta;
                    } else if (Type != IMAGE_REL_BASED_ABSOLUTE) {
                        goto BailOut;
                    }
                }

                pReloc = (IMAGE_BASE_RELOCATION*) ((BYTE*) pReloc + pReloc->SizeOfBlock);
            }
        }
    }

    {
        IMAGE_DATA_DIRECTORY ImportDir = pDstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

        if (ImportDir.VirtualAddress) {
            if ((QWORD) ImportDir.VirtualAddress + ImportDir.Size > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

            IMAGE_IMPORT_DESCRIPTOR* pImport = (IMAGE_IMPORT_DESCRIPTOR*) (pImage + ImportDir.VirtualAddress);
            BYTE* pImportEnd = pImage + ImportDir.VirtualAddress + ImportDir.Size;

            while ((BYTE*) (pImport + 1) <= pImportEnd && pImport->Name) {
                if (pImport->Name >= pDstNt->OptionalHeader.SizeOfImage) goto BailOut;
                if (pImport->FirstThunk >= pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

                HMODULE hDll = LoadLibraryA((char*) (pImage + pImport->Name));
                if (!hDll) goto BailOut;

                IMAGE_THUNK_DATA64* pNameThunk = (IMAGE_THUNK_DATA64*) (pImage +
                    (pImport->OriginalFirstThunk ? pImport->OriginalFirstThunk : pImport->FirstThunk));
                IMAGE_THUNK_DATA64* pAddrThunk = (IMAGE_THUNK_DATA64*) (pImage + pImport->FirstThunk);

                while (pNameThunk->u1.AddressOfData) {
                    FARPROC hProc = 0;

                    if ((BYTE*) (pNameThunk + 1) > pImage + pDstNt->OptionalHeader.SizeOfImage) goto BailOut;
                    if ((BYTE*) (pAddrThunk + 1) > pImage + pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

                    if (IMAGE_SNAP_BY_ORDINAL64(pNameThunk->u1.Ordinal)) {
                        hProc = GetProcAddress(hDll, (char*) (LONG_PTR) IMAGE_ORDINAL64(pNameThunk->u1.Ordinal));
                    } else {
                        DWORD NameRva = (DWORD) pNameThunk->u1.AddressOfData;
                        if ((QWORD) NameRva + sizeof(IMAGE_IMPORT_BY_NAME) > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;
                        IMAGE_IMPORT_BY_NAME* pImportName = (IMAGE_IMPORT_BY_NAME*) (pImage + NameRva);
                        hProc = GetProcAddress(hDll, (char*) pImportName->Name);
                    }

                    if (!hProc) goto BailOut;
                    pAddrThunk->u1.Function = (QWORD) hProc;
                    pNameThunk++;
                    pAddrThunk++;
                }

                pImport++;
            }
        }
    }

    {
        IMAGE_DATA_DIRECTORY ExceptionDir = pDstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];

        if (ExceptionDir.VirtualAddress && ExceptionDir.Size) {
            if ((QWORD) ExceptionDir.VirtualAddress + ExceptionDir.Size > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;
            if (ExceptionDir.Size % sizeof(RUNTIME_FUNCTION)) goto BailOut;

            pFunctionTable = (RUNTIME_FUNCTION*) (pImage + ExceptionDir.VirtualAddress);
            DWORD EntryCount = ExceptionDir.Size / sizeof(RUNTIME_FUNCTION);

            if (EntryCount) {
                if (!RtlAddFunctionTable(pFunctionTable, EntryCount, (DWORD64) pImage)) goto BailOut;
                FunctionTableAdded = -1;
            }
        }
    }

    {
        IMAGE_DATA_DIRECTORY TlsDir = pDstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];

        if (TlsDir.VirtualAddress && TlsDir.Size) {
            if ((QWORD) TlsDir.VirtualAddress + sizeof(IMAGE_TLS_DIRECTORY64) > pDstNt->OptionalHeader.SizeOfImage) goto BailOut;

            IMAGE_TLS_DIRECTORY64* pTls = (IMAGE_TLS_DIRECTORY64*) (pImage + TlsDir.VirtualAddress);
            PIMAGE_TLS_CALLBACK* pCallback = (PIMAGE_TLS_CALLBACK*) (LONG_PTR) pTls->AddressOfCallBacks;

            if (pCallback) {
                while (*pCallback) {
                    (*pCallback)((LPVOID) pImage, DLL_PROCESS_ATTACH, 0);
                    pCallback++;
                }
            }
        }
    }

    {
        static DWORD ProtectTable[8] = {
            PAGE_NOACCESS,
            PAGE_EXECUTE,
            PAGE_READONLY,
            PAGE_EXECUTE_READ,
            PAGE_READWRITE,
            PAGE_EXECUTE_READWRITE,
            PAGE_READWRITE,
            PAGE_EXECUTE_READWRITE
        };

        if (!VirtualProtect(pImage, pDstNt->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &OldProtect)) goto BailOut;

        for (K = 0; K < pDstNt->FileHeader.NumberOfSections; K++) {
            DWORD Index = 0;
            DWORD SectionSize = max(pDstSection[K].Misc.VirtualSize, pDstSection[K].SizeOfRawData);

            if (pDstSection[K].Characteristics & IMAGE_SCN_MEM_EXECUTE) Index |= 1;
            if (pDstSection[K].Characteristics & IMAGE_SCN_MEM_READ) Index |= 2;
            if (pDstSection[K].Characteristics & IMAGE_SCN_MEM_WRITE) Index |= 4;

            if (SectionSize) {
                if (!VirtualProtect(pImage + pDstSection[K].VirtualAddress, SectionSize,
                                    ProtectTable[Index], &OldProtect)) goto BailOut;
            }
        }
    }

    FlushInstructionCache(GetCurrentProcess(), pImage, pDstNt->OptionalHeader.SizeOfImage);

    if (pDstNt->OptionalHeader.AddressOfEntryPoint) {
        DLLENTRYPROC pEntryPoint = (DLLENTRYPROC) (pImage + pDstNt->OptionalHeader.AddressOfEntryPoint);
        if (!pEntryPoint((HINSTANCE) pImage, DLL_PROCESS_ATTACH, 0)) goto BailOut;
    }

    hModule = (HMODULE) pImage;
    return hModule;

BailOut:
    if (FunctionTableAdded && pFunctionTable) RtlDeleteFunctionTable(pFunctionTable);
    if (pImage) VirtualFree(pImage, 0, MEM_RELEASE);
    return 0;
}

static FARPROC GetProcAddressDirectly(IN HMODULE hModule, IN char* lpProcName) {
    FARPROC hProc = 0;
    BYTE* pImage = (BYTE*) hModule;

    if (pImage && lpProcName) {
        IMAGE_DOS_HEADER* pDos = (IMAGE_DOS_HEADER*) pImage;

        if (pDos->e_magic == IMAGE_DOS_SIGNATURE) {
            IMAGE_NT_HEADERS64* pNt = (IMAGE_NT_HEADERS64*) (pImage + pDos->e_lfanew);

            if (pNt->Signature == IMAGE_NT_SIGNATURE) {
                IMAGE_DATA_DIRECTORY ExportDir = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

                if (ExportDir.VirtualAddress && ExportDir.Size) {
                    IMAGE_EXPORT_DIRECTORY* pExport = (IMAGE_EXPORT_DIRECTORY*) (pImage + ExportDir.VirtualAddress);
                    DWORD* pFunctions = (DWORD*) (pImage + pExport->AddressOfFunctions);
                    DWORD* pNames = (DWORD*) (pImage + pExport->AddressOfNames);
                    WORD* pOrdinals = (WORD*) (pImage + pExport->AddressOfNameOrdinals);

                    for (DWORD K = 0; K < pExport->NumberOfNames; K++) {
                        char* pName = (char*) (pImage + pNames[K]);

                        if (lstrcmpA(pName, lpProcName) == 0) {
                            WORD Ordinal = pOrdinals[K];

                            if (Ordinal < pExport->NumberOfFunctions) {
                                DWORD FunctionRva = pFunctions[Ordinal];

                                if (FunctionRva < ExportDir.VirtualAddress ||
                                    FunctionRva >= ExportDir.VirtualAddress + ExportDir.Size) {
                                    hProc = (FARPROC) (pImage + FunctionRva);
                                }
                            }

                            break;
                        }
                    }
                }
            }
        }
    }

    return hProc;
}
5
Eye Candies / BassBox Radio & TV
« Last post by Patrice Terrier on June 14, 2026, 07:45:12 am »
BBR - New TV Feature (Release Candidate)

I have just completed a new TV feature for BBR (BassBox Radio).



Instead of maintaining a large list of individual TV channels, BBR now provides a visual TV catalog using thumbnails. Selecting a thumbnail opens the corresponding TV portal directly inside the integrated WebView2 browser.

Current portals include France and USA, and adding new entries is very simple.

The goal is to build a collection of useful, legal and freely accessible TV portals from different countries.

I am currently looking for suggestions, especially for:

  • Spain
  • Germany
  • Italy
  • Portugal
  • Canada
  • Belgium
  • Switzerland
  • Latin America

If you know a good TV portal that offers live television channels, please post the URL.

The TV catalog uses PNG thumbnails, so if you can suggest a suitable icon, even better.

Thank you for your help and testing.

The full VS2022 project is attached to this post.

6
Eye Candies / BassBox Radio C/C++ version 4.01
« Last post by Patrice Terrier on June 07, 2026, 01:52:12 pm »
BBR Update

Today I finished a major cleanup of the station management system.

Previously, stations automatically disabled by the validation thread and stations manually removed by the user were mixed together in the same file.

This has now been split into:

Code: [Select]
US_stations.lst   = master station list
US_favorite.lst   = favorites
US_broken.lst     = automatically detected bad URLs
US_removed.lst    = stations permanently removed by the user

This means:

  • A dead station is no longer confused with a user deletion.
  • User choices are preserved.
  • Broken stations can be rechecked independently.
  • Future restore and maintenance tools become possible.

I also added a small station statistics dialog showing:

Code: [Select]
Stations
Active
Broken
Removed
Favorites

The stream checker has also been improved and now distinguishes temporary network failures from more serious URL errors.

As usual, the attachment linked to the first post of this trhead has been updated.

A small change internally, but an important step toward making BBR more reliable and easier to maintain.
7
Eye Candies / BassBox Radio C/C++ version 4.00 (updated)
« Last post by Patrice Terrier on May 24, 2026, 01:50:10 pm »
The project code has been updated, see new attachment linked to the first post.

05-22-2026
Removing radio didn't updated the counter.
The Oscillo popup, is now a real child of the Radio TAB.

05-24-2026
Better radio icon management.
Thread detection revised when using "Update".
The tab "Radio" is now using auto column size adjustment when resizing the window.
Marquee alignment was improperly using anchor mode.
8
Eye Candies / BassBox Radio C/C++ version 4.00
« Last post by Patrice Terrier on May 21, 2026, 08:06:15 am »
BassBox Radio 4.00

Version 4.00 is a complete rewrite of BassBox Radio.

The original version was written with WinDev in 2014.
This new version has been fully rewritten in native C/C++ with a major size reduction, producing a tiny 62 KB executable.



Despite its very small size, BBR 4.00 includes:

  • Integrated Internet radio station browser
  • Favorites management
  • Country filtering
  • Embedded WebView2 browser
  • OpenGL visual plugin support
  • Realtime oscilloscope
  • GDImage/WinLIFT composited interface
  • Very low resource usage
  • Native Win32 responsiveness

The oscilloscope and rendering system are fully hardware accelerated and continue updating smoothly even while moving or resizing the window.

This version is based on my own native libraries:
  • WinLIFT
  • GDImage
  • BassBox audio engine

Everything has been designed to remain lightweight, reactive and visually clean without relying on heavy frameworks.

The full VS2022 project is attached to this post.

If you download it and test it, please give me your feedback.
I would be interested to know if you find any oddities, bugs, or if you have suggestions for improvement.


9
Eye Candies / Re: BassBox Radio (more than 33000 internet radio)
« Last post by Patrice Terrier on May 14, 2026, 09:31:34 pm »
Working on a brand new version, written in pure C/C++, to create tiny standalone binary, with great scope of features.
And compatible with the new GLSL plugins.

Stay tuned...
10
Runtime activation of Common Controls v6 (Manifest-free alternative)

For years, the standard way to enable modern Windows visual styles (ComCtl32 v6) has been through a manifest, either embedded or via:

Code: [Select]
// Include the v6 common controls in the manifest
#pragma comment(linker,""/manifestdependency:type='win32'
name='Microsoft.Windows.Common-Controls' version='6.0.0.0'
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"")

While this works, it introduces a dependency on the linker and can sometimes lead to inconsistent behavior depending on build settings, resources, or memory conditions.


Alternative: Runtime activation (no manifest required)

It is possible to activate visual styles dynamically at runtime using an activation context (ACTCTX).
This method loads the ComCtl32 v6 resources directly from shell32.dll.

Code: [Select]
static HANDLE    g_hActCtx = INVALID_HANDLE_VALUE;
static ULONG_PTR g_ulActCookie = 0;
static BOOL      g_bActCtxActive = FALSE;

static BOOL EnableVisualStylesRuntime(VOID) {
    WCHAR dir[MAX_PATH];
    DWORD cch = GetSystemDirectory(dir, MAX_PATH);
    if (!cch || cch >= MAX_PATH) return FALSE;

    ACTCTX actCtx; ClearMemory(&actCtx, sizeof(actCtx));
    actCtx.cbSize = sizeof(actCtx);
    actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
    actCtx.lpSource = TEXT("shell32.dll");
    actCtx.lpAssemblyDirectory = dir;
    actCtx.lpResourceName = MAKEINTRESOURCE(124);

    g_hActCtx = CreateActCtx(&actCtx);
    if (g_hActCtx == INVALID_HANDLE_VALUE) return FALSE;

    if (!ActivateActCtx(g_hActCtx, &g_ulActCookie)) {
        ReleaseActCtx(g_hActCtx);
        g_hActCtx = INVALID_HANDLE_VALUE;
        return FALSE;
    }

    g_bActCtxActive = TRUE;
    return TRUE;
}

static VOID DisableVisualStylesRuntime(VOID) {
    if (g_bActCtxActive) {
        DeactivateActCtx(0, g_ulActCookie);
        g_bActCtxActive = FALSE;
        g_ulActCookie = 0;
    }

    if (g_hActCtx != INVALID_HANDLE_VALUE) {
        ReleaseActCtx(g_hActCtx);
        g_hActCtx = INVALID_HANDLE_VALUE;
    }
}


Important notes

* Must be called very early (ideally at the start of wWinMain or inside your core init like skInitEngine).
* Affects all subsequently created controls (ComboBox, ListView, TreeView, etc.).
* Ensures consistent theming without relying on external manifests.
* Particularly useful in:
   - CRT-free builds
   - DLL-based UI engines (e.g. WinLIFT)
   - Custom control frameworks
* Avoid calling it after controls are already created.


Why this matters

In practice, inconsistent behavior of controls (especially owner-drawn or themed ones) often comes from:

* Missing or partial v6 activation
* Timing issues (controls created before activation)
* Resource/memory edge cases

By forcing activation at runtime, behavior becomes deterministic and uniform.


Conclusion

This approach is a reliable replacement for manifest-based activation and gives full control over when and how visual styles are enabled.

In my case, integrating this directly into the initialization phase removed all inconsistencies without requiring any manifest handling.


Tip

If you already use a framework like WinLIFT, placing this call inside the engine initialization guarantees that all controls benefit from v6 styling automatically.
Pages: [1] 2 3 ... 10