#+date: [2026-08-20 Thu 10:24:00] #+title: Generating Scene-Style NFO Files in 2026 #+description: Announcing nfogen, a small utility to generate NFO files. #+slug: nfogen #+filetags: :development: * What Is an .nfo File? If you're unfortunate enough not to have lived through the heyday of =.nfo= files, I want to provide some context. Because honestly, this project might seem weird or useless without it. First, an [[https://en.wikipedia.org/wiki/.nfo][NFO]] file (short for info/informational) is a file format originally released in 1990. This file format was used by various projects, but especially by [[https://en.wikipedia.org/wiki/Warez_scene][warez/scene]] groups to provide info about their projects. This might include credits, installation instructions, BBS/contact info, greetz/notes, etc. For example, here's the =bonzai.nfo= file for a patched save game for Lemmings III, with credits to those involved and instructions to get in touch. /Note: The [[http://www.textfiles.com/piracy/NFO/bonzai.nfo][original file]] on textfiles.com contains CP437 bytes decoded as Cyrillic, so I converted this example correctly to CP437. If you view the online version, you'll notice that the box drawing and group logo are incomprehensible, which proves my point later on in the CP437 section./ #+begin_src nfo ║ ║ █████║ ║ █████║ ║ █████▀███ █████▀███ █████▀███ █████▀███ █████▀███ ▀▀▀▀▀ █████║████ █████ ████ █████ ████ █████ ████ █████ ████ █████ █████║████ █████ ████ █████ ████ ▄▄▄▄▄▄████ █████▄████ ██████ █████║████ █████ ████ █████ ████ █████ ▄▄▄▄ █████ ████ ██████ █████ ████ █████ ████ █████ ████ █████ ████ █████ ████ ██████ █████║████ █████ ████ █████ ████ █████ ████ █████ ████ ██████ █████║████ █████ ████ █████ ████ █████ ████ █████ ████ ██████ █████ ████ █████ ████ █████ ████ █████ ████ █████ ████ ██████ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀ ║ ╓────────────────────────────────────────╖ ║ ║ ■ BONZAi ─ EVERYTHiNG BUT FRiENDSHiP ■ ║ ║ ╙────────────────────────────────────────╜ ║ R E L E A S E D : ║ ║ ║ 9O BYTES SAVE GAME PATCHER FOR LEMMiNGS III ║ ║ FiLENAME .............. : ........... BNZ-L3T.ZiP ║ ║ TYPE .................. : ............... TRAiNER ║ ║ COMPANY ............... : ............. PSYGNOSiS ║ ║ CODER .. .............. : ................ SCAMPY ║ ║ PACKAGER .............. : ................ SCAMPY ║ ║ DATE AT RELEASiNG ..... : ............ 11-27-1994 ║ ║ TiME AT RELEASiNG ..... : .............. 15:OO PM ║ ║ RELEASE NOTES : ..................... READ BELOW! ║ ║ ║ ║ !!!*!!! IMPORTANT !!!*!!! ║ ║ ║ ║ NiCE PATCHER FOR A NiCE GAME - THiS PATCHER WAS ║ ║ KINDA DiFFiCULT BECAUSE OF THE MANY BYTES TO BE ║ ║ PATCHED. BUT PROBLEMS WiTH PATCHiNG TiME AND ║ ║ FiLE LENGTH HAVE BEEN SOLVED. ║ ║ ║ ║ ENJOY PLAYiN' -SCAMPY! ║ ║ ║ ║ ║ ║ MEMBERS OF BONZAi ARE . : ║ ║ MOTiV8- SCAMPY- SUM-1- DEViL ║ ║ ║ ║ BULLETiN BOARD SYSTEMS : ║ ═ ═ ══ ═══╝ NONE! SEARCHiNG! ╚═══ ══ ═ ═ GREETiNGS TO: TOXiC HYBRiD PENTAGRAM PARADOX THHG G.T.A TRSI #TO GET iN TOUCH WiTH BNZ LEAVE MAiL TO US ANYWHERE YOU FiND US# CURRENTLY WE ARE SEARCHiNG FOR ALL KiND OF MEMBERS.. LiKE TRADERS SUPPLiERS.. SYSOPS.. ETC. ═ ═ ══ ══════════════════════════════════════════════════════════ ══ ═ ═ #+end_src If you want to browse an archive of NFO files, head over to [[http://www.textfiles.com/piracy/NFO/][textfiles]]. * The Aesthetic ** ASCII Art and Box Drawing As you can see in the example above, complex ASCII drawings, logos, and structure are a staple of a good NFO file. Commonly, the ASCII art is used to create recognizable logos for specific groups in the scene. This requires fixed-width fonts to be viewed appropriately. E.g., on Windows, you could open an NFO file in Notepad and ensure a fixed-width font is selected, such as Consolas. Proportional and custom fonts break the flow and structure of these files, which is unfortunately the default in modern browsers (unless you use code blocks, like I did above). ** Codepage 437 One such character set was often used for NFO files: IBM's [[https://en.wikipedia.org/wiki/Code_page_437][CP437]] character set, released with the 1981 IBM PC. This set was popular because each character could be typed with an alt code in DOS, even if they were not on the user's keyboard. E.g., =Alt + 3= would result in the following character: =♥=. * Introducing: nfogen So, why am I writing about this now? Well, I was feeling nostalgic for NFO files recently. As part of this, I started using NFO files for my git repos under my new org and wanted a tool that could help me accelerate the generation of these files. Introducing [[https://github.com/krazywarez/nfogen][nfogen]], a small Python utility that can generate NFO files for you based on: - templates - profiles - configuration files - CLI flags - interactive CLI prompts This enables fast, customizable, and templatized NFO files you can generate on the fly. ** Sample Output There's no better way to introduce a project than to show a demo, so here's a demo of how I generated the =README.nfo= for nfogen itself. First, I defined =nfogen.toml= so that I could use this as a configuration file during generation: #+begin_src toml group = "KRZ" site = "krz.sh" width = 79 style = "double" layout = "rows" banner = true banner_text = "NFOGEN" footer = true footer_text = "nfogen // krazy warez // krz.sh" title = "nfogen 1.2.2" type = "CLI / library" publisher = "krazy warez" language = "Python 3.11+ (any 3.x with a JSON config)" files = "nfogen.py -- single file, zero deps" url = "krz.sh" notes = """ nfogen builds scene-style ASCII .nfo files from CLI flags, a config file, interactive prompts, or any mix of the three. FEATURES - CP437 or UTF-8 output; single / double / block box styles - two-column release+game info panel, or simple rows - group profiles: reusable logo, members, couriers, boards - mediainfo import auto-fills video / audio / resolution / size - built-in ASCII banner generator for header logos (this one!) - bundled templates: razor, modern, minimal QUICKSTART nfogen.py --title "Some.App.v1-KRZ" --type APP --banner nfogen.py -c release.toml -p group.toml -o release.nfo nfogen.py -t razor --title "..." --mediainfo dump.json nfogen.py -i (prompt for every field) RESOLUTION ORDER template < profile < config file < CLI flags < prompts See examples/ for full release nfos and a group profile, and templates/ for the bundled presets. This README.nfo was itself generated by nfogen: nfogen.py -c nfogen.toml """ #+end_src Next, I ran the following command, which produced the NFO file below: #+begin_src shell nfogen.py -c nfogen.toml -o README.nfo #+end_src #+begin_src nfo # # ##### ### #### ##### # # ## # # # # # # ## # # # # ### # # # ## ### # # # # ## # # # # # # # ## # # # ### #### ##### # # ╔═════════════════════════════════════════════════════════════════════════════╗ ║ N F O G E N 1 . 2 . 2 ║ ║ [ KRZ ] krz.sh ║ ╠═════════════════════════════════════════════════════════════════════════════╣ ║ DATE 2026-08-08 ║ ║ TYPE CLI / library ║ ║ PUBLISHER krazy warez ║ ║ LANGUAGE Python 3.11+ (any 3.x with a JSON config) ║ ║ FILES nfogen.py -- single file, zero deps ║ ║ URL krz.sh ║ ╠═════════════════════════════════════════════════════════════════════════════╣ ║ NOTES ║ ║ ║ ║ nfogen builds scene-style ASCII .nfo files from CLI flags, a config ║ ║ file, interactive prompts, or any mix of the three. ║ ║ ║ ║ FEATURES ║ ║ - CP437 or UTF-8 output; single / double / block box styles ║ ║ - two-column release+game info panel, or simple rows ║ ║ - group profiles: reusable logo, members, couriers, boards ║ ║ - mediainfo import auto-fills video / audio / resolution / size ║ ║ - built-in ASCII banner generator for header logos (this one!) ║ ║ - bundled templates: razor, modern, minimal ║ ║ ║ ║ QUICKSTART ║ ║ nfogen.py --title "Some.App.v1-KRZ" --type APP --banner ║ ║ nfogen.py -c release.toml -p group.toml -o release.nfo ║ ║ nfogen.py -t razor --title "..." --mediainfo dump.json ║ ║ nfogen.py -i (prompt for every field) ║ ║ ║ ║ RESOLUTION ORDER ║ ║ template < profile < config file < CLI flags < prompts ║ ║ ║ ║ See examples/ for full release nfos and a group profile, and ║ ║ templates/ for the bundled presets. This README.nfo was itself ║ ║ generated by nfogen: nfogen.py -c nfogen.toml ║ ╠═════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ nfogen // krazy warez // krz.sh ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════════╝ #+end_src For more examples, browse the [[https://github.com/krazywarez/nfogen/tree/main/examples][examples]] directory within the repository. * Usage ** Installation The fastest installation method is to fetch and use the Python file directly: #+begin_src shell curl -O https://raw.githubusercontent.com/krazywarez/nfogen/main/nfogen.py python3 nfogen.py -i #+end_src ** Generating a File As noted in the README above, there are numerous ways to generate an NFO file: #+begin_src shell # CLI Flags nfogen.py --title "Some.App.v1-KRZ" --type APP --banner # TOML Configuration Files nfogen.py -c release.toml -p group.toml -o release.nfo # CLI Flags & JSON for mediainfo nfogen.py -t razor --title "..." --mediainfo dump.json # Interactive Mode nfogen.py -i #+end_src * How It Works nfogen itself works by parsing the various inputs, determining a style to use for the box-drawing/character sets, and rendering the characters individually by rows and columns. ** Width The first issue I ran into was determining the width to use. I settled on 79 characters, border characters included, because terminals were 80 columns wide and a line that hits exactly 80 wraps on its own. This means that my script needs to perform some math to calculate wrapping, padding, and centering of text throughout. The math is relatively simple because CP437 is single-byte and single-width, which means I can write functions that depend on that one-to-one correlation to calculate the spacing properly. =_Canvas.__init__= defines a few key variables for width: #+begin_src python self.width = width self.inner = width - 4 # single-column content width self.cl = (width - 7) // 2 # left panel content width self.cr = (width - 7) - self.cl self.rfill = width - self.cl - 5 #+end_src At a width of 79 characters, this breaks down into: | Variable | Value | What it covers | |------------+-------+---------------------------------------------------------| | =self.width= | 79 | Total line width, borders included | | =self.inner= | 75 | Single-column content, minus two verticals and two pads | | =self.cl= | 36 | Left panel content width | | =self.cr= | 36 | Right panel content, taking the remainder | | =self.rfill= | 38 | Rule segment right of the joint, =cr= plus its two pads | These combine into a cohesive 79-character-width NFO file with consistent padding and alignment. ** Character Sets =nfogen= currently supports two different character sets as outputs: UTF-8 and CP437. For example, there are =╔ ═ ║= characters in the UTF-8 example, but a real CP437 file contains the single bytes =0xC9 0xCD 0xBA=. However, this doesn't really change the core generation of the NFO file. The file itself is charset-independent and the charset only comes into play during the final encode of the file. Referring back to the =bonzai.nfo= file above, it was decoded improperly as ISO-8859-5, which resulted in Cyrillic characters instead of the proper charset. Defining an appropriate charset and encoding/decoding is critical to ensuring the longevity of NFO files. ** Banner Generator The banner generator is a function by itself, which renders independent from the rest of the file and separate from the box geometry. This function defines a set of glyphs, takes an input text string from the user, and maps each character to its glyph. The result is rendered above the box and is centered, so it does not depend on the math applied to the canvas. If an explicit art file is defined, this generator does not run. #+begin_src python # A compact 5-row block font for the banner generator. Each glyph is five # rows joined by "/". Uppercase letters, digits, space, and a few symbols. _FONT = { "A": " ### /# #/#####/# #/# #", "B": "#### /# #/#### /# #/#### ", "C": " ####/# /# /# / ####", "D": "#### /# #/# #/# #/#### ", # ... through Z, 0-9, space, -, ., !, : } def banner(text: str) -> str: """Render text as a 5-row ASCII banner using the built-in block font.""" rows = ["", "", "", "", ""] for ch in text.upper(): glyph = _FONT.get(ch, _FONT[" "]).split("/") for i in range(5): rows[i] += glyph[i] + " " return "\n".join(r.rstrip() for r in rows) #+end_src ** Resolution Order Finally, I defined a specific order to the inputs. As stated in the README, the order of importance is: =template < profile < config file < CLI flags < prompts=. This was an opinionated design choice, as I felt that if you were to run the script in interactive mode, that meant you wanted those to take precedence over all. Then again, specifying CLI flags directly when running the script should do the same. Finally, specifying configuration files, profiles, and templates will take precedence in descending order. * The Future I'd love to build this tool out to support more character sets, pre-built libraries for ASCII art and table layouts, and more customization options. For now, the tool works and is sufficient for my personal use cases, so I'm content with it. If you're interested in helping, feel free to reach out!