Skip to content
Lesson 8.7In-Process Tools, Input, and Integration

In-Game Menus and Text Rendering

Understand what a game's interface is made of, how its text function can draw a small menu, and why input edges, frame timing, and string lifetimes matter.

Lesson 8.7
Read or listenPrint chapter or book
About 36 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 8.7Key ideas, reference and questions

A one-page summary of “In-Game Menus and Text Rendering” you can keep beside your notes. Plain Markdown.

Lesson 8.7 — In-Game Menus and Text Rendering: cheatsheet

  • Remember: A menu advances on a press transition, rather than on every frame in which the key remains down.
  • Also useful: Drawing location and interface storage are separate choices: the same saved menu settings can drive either a separate window or an in-game text pass.

Key terms

  • CString: CString is the same tool Lesson 3.8 used at the C boundary, and for the same reason: it guarantees the terminating zero and rejects interior zero bytes instead of silently truncating your text.
  • consumed: The marker and index are consumed, not drawn, so the string's byte length is larger than the number of visible characters.
  • cursor: When cursor is 0, 0 - 1 on an unsigned integer panics in a debug build.
  • draining: While an in-flight frame is still executing the detour, the hook is draining: no new calls should enter, but the current call must finish before its code is removed.

Review

  • Q: You want a menu drawn by the game's own text function. Why is hooking that text function itself the wrong site? A: Your hook would call the text function per line, re-entering itself until the stack overflows
  • Q: Why is calling the game's own print function the cheapest way to put a menu inside the game? A: It already knows the font, scale, screen size, and the right moment in the frame
Download cheatsheet (.md)

A third option between a window and an overlay

Section titled “A third option between a window and an overlay”

Lesson 8.6 put the menu in its own window, which is the right default: it cannot corrupt the game’s graphics state, and you can close the game without losing it.

But you have already met a function that draws text inside the game. In Lesson 3.11 you traced Wesnoth’s print function; Lesson 8.3 then used its call site for a verified, reversible text hook. In Lesson 7.9 you called AssaultCube’s print function at 0x0041_9880 to label players. That function already knows the font, the scale, the screen size, and the right moment in the frame to draw. Calling it is the cheapest way to put a menu on screen, because the hard graphics work is done and you are only supplying strings.

The trade is that you are now running inside the game’s render path, so the hook must finish quickly. A file read, sleep, or blocking channel receive there delays the game’s next frame; Lesson 6.11 measures the same cost in a per-frame recorder.

Before borrowing the game’s text function, it helps to know what it sits on top of. A game’s interface is drawn by the same pipeline as its world (Lesson 7.2), in a last pass that ignores the 3D camera: flat rectangles and text, placed in screen units.

  • Text. A font is turned into a glyph atlas, a texture holding every character as a small image (the atlas idea from Lesson 7.2). To draw “Gold”, the engine draws one quad per letter, each showing that letter’s rectangle in the atlas, and moves along by each letter’s advance width. If the advances of G, o, and l are 12, 9, and 4 pixels, the letter d starts 12 + 9 + 4 = 25 pixels to the right of where the G started.
  • Layout. Elements sit in boxes. Anchoring pins a box to part of the screen: a 200 × 50 box anchored to the bottom-right corner of a 1280 × 720 screen with a 20-pixel margin is at x = 1280 − 20 − 200 = 1060 and y = 720 − 20 − 50 = 650. In a flex layout, boxes in a row share the space by weight: in a 600-pixel row, three boxes with weights 1, 2, and 1 get 150, 300, and 150 pixels. A grid layout assigns boxes to rows and columns, and size limits keep a box between a smallest and a largest size.
  • Scaling. Interfaces are usually designed for a reference size, such as 1920 × 1080, and scaled by the smaller of width ÷ 1920 and height ÷ 1080. On a 1280 × 720 screen both are 0.6667, so a 300-pixel button is drawn 200 pixels wide. The window’s scale factor from Lesson 6.9 multiplies the result again.
  • Stretchable art. A button picture with decorated corners cannot simply be stretched. Nine-slice drawing cuts it into a 3 × 3 grid: the four corners keep their size, the four edges stretch in one direction, and the centre stretches in both. Drawing a picture with 16-pixel corners as a 200 × 60 button gives four 16 × 16 corners, top and bottom edges 200 − 32 = 168 wide, side edges 60 − 32 = 28 tall, and a 168 × 28 centre.
  • Order and clipping. Elements are drawn back to front by a layer number. Clipping hides whatever falls outside a box, which is how a scrolling list works: the items are moved up by the scroll amount, and only the part inside the list’s box is drawn.
  • Input and focus. A click is tested against each box’s rectangle, the 2D picking of Lesson 7.5, and the topmost box under the cursor gets it. For a keyboard or gamepad, the game keeps a focus, the element that will respond, and moves it to the nearest element in the direction pressed. A text box keeps a string and a cursor position and receives typed characters. Sliders, checkboxes, and buttons are each a small state machine: a value, and whether they are hovered, pressed, or focused.
  • Popups. A context menu is a box created at the cursor when the right button is pressed and sized to fit its items. If it would run off the screen it is moved back: a 150 pixel wide menu opened at x = 1200 on a 1280 pixel screen is placed at min(1200, 1280 − 150) = 1130.
  • Where it is drawn. Most interface is drawn over the finished 3D picture. It can also be drawn into a texture and placed on a surface in the world, such as a screen on a wall, and a label that follows an object uses the projection from Lesson 7.9.

