Building a UEFI Brick Breaker Game with AI Assistance: A Practical Roadmap

    This article is a rewritten version of an earlier experiment log. The original experiment described building a UEFI brick breaker game without a starter codebase and without hand-writing code, but it provided no code, build logs, or verifiable results. Rather than claiming a success rate or efficiency gains, this article breaks the implementation into executable steps, and for each step lists the key APIs, commands, and common failure causes. Commands follow the standard EDK II workflow; run them in your own environment and record the actual output.

    This article is a rewritten version of an earlier experiment log. The original experiment described building a UEFI brick breaker game without a starter codebase and without hand-writing code, but it provided no code, build logs, or verifiable results. Rather than claiming a success rate or efficiency gains, this article breaks the implementation into executable steps and, for each step, lists the key APIs, commands, and common failure causes. Commands follow the standard EDK II workflow; run them in your own environment and record the actual output.

    1. Environment and Toolchain

    The target program is a graphical brick breaker game that runs under the UEFI Shell with Chinese text support. It is built as a EFI application named BrickBreaker.efi. Copy it to a FAT32-formatted USB drive and it can be launched from the Shell.

    Recommended environment:

    • Host machine: Ubuntu 22.04 LTS x86_64. Windows users can use the same Ubuntu version inside WSL2.
    • EDK II: use the stable tag edk2-stable202408. Confirm the version with git describe --tags before building.
    • Tools: Python 3.8 or later, NASM 2.14 or later, iASL, and GCC 11 or later. The default GCC in Ubuntu 22.04 is sufficient.
    • VM validation: QEMU 6.2 or later, paired with OVMF firmware.

    Install dependencies:

    1. sudo apt update
    2. sudo apt install -y build-essential uuid-dev iasl git nasm python3 python-is-python3 qemu-system-x86 ovmf

    Fetch and initialize EDK II:

    1. git clone https://github.com/tianocore/edk2.git
    2. cd edk2
    3. git checkout edk2-stable202408
    4. git submodule update --init --recursive
    5. make -C BaseTools
    6. source edksetup.sh

    edksetup.sh sets the WORKSPACE environment variable. Run all subsequent commands from the edk2 root directory.

    2. Program Structure and Key Implementation

    2.1 Project Layout

    Create a BrickPkg directory under the edk2 root with the following structure:

    1. BrickPkg/
    2. BrickPkg.dec
    3. BrickPkg.dsc
    4. App/
    5. BrickBreaker.inf
    6. BrickBreaker.c
    7. Font16.c
    8. Font16.h

    BrickPkg.dec only needs the basic package declarations:

    1. [Defines]
    2. DEC_SPECIFICATION = 0x00010005
    3. PACKAGE_NAME = BrickPkg
    4. PACKAGE_GUID = 3E8A4C21-5B7D-4F90-A1E6-2D9C8B7A6F15
    5. PACKAGE_VERSION = 0.1

    BrickPkg.dsc declares the platform and library instances:

    1. [Defines]
    2. PLATFORM_NAME = BrickPkg
    3. PLATFORM_GUID = 9C2F6A10-3B7E-4D21-8E55-1A2B3C4D5E6F
    4. PLATFORM_VERSION = 0.1
    5. DSC_SPECIFICATION = 0x00010005
    6. OUTPUT_DIRECTORY = Build/BrickPkg
    7. SUPPORTED_ARCHITECTURES = X64
    8. BUILD_TARGETS = DEBUG|RELEASE
    9. SKUID_IDENTIFIER = DEFAULT
    10. [LibraryClasses]
    11. BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
    12. BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
    13. PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
    14. DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
    15. MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
    16. UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
    17. UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
    18. UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
    19. UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
    20. [Components]
    21. BrickPkg/App/BrickBreaker.inf

    BrickBreaker.inf describes the module type and source files:

    1. [Defines]
    2. INF_VERSION = 0x00010005
    3. BASE_NAME = BrickBreaker
    4. FILE_GUID = 7B3D1E52-6A0C-4F8B-9E21-3C5D7A9B1F04
    5. MODULE_TYPE = UEFI_APPLICATION
    6. VERSION_STRING = 0.1
    7. [Sources]
    8. BrickBreaker.c
    9. Font16.c
    10. [Packages]
    11. MdePkg/MdePkg.dec
    12. [LibraryClasses]
    13. UefiApplicationEntryPoint
    14. UefiLib
    15. UefiBootServicesTableLib
    16. MemoryAllocationLib
    17. BaseMemoryLib
    18. PrintLib

    Regenerate sample GUIDs such as FILE_GUID and PACKAGE_GUID with uuidgen; do not reuse the values shown here. The entry function name is fixed as UefiMain and is invoked by the UefiApplicationEntryPoint library, so no separate ENTRY_POINT needs to be specified in the INF.

    2.2 Graphics Output: GOP and the Back Buffer

    UEFI has no GPU driver stack; graphics output relies on the Graphics Output Protocol (GOP). Obtain the protocol through the Boot Services LocateProtocol call:

    1. #include <Uefi.h>
    2. #include <Protocol/GraphicsOutput.h>
    3. #include <Library/UefiBootServicesTableLib.h>
    4. #include <Library/MemoryAllocationLib.h>
    5. static EFI_GRAPHICS_OUTPUT_PROTOCOL *mGop;
    6. static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mBackBuffer;
    7. static UINTN mWidth;
    8. static UINTN mHeight;
    9. EFI_STATUS
    10. InitGraphics (VOID)
    11. {
    12. EFI_STATUS Status;
    13. Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&mGop);
    14. if (EFI_ERROR (Status)) {
    15. return Status;
    16. }
    17. mWidth = mGop->Mode->Info->HorizontalResolution;
    18. mHeight = mGop->Mode->Info->VerticalResolution;
    19. mBackBuffer = AllocatePool (mWidth * mHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
    20. if (mBackBuffer == NULL) {
    21. return EFI_OUT_OF_RESOURCES;
    22. }
    23. return EFI_SUCCESS;
    24. }

    Do not write to video memory directly. Instead, draw into a back buffer and commit each frame in one Blt call. The reason is that the GOP PixelFormat may be PixelBltOnly, in which case FrameBufferBase is invalid and the screen is accessible only through Blt. A back buffer works under all pixel formats. At 1024×768 the back buffer is roughly 3 MB, so it must be allocated with AllocatePool, not placed on the stack.

    Pixel writing and frame submission:

    1. STATIC VOID
    2. PutPixel (UINTN X, UINTN Y, EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color)
    3. {
    4. if (X < mWidth && Y < mHeight) {
    5. mBackBuffer[Y * mWidth + X] = Color;
    6. }
    7. }
    8. VOID
    9. FlushFrame (VOID)
    10. {
    11. mGop->Blt (mGop, mBackBuffer, EfiBltBufferToVideo, 0, 0, 0, 0, mWidth, mHeight, 0);
    12. }

    The fields of EFI_GRAPHICS_OUTPUT_BLT_PIXEL are ordered Blue, Green, Red, Reserved — the opposite of the common RGB order. Keep this in mind when writing colors.

    2.3 Non-Blocking Keyboard Input

    Under the Shell, key reading is done with ReadKeyStroke. When no key is pressed it returns EFI_NOT_READY without blocking:

    1. EFI_STATUS
    2. ReadKey (EFI_INPUT_KEY *Key)
    3. {
    4. return gST->ConIn->ReadKeyStroke (gST->ConIn, Key);
    5. }

    The game needs only three keys: move left, move right, and quit. The corresponding scan codes are defined in the UEFI specification:

    • SCAN_LEFT, value 0x0004
    • SCAN_RIGHT, value 0x0003
    • SCAN_ESC, value 0x0017

    The main loop runs on a fixed tick:

    1. UINTN Quit = 0;
    2. while (!Quit) {
    3. ProcessInput ();
    4. UpdateGame ();
    5. DrawScene ();
    6. FlushFrame ();
    7. gBS->Stall (16000); /* microseconds; roughly 60 fps, actual rate depends on per-frame draw time */
    8. }

    Two cautions. First, do not call WaitForEvent on the WaitForKey event inside the loop — it freezes the screen. Second, UEFI provides no key-release event, so left/right movement depends on the firmware’s key repeat rate, which feels noticeably laggy. For smoother movement, keep a short-lived movement state after each key press.

    All game logic uses integers, not floating point. The default EDK II build does not link the runtime libraries needed for floating-point operations, and floating-point behavior varies across firmware. Represent speed and coordinates with fixed-point numbers, for example in units of 1/16 pixel.

    2.4 Chinese Text: a Bitmap Font

    UEFI’s default character output has limited support for Chinese, and the HII font protocol may not exist. The approach here is to embed only the Chinese characters used in the game UI and render them pixel by pixel.

    The glyph structure is a 16×16 bitmap, 32 bytes per character, two bytes per row, with the high bit on the left:

    1. typedef struct {
    2. CHAR16 Code;
    3. UINT8 Bitmap[32];
    4. } GLYPH16;
    5. CONST GLYPH16 mGlyphs[] = {
    6. { L'score', { 0x00, 0x00, 0x10, 0x20, /* ...remaining 28 bytes generated by script... */ } },
    7. /* other characters */
    8. };

    The drawing function checks each bit to decide whether a pixel is lit:

    1. VOID
    2. DrawGlyph (UINTN X, UINTN Y, CHAR16 Ch, EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color)
    3. {
    4. CONST GLYPH16 *G;
    5. UINTN Row;
    6. UINTN Col;
    7. UINT16 Bits;
    8. G = FindGlyph (Ch);
    9. if (G == NULL) {
    10. return;
    11. }
    12. for (Row = 0; Row < 16; Row++) {
    13. Bits = (UINT16)((G->Bitmap[Row * 2] << 8) | G->Bitmap[Row * 2 + 1]);
    14. for (Col = 0; Col < 16; Col++) {
    15. if (Bits & (0x8000 >> Col)) {
    16. PutPixel (X + Col, Y + Row, Color);
    17. }
    18. }
    19. }
    20. }

    FindGlyph can use a linear search; performance is adequate for a small character set.

    Glyphs are generated by a Python script. The script renders characters with PIL, and the font file must permit embedding — for example, a font licensed under SIL OFL. Verify the license before use:

    1. from PIL import Image, ImageDraw, ImageFont
    2. CHARS = "score lives game over press key restart quit exit" # only characters used in the UI
    3. font = ImageFont.truetype("NotoSansCJKsc-Regular.otf", 16)
    4. for ch in CHARS:
    5. img = Image.new("1", (16, 16), 0)
    6. ImageDraw.Draw(img).text((0, 0), ch, font=font, fill=1)
    7. rows = []
    8. for y in range(16):
    9. byte_pair = 0
    10. for x in range(16):
    11. byte_pair = (byte_pair << 1) | (1 if img.getpixel((x, y)) else 0)
    12. rows.append(byte_pair)
    13. # write rows as bytes in the GLYPH16 array, high byte first per row

    PIL’s text offsets and font metrics can shift glyphs off-center; adjust the offsets based on the actual rendered output.

    Chinese strings appear in source code as L"..." wide-character literals. EDK II’s GCC build configuration requires 16-bit wide characters, i.e. UTF-16. Before building, check that the GCC toolchain entry in Conf/tools_def.txt includes -fshort-wchar. Without it, a wide-character literal becomes 4-byte characters and the rendered output will be garbled.

    3. Building, Packaging, and Verification

    3.1 Building

    1. cd edk2
    2. source edksetup.sh
    3. build -a X64 -t GCC5 -b DEBUG -p BrickPkg/BrickPkg.dsc -m BrickPkg/App/BrickBreaker.inf

    The -m flag builds only the specified module, which suits iterative development. Build output goes under the Build directory; the exact path varies by EDK II version and target configuration. Locate it with:

    1. find Build -name BrickBreaker.efi

    After building, check the file type:

    1. file BrickBreaker.efi

    The output should contain PE32+ executable (EFI application) and x86-64. If it shows something else, the entry point or module type is wrong — go back and check the INF file.

    3.2 VM Validation

    Before testing on real hardware, run the program in QEMU. Place the binary in a FAT directory layout so OVMF can boot it automatically:

    1. mkdir -p esp/EFI/BOOT
    2. cp BrickBreaker.efi esp/EFI/BOOT/BOOTX64.EFI
    3. qemu-system-x86_64 -machine q35 -m 256 \
    4. -vga std \
    5. -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
    6. -drive format=raw,file=fat:rw:esp

    The OVMF_CODE path may differ by distribution, for example OVMF_CODE.fd. -vga std ensures GOP is available.

    3.3 Real-Hardware Validation

    1. Format a USB drive as FAT32 and copy BrickBreaker.efi to its root.
    2. Select UEFI Shell in the boot menu of the test machine, or use an existing Shell entry.
    3. In the Shell, run map -r to find the file system number assigned to the USB drive, typically fs0: or fs1:.
    4. Switch to that number and run the program:
    1. fs0:
    2. BrickBreaker.efi

    Unsigned EFI programs are rejected when Secure Boot is enabled. You can temporarily disable Secure Boot on the test machine and note this setting in your test record. Restore the original setting after testing.

    This program only writes image data to the frame buffer or the Blt interface; it does not read or write disks or flash firmware. Still, run it only on a test machine — do not install it into the EFI system partition of your main system.

    4. Failure Cases and Troubleshooting

    The issues below are common situations in EDK II development, not measured results from the original experiment. Each item includes a troubleshooting direction.

    1. LocateProtocol returns EFI_NOT_FOUND. The firmware provides no GOP, which is common on servers without display output or environments using serial redirection. In QEMU, add -vga std; on real hardware, confirm display output is not redirected.

    2. Program runs but the screen is all black. Two common causes. One is writing directly to FrameBufferBase when the GOP pixel format is PixelBltOnly — the address is invalid in that case. The other is computing pixel indices with HorizontalResolution instead of PixelsPerScanLine. Fix both by using a back buffer plus Blt, and use PixelsPerScanLine for row offsets.

    3. Chinese text shows as blocks or garbled characters. Possible causes: a missing glyph, reversed byte order, a source file not saved as UTF-8, or wide characters not being 16 bits. Check in order: the glyph table contains the character, bit order is high-bit-left, source file encoding, and the -fshort-wchar flag.

    4. Linker reports undefined reference. Usually floating-point operations in the code, or a symbol from a runtime library. Remove float and double and use fixed-point integers.

    5. Sluggish key response or stuttering. Common causes: calling WaitForEvent in the loop, or an overly long Stall. Switch to direct polling of ReadKeyStroke and measure per-frame draw time.

    6. Security violation on launch or program rejected. Secure Boot blocked the unsigned program. Temporarily disable Secure Boot on the test machine and retry.

    7. Random crashes. Common cause: placing the ~3 MB back buffer on the stack. Allocate it with AllocatePool and check the result.

    8. Build errors about missing library instances. Add the missing library declarations to [LibraryClasses] in BrickPkg.dsc per the error message, then rebuild.

    5. Evaluation and Limitations

    The original article provided no data on completion rate, time spent, lines of code, or number of fixes, so this article offers no such numbers and no claimed efficiency multiplier.

    When reproducing, record the following metrics so results can be compared:

    • Number of prompts per round of AI interaction;
    • Number of compile errors and rounds needed to fix them;
    • Number of lines manually edited;
    • Time to first correct display in the VM;
    • Pass/fail results on the VM and at least two real machines.

    The original experiment used an AI coding subscription from a cloud provider and GLM-series models. This article runs no controlled comparison between models, so it makes no judgment about which models are better for UEFI development or their coding accuracy.

    The program in this article runs only at the UEFI Shell application layer. It does not involve DXE drivers, firmware integration, flashing, or interrupt handling. A separate review is required before using it in real firmware.

    Testing should be completed in a VM first, then on real machines booted from a USB drive. Do not write to the system disk’s EFI partition or flash firmware during real-machine testing.

    6. Reproduction Checklist

    • Pin the edk2 version and record the output of git describe --tags.
    • Regenerate all GUIDs with uuidgen.
    • The glyph table contains only UI characters, and the font license permits embedding.
    • Confirm the -fshort-wchar flag is in effect.
    • Use file to confirm the binary is a PE32+ EFI application.
    • Test in a VM before moving to real hardware.
    • Record Secure Boot settings when disabling it, and restore them afterward.
    • Record each failure’s symptoms, exact error messages, and fixes to build your own reproduction log.