Skip to content

ButtonIcons and Picture

Bartholomew Ho edited this page Nov 9, 2026 · 1 revision

ButtonIcons and Picture

ButtonIcons shows the controller's button pictures in the menus and on the HUD as something else. A keyboard mod uses it to show its keys ("Enter" instead of A) in the help bar and in prompts. Picture reads a PNG, resizes it, and turns it into the game's texture file, so you can ship your own button pictures as PNGs.

Thread: Enable, Refresh and Override work from any thread. Your pick functions are called on the UI thread. Picture works on any thread.

Example

Turns Key_Enter.png (shipped in your mod's folder) into a game texture, and shows it wherever the game shows the A button.

using FH2;
using FH2ModPlatform;

public sealed class KeyIcons : Mod
{
    public override ModInfo Info => new("KeyIcons", "1.0.0");

    protected override void OnInit()
    {
        var pic = Picture.FromPng(File.ReadAllBytes(Path.Combine(ModFolder, "Key_Enter.png")));
        if (pic == null) return;
        SetFile("UI/textures/data_bound/KeyIcons/Key_Enter.swatchbin", pic.Resize(64, 64).ToSwatchbin());

        ButtonIcons.Enable(req => req.Button == "A" ? new ButtonIcon("KeyIcons/Key_Enter") : null);
    }
}
Imports System.IO
Imports FH2
Imports FH2ModPlatform

Public Class KeyIcons
    Inherits [Mod]

    Public Overrides ReadOnly Property Info As ModInfo
        Get
            Return New ModInfo("KeyIcons", "1.0.0")
        End Get
    End Property

    Protected Overrides Sub OnInit()
        Dim pic = Picture.FromPng(File.ReadAllBytes(Path.Combine(ModFolder, "Key_Enter.png")))
        If pic Is Nothing Then Return
        SetFile("UI/textures/data_bound/KeyIcons/Key_Enter.swatchbin", pic.Resize(64, 64).ToSwatchbin())

        ButtonIcons.Enable(Function(req As ButtonIconRequest) As ButtonIcon?
                               If req.Button = "A" Then Return New ButtonIcon("KeyIcons/Key_Enter")
                               Return Nothing
                           End Function)
    End Sub
End Class

ButtonIcons

Member What it does
IsReady (property, bool) True once the pictures can be swapped (the game's code is as expected).
Enable(Func<ButtonIconRequest, ButtonIcon?> pick) (method) From now on, pick says what each button shows. Return null for the controller's own picture. It's asked each time the game shows a button.
Refresh() (method) Shows the pictures on screen again, asking your pick function anew, at the next UI frame. Call it when your answers change (the player rebinds a key).
Override(string controlType, string resource, Func<ButtonIcon?> pick) (method) Swaps a picture that another control keeps as a resource of its own. For example FH2 ANNA's HUD: control "HUDAnnaControl", resource "FH2ANNA_Icon_Open". Return null for its own picture. Asked again at each Refresh.

ButtonIconRequest (record struct)

What the game asks for.

Member What it does
Button (property, string) The button: "A", "X", "Menu", "View", "LB", "LT", "L3", "DPadUp", "DPadUpDown"...
Large (property, bool) True for the large picture (the HUD's).
Place (property, ButtonIconPlace) Where it's shown.

ButtonIcon (record struct)

The picture to show instead.

Member What it does
ButtonIcon(string Texture, float Aspect = 1) (constructor) A texture you've written under ui/textures/data_bound, named without the extension ("MyKeys/Key_0D"), and how many times as wide as high it is.
Texture (property, string) The texture's name.
Aspect (property, float) Width divided by height. Only used in the help bar (up to 4).

ButtonIconPlace (enum)

Value Meaning
HelpBar The help bar ("(A) Select (B) Back"). The picture may be wider than high (up to 4 times).
HudPrompt The HUD's prompts while driving ("(X) Enter garage"). A square.
Page A picture a page names itself ("PRESS (A) TO START", the tab strips' LB/RB). A square.

Picture

A picture in memory, in BGRA with the colour premultiplied by alpha (as the game's UI textures are).

Member What it does
Picture(int width, int height, byte[]? bgra = null) (constructor) A picture of that size. Without bgra, it starts fully transparent. Throws ArgumentException if bgra isn't width * height * 4 bytes.
Width (property, int) Its width in pixels.
Height (property, int) Its height in pixels.
Bgra (property, byte[]) The pixels, in rows from the top: blue, green, red, alpha, with the colour premultiplied by alpha. You can change them.
FromPng(byte[] png) (static method, returns Picture?) Reads a PNG, or null if it can't be read.
Resize(int width, int height) (method, returns Picture) A new picture at another size. Smaller: each pixel is the average of what it covers. Larger: smoothly stretched.
ToSwatchbin() (method, returns byte[]) The game's texture file (.swatchbin) of it, for SetFile under ui/textures/.... In XAML it's then shown as "/ForzaUI;component/Textures/....png".

Notes

  • Write your textures with SetFile in OnInit, so they're in place before the game reads them. Use the same name in ButtonIcon, without ui/textures/data_bound/ and without the extension.
  • Your pick function is called often (each time the game shows a button picture). Keep it quick: look the answer up, don't load files there.
  • If a pick function throws, the error goes to platform.log and the controller's own picture is shown.
  • Enable waits for Menus to be ready (up to a minute), so you can call it from OnInit.
  • VB: a pick function has to return ButtonIcon? (a nullable). Write it as a multi-line Function(...) As ButtonIcon? as in the example. If(cond, New ButtonIcon(...), Nothing) gives an empty ButtonIcon instead of Nothing.
  • FromPng reads PNGs with 8 bits per channel, or with a palette. It doesn't read 16-bit or interlaced PNGs (it returns null).
  • ToSwatchbin throws InvalidOperationException if a side isn't a multiple of 8. Resize first if needed.
  • Unsupported game build: if the help bar's code isn't as expected, ButtonIcons switches itself off, IsReady stays false, and platform.log says "ButtonIcons off: ...". The game then shows the controller's pictures. If only the HUD prompts don't match, platform.log says "ButtonIcons: the HUD's prompts keep the controller's buttons", and the rest still works.

See also

Clone this wiki locally