Lesson 8.6 contrasts the two ways such code is written: an immediate-mode interface redraws every element on every frame from the current state, and a retained-mode interface keeps a tree of elements in memory and changes them when something happens.

Hook a per-frame site, then call the text function

Section titled “Hook a per-frame site, then call the text function”

The obvious idea is to hook the text function itself. That is the one thing that cannot work:

your hook runs
you want to draw a menu line
you call the text function
...which is hooked

The hook calls the text function, and the text function runs the hook again: recursion with no base case, the same failure as a wrapper that forwards to itself in Lesson 7.3. Every nested call adds a stack frame, so the stack overflows while the hook is still trying to draw its first menu line.

Hook a site that runs once per frame instead, and call the text function from there as an ordinary function.

Lesson 7.9 already identified such a site in AssaultCube: the final print hook at 0x0040_BE7E, resuming at 0x0040_BE83. Drawing there puts your lines in the same pass as the game’s own HUD, so they inherit its ordering and never appear underneath the world:

Your text, the game’s font and frameAssaultCube’s HUD site 0x0040BE7E jumps to your detour once per frame; the print function at 0x00419880 does the drawing.
  1. Control jmp from 0x0040BE7E

    Step 1: Game’s HUD pass to Your detour

    Once per frame the game reaches the patched site while it draws its HUD, so your code runs at the right moment.

  2. Request print(x, y, pointer to text)

    Step 2: Your detour to Print function

    You supply only a position and a pointer to an owned, zero-terminated string, passed the way the function expects (cdecl or thiscall).

  3. Data one glyph quad per letter

    Step 3: Print function to This frame’s image

    The function adds what it already knows: the font atlas, the scale, the screen size and this frame’s drawing state.

  4. Reply returns

    Step 4: Print function to Your detour

    Call it again for each menu line; each call must finish quickly because the game’s frame is waiting.

  5. Control resume at 0x0040BE83

    Step 5: Your detour to Game’s HUD pass

    Replay the displaced instructions and jump back. The game finishes its HUD, so your lines appear in the same pass.

AssaultCube’s HUD site 0x0040BE7E jumps to your detour once per frame; the print function at 0x00419880 does the drawing.

Reconstruct the contract before you call it

Section titled “Reconstruct the contract before you call it”

Calling a function you found is Lesson 6.7’s problem, not a new one. Before the first call, answer its four parts:

  • which arguments it takes, and in what order — typically a screen position and a string;
  • where those arguments go, which for a 32-bit game usually means cdecl on the stack, or thiscall with the renderer object in ecx;
  • what it returns, if anything;
  • whether it copies the string or merely stores the pointer.

That last question is the one that bites. If the function keeps the pointer and draws later in the frame, a string you built on the stack is gone by then:

