Catalog PyPI
PACKAGE EXPLORERView in registry

agbcia

PyPI

Pure-Python library and CLI for injecting GBA ROMs into 3DS CIA files, with custom Home Menu icons, banners, and sound.

Versions collected1
Direct dependencies3From release 0.1.3
Last observed20 Oct, 21:20 UTC

README

Version 0.1.3 documentation

agbcia

PyPI CI License: GPL v3+ Python 3.12+

A pure-Python library and CLI for injecting Game Boy Advance ROMs into installable 3DS CIA files — a free, open-source alternative to NSUI/vcoven — with custom Home Menu icons, animated banners, sound, and box art.

Everything is implemented from scratch in Python, cross-checked against public documentation (3dbrew, GBATEK) and the real behavior of existing open-source tools (bannertool, 3dstool, vcoven, pyctr, SPICA). No C/C++ binaries are bundled or shelled out to.

Table of contents

What this does

agbcia takes a GBA ROM and produces a .cia file you can install on a real (or emulated) 3DS/2DS with any standard CIA installer. It supports two independent ways of getting the GBA game to actually run once installed:

  • native mode reuses the 3DS's own built-in GBA Virtual Console firmware (AGB_FIRM) — exactly the mechanism Nintendo's own GBA VC releases use. This needs a one-time-extracted boot logo asset from a GBA VC CIA you already own (see Getting the assets you'll need).
  • homebrew mode wraps your ROM around an existing, already-working forwarder/emulator core CIA (for example a compiled 3DS build of mGBA). No donor CIA or boot logo needed, at the cost of running through an emulator instead of real GBA VC firmware.

Either way, you get to set your own title name, icon, animated banner, and banner sound — the same customization NSUI/vcoven-style injectors offer, built independently and kept in pure Python.

Features

  • Two injection modes (native/AGB_FIRM and homebrew), described above.
  • Custom Home Menu icon (SMDH, both grid sizes) and a real, animated 3D banner (CGFX) with custom looping sound (CWAV) — not just a static image.
  • Donor-banner patching: reuse a real GBA VC title's own animated 3D banner scene (mesh, skeleton, rotation animation, materials, shaders) and patch only your own box-art image, an optional bottom-screen badge image, and an optional box-shell color onto it. See The donor-banner feature.
  • GBA save-type (EEPROM/SRAM/FLASH512/FLASH1M/none) and real-time-clock auto-detection, both overridable.
  • Auto-derived product code and TMD save-data size, both overridable.
  • Fully stateless library and CLI: no config files, no cached keys or donor material between runs — every invocation takes everything it needs as explicit input.
  • Lower-level building blocks (icon/banner building, footer building, boot logo extraction, key verification, ROM/CIA inspection) are all exposed as their own standalone commands too, not hidden behind one big inject step.
  • A CLI usable entirely standalone; a separate webapp, agbcia-web, builds on top of this library for people who'd rather not use a terminal.

Requirements

  • Python 3.12 or later.
  • For native mode: a donor GBA Virtual Console CIA you legitimately own (used once, locally, to extract a boot logo), and your own ARM9 bootROM dump (boot9.bin/boot9_prot.bin).
  • For homebrew mode: a working forwarder/emulator core CIA for the 3DS (e.g. a compiled mGBA-3DS build). No donor CIA or bootROM dump needed unless that core CIA happens to be encrypted.
  • Nothing from Nintendo is ever bundled with this project or fetched automatically — every donor/key asset above is something you already have and point the CLI at explicitly, every time.

Installation

Published on PyPI:

# As a standalone CLI tool (recommended for most users):
pipx install agbcia
# or, with uv:
uv tool install agbcia

# As a library, into your own project:
uv add agbcia
pip install agbcia

To install straight from the repository instead (e.g. an unreleased commit on main), replace agbcia above with git+https://github.com/marco-zanella/agbcia.

To work on agbcia itself, see Development instead.

Quick start

# One-time: extract the AGB_FIRM boot logo from a donor GBA VC CIA you own.
agbcia footer extract-logo --donor-cia donor.cia --boot9 boot9.bin --output logo.bin

# Native/Virtual Console-style mode:
agbcia inject native \
  --rom game.gba --icon icon.png --banner-image banner.png --banner-sound banner.wav \
  --boot-logo logo.bin --title-name "My Game" \
  --title-id 0004000000f09900 --output game.cia

# Homebrew-emulator mode (no donor CIA/boot logo needed):
agbcia inject homebrew \
  --rom game.gba --icon icon.png --banner-image banner.png \
  --emulator-core mgba-3ds-forwarder.cia --title-name "My Game" \
  --title-id 0004000012345678 --output game.cia

--title-id is always mandatory — agbcia never invents one for you (see Command reference for why, and for the range it must fall in for native mode).

Command reference

Every command also accepts --help for the same information Typer generates directly from the code, which is always the authoritative source if this section and the CLI ever disagree.

agbcia inject native

Builds a complete AGB_FIRM/Virtual Console-style CIA.

FlagRequiredDescription
--romyesThe GBA ROM to inject.
--iconyesHome Menu icon image.
--banner-imageyesHome Menu banner image.
--boot-logoyesAlready-extracted AGB_FIRM boot logo (agbcia footer extract-logo).
--title-nameyesDisplay title shown on the Home Menu.
--title-idyes16-hex-digit title ID. Must be in Nintendo's real GBA VC range: 0004000000F???00 (only the F??? nibbles are free — see below).
--outputyesWhere to write the finished .cia.
--long-titlenoLong description shown on the bottom screen. Defaults to --title-name.
--publishernoPublisher name shown on the bottom screen. Default: Homebrew.
--banner-soundnoBanner sound (WAV). A short silence is used if omitted.
--donor-bannernoA real GBA VC banner extracted from a donor CIA's ExeFS, to patch --banner-image onto instead of this project's own flat template. See The donor-banner feature.
--bottom-badgenoBottom-screen badge image, patched onto a --donor-banner's own badge texture. Only applies alongside --donor-banner; left blank if omitted.
--box-shell-colornoRRGGBB hex color, patched onto a --donor-banner's box-shell texture. Only applies alongside --donor-banner; left as the donor's own color if omitted.
--save-typenoOverride the auto-detected save type: none, eeprom, sram, flash512, or flash1m.
--rtc / --no-rtcnoOverride the auto-detected real-time-clock presence.
--product-codenoOverride the auto-derived product code (derived from the ROM's game code, e.g. CTR-P-BPEE; falls back to CTR-P-AGBC if the ROM has no 4-character game code).
--title-versionnoTMD title version, default 0. Bump this on a reinstall over the same title ID — some installers/Application Manager treat an unchanged version as already up to date and skip replacing the installed content.

Why --title-id has to fall in a specific range: the 3DS decides whether to boot a title through AGB_FIRM's GBA-save-compatible path from the title ID's own numeric value, not just its NCCH content. Title IDs outside 0004000000F???00 fail to launch even with an otherwise byte-for-byte correct CIA, so agbcia rejects them up front instead of letting you find out on a console.

agbcia inject homebrew

Builds a complete CIA around an existing forwarder/emulator core. Same flags as native above, except:

FlagRequiredDescription
--emulator-coreyes, instead of --boot-logoA full, already-working forwarder/emulator .cia (e.g. a compiled mGBA-3DS build). Its own .code/extended header are reused verbatim.
--title-idyes16-hex-digit title ID. Homebrew mode has no range restriction — it never boots through AGB_FIRM.
--boot9noYour ARM9 bootROM dump. Only needed if --emulator-core happens to be an encrypted donor CIA (rare).

The GBA ROM is embedded into the built title's own RomFS, at a fixed path (rom.gba) that this project defines as its own contract — the --emulator-core binary itself has to be written to read the ROM from there. This is not a wider 3DS/homebrew standard.

Builds the GBA VC footer (the tail AGB_FIRM reads after a ROM, describing ROM size and save type) standalone — already done automatically inside inject native.

FlagRequiredDescription
--romyesThe GBA ROM.
--outputyesWhere to write the footer.
--save-typenoOverride the auto-detected save type.

Extracts the AGB_FIRM boot logo from a donor CIA — a one-time step whose output gets reused for every future inject native run.

FlagRequiredDescription
--donor-ciayesA donor GBA Virtual Console CIA you own.
--boot9yesYour ARM9 bootROM dump (boot9.bin/boot9_prot.bin).
--outputyesWhere to write the extracted boot logo.

agbcia banner build

Builds a complete CBMD banner standalone — already done automatically inside inject. Useful for previewing a banner without a full ROM/CIA build.

FlagRequiredDescription
--imageyesBanner image.
--outputyesWhere to write the banner.
--soundnoBanner sound (WAV). A short silence is used if omitted.

agbcia icon build

Builds a complete SMDH icon standalone — already done automatically inside inject.

FlagRequiredDescription
--imageyesIcon image.
--title-nameyesDisplay title used across every language slot.
--outputyesWhere to write the icon.
--save-data / --no-save-datanoWhether the title uses save data (sets FLAG_SAVE_DATA). Default: --no-save-data.

agbcia rom info / detect-save-type / detect-rtc

Inspect a GBA ROM without building anything. Each takes the ROM path as a plain positional argument (no flag).

agbcia rom info game.gba
# title:          MY GAME
# game_code:      BPEE
# maker_code:     01
# checksum_valid: True
# save_type:      flash1m
# rtc_present:    True

agbcia rom detect-save-type game.gba   # -> flash1m
agbcia rom detect-rtc game.gba         # -> True

agbcia cia info

Shows a CIA's single content's NCCH header fields — works on any CIA, not just this project's own output.

agbcia cia info game.cia
# title_id:      0004000000f09900
# product_code:  CTR-P-BPEE
# no_crypto:     True
# crypto_method: 0x0
# exheader:      present
# exefs:         present
# romfs:         present
# logo region:   present

agbcia keys verify

Checks that a file is a genuine boot9 dump agbcia can use.

agbcia keys verify boot9.bin
# valid boot9 dump (prot)

The donor-banner feature

By default, inject's banner is this project's own flat, non-animated CBMD template with your image glued onto it — simple, and works everywhere, but visually plain next to a real GBA VC title's rotating 3D box.

Pass --donor-banner (extracted from a real GBA VC title's own CIA — see below) and agbcia instead reuses that donor's entire banner scene byte for byte (its 3D mesh, skeleton, rotation animation, materials, and shaders), patching only:

  • your box-art image, onto the donor's front-face texture (always, required alongside --donor-banner);
  • an optional bottom-screen badge image (--bottom-badge), left blank if omitted, instead of showing the donor's own game's name;
  • an optional flat box-shell color (--box-shell-color), left as the donor's own color if omitted.

--box-shell-color is intentionally solid-color-only, not an arbitrary image: the box-shell texture's UV mapping wraps around the whole 3D box in a panoramic, non-obvious way (most of the texture stretches across the sides, a thin strip covers the top/bottom, and the front is nearly degenerate since it's covered by the box-art decal anyway) — a flat color renders correctly there, but a photo or logo would come out visibly warped around every rounded edge.

Any real GBA VC banner can be used as the donor, independent of which ROM you're actually injecting.

Getting the assets you'll need

None of the following are provided by this project — they're either your own hardware's key material or something you dump from a title you legitimately own, exactly once, and then reuse locally forever after.

  • boot9.bin/boot9_prot.bin (your console's ARM9 bootROM dump): needed to extract a boot logo for native mode, and to decrypt an encrypted homebrew donor CIA. Dumped from a real console you own, using standard, widely-documented 3DS homebrew tools (e.g. boot9strap + a dumping homebrew app). agbcia keys verify checks a dump is usable.
  • A donor GBA Virtual Console CIA (native mode only): any real GBA VC title you own works — its boot logo is the same across every GBA VC title, since it's Nintendo's own AGB_FIRM asset, not game-specific. Extract with agbcia footer extract-logo.
  • A donor banner (optional, any mode): extract the banner entry from any real GBA VC donor CIA's ExeFS (e.g. with ctrtool or 3dstool) and pass the resulting file to --donor-banner. See The donor-banner feature.
  • A homebrew forwarder/emulator core CIA (homebrew mode only): a full, working, already-built 3DS application — e.g. a compiled mGBA 3DS build set up as a forwarder.

Project layout

src/agbcia/
  formats/   generic 3DS container formats: CIA, NCCH, ExeFS, RomFS, TMD,
             ticket, SMDH, CBMD/CGFX, CWAV (read + write)
  crypto/    AES/RSA/SHA, keys.bin parsing, key derivation
  banner/    banner/icon/sound authoring
  gba/       GBA-specific logic (footer, save-type/RTC detection, AGB_FIRM,
             emulator-core packaging)
  inject/    high-level pipeline, public API surface
  cli/       Typer CLI, thin wrapper over the above

formats/, crypto/, and banner/ are deliberately GBA-agnostic; gba/ depends on them, never the reverse.

Development

uv sync --all-extras --dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy src

The same four checks run in CI on every push/PR (see .github/workflows/ci.yml).

Known limitations

  • --box-shell-color sets a flat color only, not an arbitrary image (see The donor-banner feature for why).
  • Building a genuinely custom 3D banner scene (a different mesh, skeleton, or animation, rather than patching textures onto a donor's own scene) is out of scope.
  • RomFS only supports a flat file list in the root directory — no subdirectories. Covers this project's own needs (an empty RomFS for native mode, a few files for homebrew mode) but not the fully general case.
  • RTC-title detection is a lookup against the known, finite list of officially released GBA games that shipped with an RTC chip; a ROM hack or translation of one of those games won't match unless it keeps the original game code.

Acknowledgments

No third-party binaries are bundled or shelled out to, but getting the byte-level format details right leaned heavily on public documentation and existing open-source implementations:

  • 3dbrew and GBATEK — the primary format documentation for the 3DS and GBA respectively.
  • pyctr (ihaveamac, MIT) — NCCH/ExeFS/crypto reference behavior, and the source of the long-public cryptographic constants this project embeds (the same ones pyctr itself embeds, for the same reason: they're needed to read real-world donor content and aren't extractable from a boot9 dump).
  • makerom (Project_CTR, MIT) — the real, long-public 3DS "Test PKI" signing key and certificate chain embedded verbatim in agbcia.crypto.test_pki, used across the 3DS homebrew ecosystem to produce genuinely-valid, non-Nintendo signatures on self-built CIAs.
  • bannertool (Epicpkmn11, MIT; itself based on the original Steveice10 bannertool) — the source of this project's own flat CGFX banner template and CWAV/CBMD container layout.
  • 3dstool — NCCH/ExeFS/RomFS byte-layout details not fully covered by 3dbrew alone.
  • vcoven — the GBA VC footer/config-block format and several CLI design conventions (mandatory title ID, its GBA VC range, default fallbacks).
  • SPICA (gdkchan, MIT) — reference for the donor-banner CGFX texture/animation byte layout.
  • gc-dspadpcm-encode (jackoalan, MIT) — the DSP-ADPCM encoder this project ports (currently unused by the default PCM16 banner audio path, kept for future use).
  • mGBA — the GBA RTC-title list this project's own list is taken from.
  • devkitPro's gbafix — the GBA header checksum formula reference.

License

GPL-3.0-or-later — see LICENSE.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.311 Aug 20263

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
marco-zanella/agbcia

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 21:20 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:c92c6d98c11a2ce6c06c57c35c160dd0b336942ad41f0e59c5cf71ae257ca6fb