// ❌ The buffer dies at the end of this function. If the game reads the
// pointer later in the frame, it reads freed stack space.
let line = format!("radar [on]");
draw_text(10, 10, line.as_ptr());

Keep an owned buffer alive for at least as long as the frame, and give the function a pointer into that:

// ✅ `lines` outlives the call, and the trailing zero is explicit because a
// C-style text function reads until it finds one.
struct MenuText {
lines: Vec<std::ffi::CString>,
}

CString is the same tool Lesson 3.11 used at the C boundary, and for the same reason: it guarantees the terminating zero and rejects interior zero bytes instead of silently truncating your text.

Colour is usually a marker inside the string

Section titled “Colour is usually a marker inside the string”

Many engines let a string carry its own formatting. The mechanism is nearly always the same: a marker byte the renderer watches for, followed by an index that selects a palette entry. AssaultCube uses the form feed character, 0x0C, followed by a digit.

0C
33
52
61
64
61
72
marker (form feed)
palette index '3'
visible text: Radar
AssaultCube's colour marker in front of the string Radar. The marker and index are consumed by the renderer, not drawn, so these 7 bytes show only 5 visible characters.

Two consequences follow, and both matter.

The marker and index are consumed, not drawn, so the string’s byte length is larger than the number of visible characters. That is the same distinction Lesson 3.11 drew between bytes, code units, scalars, and what a reader sees; a menu that lines up columns by counting bytes will be crooked.

And the exact marker is a fact about one build. Confirm it by drawing a string containing a candidate byte and watching what happens, rather than trusting the number above.

text_menu_lab.rs

rust-labs/src/bin/ · Rust, 330 lines

Show the codeHide the code
//! The state machine behind an in-game text menu, with no graphics API in
//! sight.
//!
//! Drawing the menu is the easy half: you call the game's own text function
//! once per line. The half that actually goes wrong is the bookkeeping —
//! a cursor that wraps in both directions, keys that must fire once per press
//! rather than once per frame, and a render path that must not allocate.
//!
//! Everything here is portable safe Rust, so the rules can be tested without
//! attaching to anything.
//!
//! ```text
//! cargo run --bin text_menu_lab
//! cargo test --bin text_menu_lab
//! ```

/// One switchable feature in the menu.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct MenuItem {
    label: &'static str,
    enabled: bool,
}

/// What the reader pressed this frame. A real build fills this in from
/// `GetAsyncKeyState`; the lab supplies it directly so the rules stay testable.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct KeyFrame {
    up: bool,
    down: bool,
    toggle: bool,
}

/// Level-to-edge conversion. `GetAsyncKeyState` reports whether a key *is*
/// down, which is true on every frame of a single press. A menu needs to know
/// when it *became* down, so the previous frame has to be remembered.
#[derive(Clone, Copy, Debug, Default)]
struct EdgeDetector {
    previous: KeyFrame,
}

impl EdgeDetector {
    const fn new() -> Self {
        Self {
            previous: KeyFrame {
                up: false,
                down: false,
                toggle: false,
            },
        }
    }

    /// Returns the keys that went from up to down between the last frame and
    /// this one.
    fn edges(&mut self, current: KeyFrame) -> KeyFrame {
        let edges = KeyFrame {
            up: current.up && !self.previous.up,
            down: current.down && !self.previous.down,
            toggle: current.toggle && !self.previous.toggle,
        };
        self.previous = current;
        edges
    }
}

#[derive(Debug)]
struct Menu {
    items: Vec<MenuItem>,
    cursor: usize,
    open: bool,
    /// Reused every frame so the render path never allocates.
    lines: Vec<String>,
}

/// The marker byte an engine looks for when it embeds colour in a string. The
/// value is build-specific: confirm it against your target rather than
/// trusting this constant.
const COLOUR_MARKER: char = '\u{0c}';
const COLOUR_SELECTED: char = '3';
const COLOUR_ENABLED: char = '0';
const COLOUR_DISABLED: char = '4';

impl Menu {
    fn new(labels: &[&'static str]) -> Self {
        let items: Vec<MenuItem> = labels
            .iter()
            .map(|label| MenuItem {
                label,
                enabled: false,
            })
            .collect();
        let capacity = items.len();
        Self {
            items,
            cursor: 0,
            open: false,
            lines: Vec::with_capacity(capacity),
        }
    }

    /// Moves the cursor up one row, wrapping to the bottom.
    ///
    /// The wrap is the part people get wrong. `(cursor - 1) % len` looks
    /// right and underflows on an unsigned cursor the moment it is zero,
    /// which panics in debug and produces an enormous index in release.
    /// Handling the zero case explicitly says exactly what should happen.
    fn move_up(&mut self) {
        let Some(last) = self.items.len().checked_sub(1) else {
            return;
        };
        self.cursor = if self.cursor == 0 {
            last
        } else {
            self.cursor - 1
        };
    }

    /// Moves the cursor down one row, wrapping to the top.
    fn move_down(&mut self) {
        if self.items.is_empty() {
            return;
        }
        let next = self.cursor + 1;
        self.cursor = if next == self.items.len() { 0 } else { next };
    }

    fn toggle_selected(&mut self) {
        if let Some(item) = self.items.get_mut(self.cursor) {
            item.enabled = !item.enabled;
        }
    }

    /// Applies one frame of input. Only edges act, so holding a key moves the
    /// cursor exactly one row.
    fn update(&mut self, edges: KeyFrame) {
        if !self.open {
            return;
        }
        if edges.up {
            self.move_up();
        }
        if edges.down {
            self.move_down();
        }
        if edges.toggle {
            self.toggle_selected();
        }
    }

    fn is_enabled(&self, label: &str) -> bool {
        self.items
            .iter()
            .any(|item| item.label == label && item.enabled)
    }

    /// Builds the lines to hand to the game's text function.
    ///
    /// Returns a borrowed slice and clears rather than reallocates, because
    /// this runs on the render thread once per frame.
    fn render(&mut self) -> &[String] {
        self.lines.clear();
        if !self.open {
            return &self.lines;
        }
        for (index, item) in self.items.iter().enumerate() {
            let state_colour = if item.enabled {
                COLOUR_ENABLED
            } else {
                COLOUR_DISABLED
            };
            let marker = if index == self.cursor { '>' } else { ' ' };
            let colour = if index == self.cursor {
                COLOUR_SELECTED
            } else {
                state_colour
            };
            self.lines.push(format!(
                "{COLOUR_MARKER}{colour}{marker} {} [{}]",
                item.label,
                if item.enabled { "on" } else { "off" }
            ));
        }
        &self.lines
    }
}

fn main() {
    let mut menu = Menu::new(&["Radar", "Crosshair info", "Frame counter"]);
    let mut keys = EdgeDetector::new();
    menu.open = true;

    println!("1. Opened, cursor on the first row");
    for line in menu.render() {
        println!("   {}", line.replace(COLOUR_MARKER, "^"));
    }

    println!("\n2. Holding Down for three frames moves exactly one row");
    let held = KeyFrame {
        up: false,
        down: true,
        toggle: false,
    };
    for _ in 0..3 {
        let edges = keys.edges(held);
        menu.update(edges);
    }
    println!("   cursor is now {}", menu.cursor);

    println!("\n3. Releasing and pressing again moves another row");
    let _ = keys.edges(KeyFrame::default());
    menu.update(keys.edges(held));
    println!("   cursor is now {}", menu.cursor);

    println!("\n4. Toggling the selected row");
    let _ = keys.edges(KeyFrame::default());
    menu.update(keys.edges(KeyFrame {
        up: false,
        down: false,
        toggle: true,
    }));
    for line in menu.render() {
        println!("   {}", line.replace(COLOUR_MARKER, "^"));
    }

    println!(
        "   the worker would now ask: Radar enabled? {}",
        menu.is_enabled("Radar")
    );

    println!("\n5. Wrapping upward from the first row");
    menu.cursor = 0;
    menu.move_up();
    println!("   cursor wrapped to {}", menu.cursor);
}

#[cfg(test)]
mod tests {
    use super::{EdgeDetector, KeyFrame, Menu};

    fn press(down: bool, up: bool, toggle: bool) -> KeyFrame {
        KeyFrame { up, down, toggle }
    }

    #[test]
    fn the_cursor_wraps_in_both_directions() {
        let mut menu = Menu::new(&["a", "b", "c"]);
        menu.cursor = 0;
        menu.move_up();
        assert_eq!(menu.cursor, 2, "moving up from the first row wraps to last");
        menu.move_down();
        assert_eq!(
            menu.cursor, 0,
            "moving down from the last row wraps to first"
        );
    }

    #[test]
    fn a_held_key_moves_exactly_one_row() {
        let mut menu = Menu::new(&["a", "b", "c"]);
        let mut keys = EdgeDetector::new();
        menu.open = true;

        for _ in 0..10 {
            let edges = keys.edges(press(true, false, false));
            menu.update(edges);
        }
        assert_eq!(menu.cursor, 1, "holding Down must not scroll every frame");
    }

    #[test]
    fn releasing_and_pressing_again_moves_a_second_row() {
        let mut menu = Menu::new(&["a", "b", "c"]);
        let mut keys = EdgeDetector::new();
        menu.open = true;

        menu.update(keys.edges(press(true, false, false)));
        menu.update(keys.edges(KeyFrame::default()));
        menu.update(keys.edges(press(true, false, false)));
        assert_eq!(menu.cursor, 2);
    }

    #[test]
    fn a_closed_menu_ignores_input_and_draws_nothing() {
        let mut menu = Menu::new(&["a", "b"]);
        let mut keys = EdgeDetector::new();

        menu.update(keys.edges(press(true, false, true)));
        assert_eq!(menu.cursor, 0);
        assert!(!menu.is_enabled("a"));
        assert!(menu.render().is_empty());
    }

    #[test]
    fn toggling_changes_only_the_selected_row() {
        let mut menu = Menu::new(&["a", "b"]);
        let mut keys = EdgeDetector::new();
        menu.open = true;

        menu.update(keys.edges(press(false, false, true)));
        assert!(menu.is_enabled("a"));
        assert!(!menu.is_enabled("b"));
    }

    #[test]
    fn rendering_reuses_its_buffer() {
        let mut menu = Menu::new(&["a", "b", "c"]);
        menu.open = true;

        let first = menu.render().len();
        let capacity_after_first = menu.lines.capacity();
        let second = menu.render().len();

        assert_eq!(first, second);
        assert_eq!(
            capacity_after_first,
            menu.lines.capacity(),
            "the render path must not reallocate every frame"
        );
    }

    #[test]
    fn every_line_carries_a_colour_marker() {
        let mut menu = Menu::new(&["a", "b"]);
        menu.open = true;
        assert!(
            menu.render()
                .iter()
                .all(|line| line.starts_with(super::COLOUR_MARKER))
        );
    }
}

Everything above is about getting characters on screen. The part that actually misbehaves is the bookkeeping, so text_menu_lab.rs models it in portable safe Rust with no graphics API involved:

Terminal window
cd rust-labs
cargo run --bin text_menu_lab
cargo test --bin text_menu_lab

A wrapping cursor underflows if you write the obvious thing

Section titled “A wrapping cursor underflows if you write the obvious thing”

Moving the cursor up from the first row should land on the last. The natural expression is wrong:

self.cursor = (self.cursor - 1) % self.items.len(); // ❌

When cursor is 0, 0 - 1 on an unsigned integer panics in a debug build. A release build wraps it to usize::MAX, and % len then keeps the result inside the list, but usually on the wrong row:

Predict first

A release build wraps 0 - 1 on usize to usize::MAX = 18,446,744,073,709,551,615. The menu has 3 rows and the cursor is on row 0.

Pressing up runs (cursor - 1) % len. Which row is selected?

Answer: Row 0. The digits of 18,446,744,073,709,551,615 add up to 87 = 3 × 29, so the number is a multiple of 3 and the remainder is 0. The cursor does not move at all.

Moving the cursor up from row 0The obvious code is (cursor - 1) % len on usize. Change the number of rows, the cursor and the build.

Pressing up with the cursor on row 0

3 menu rows

Before
row 0
row 1
row 2
(cursor - 1) % len
row 0
row 1
row 2
Explicit check
row 0
row 1
row 2

The arithmetic on usize

cursor - 1
0 − 1 wraps to usize::MAX = 18,446,744,073,709,551,615
% len
18,446,744,073,709,551,615 % 3 = 0

18,446,744,073,709,551,615 % 3 = 0. The remainder keeps the index inside the list, but it is row 0, not the last row, 2.

Handle the boundary explicitly and the intent is visible:

self.cursor = if self.cursor == 0 { last } else { self.cursor - 1 };

GetAsyncKeyState reports a level, not a press

Section titled “GetAsyncKeyState reports a level, not a press”

GetAsyncKeyState answers “is this key down right now?”, which is true on every frame of one press, so a menu that acts on it scrolls the whole list in a fraction of a second. Act on the edge instead, the frame where the key changed from up to down, as Lesson 8.6’s menu hotkey does:

let pressed = current.down && !previous.down;

The lab test holds the key for ten frames and asserts the cursor moved exactly one row.

Build the strings without allocating every frame

Section titled “Build the strings without allocating every frame”

The render path runs thousands of times per second. Clear and reuse one buffer rather than allocating a fresh Vec per frame; the lab asserts the buffer’s capacity is unchanged after a second render.

The menu should also own only its own state. Keep Lesson 8.6’s split: the menu produces settings and commands, and the worker reads them. A menu that writes game memory while the render thread draws puts a second writer on the same data.

Move a menu cursor through its boundary. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

rows = 4; cursor = selected_row
delta = -1 for up, +1 for down
shifted = cursor + delta + rows
next = shifted % rows
cursor = next
highlight the valid selected row

Result: row 0 moves up to row 3. Wrapping did not create an unsigned underflow or an out-of-list index.

Final values: cursor = 0; direction = up; delta = -1; shifted = 3; next = 3; selected_row = 3.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.

    cursor = 0; direction = up.

  2. Step 2, line 2. Calculate the direction using a signed temporary value.

    cursor = 0; direction = up; delta = -1.

  3. Step 3, line 3. Add the row count before taking the remainder, keeping this intermediate nonnegative.

    cursor = 0; direction = up; delta = -1; shifted = 3.

  4. Step 4, line 4. The remainder selects a row from zero through three.

    cursor = 0; direction = up; delta = -1; shifted = 3; next = 3.

  5. Step 5, line 5. Store the checked row.

    cursor = 0; direction = up; delta = -1; shifted = 3; next = 3; selected_row = 3.

    Memory: row 0 = unselected; row 1 = unselected; row 2 = unselected; row 3 = selected.

  6. Step 6, line 6. Result: row 0 moves up to row 3. Wrapping did not create an unsigned underflow or an out-of-list index.

    cursor = 0; direction = up; delta = -1; shifted = 3; next = 3; selected_row = 3.

    Memory: row 0 = unselected; row 1 = unselected; row 2 = unselected; row 3 = selected.

Closing the menu is a display change. Unloading the tool is a lifetime change with a fixed order: stop the worker, wait for it to leave the hooked path, restore the displaced bytes, then release the DLL. While an in-flight frame is still executing the detour, the hook is draining: no new calls should enter, but the current call must finish before its code is removed. Lesson 8.10 applies the same rule to a general hook manager.

Combined tools running in AssaultCubeSilent demonstration · 12 seconds

The original multihack recording combines features from the preceding AssaultCube lessons. Compare their visible effects with the independent settings a menu exposes: changing a setting and drawing that setting are separate responsibilities.

Download Combined tools running in AssaultCube video

This is the same offline, version-pinned lab used since Chapter 7, and the same menu model as Lesson 8.6 rendered somewhere else. The transferable skill — calling a function you recovered, with a contract you proved, from a hook that runs at a known point in a frame — is what a debug HUD, a benchmarking overlay, and an accessibility tool all do.

Knowledge check

Pick the hook site

You want a menu drawn by the game's own text function. Why is hooking that text function itself the wrong site?

Finished Lesson 8.7?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 8: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.