cmc/cleberg.net

My personal web garden & blog.

clone: git clone https://gitbay.org/cmc/cleberg.net.git

main: content/blog/2026-08-20-nfogen.org · raw

  1#+date: [2026-08-20 Thu 10:24:00]
  2#+title: Generating Scene-Style NFO Files in 2026
  3#+description: Announcing nfogen, a small utility to generate NFO files.
  4#+slug: nfogen
  5#+filetags: :development:
  6
  7* What Is an .nfo File?
  8
  9If you're unfortunate enough not to have lived through the heyday of =.nfo= files,
 10I want to provide some context. Because honestly, this project might seem weird
 11or useless without it.
 12
 13First, an [[https://en.wikipedia.org/wiki/.nfo][NFO]] file (short for info/informational) is a file format originally
 14released in 1990. This file format was used by various projects, but especially
 15by [[https://en.wikipedia.org/wiki/Warez_scene][warez/scene]] groups to provide info about their projects. This might include
 16credits, installation instructions, BBS/contact info, greetz/notes, etc.
 17
 18For example, here's the =bonzai.nfo= file for a patched save game for Lemmings
 19III, with credits to those involved and instructions to get in touch.
 20
 21/Note: The [[http://www.textfiles.com/piracy/NFO/bonzai.nfo][original file]] on textfiles.com contains CP437 bytes decoded as
 22Cyrillic, so I converted this example correctly to CP437. If you view the online
 23version, you'll notice that the box drawing and group logo are incomprehensible,
 24which proves my point later on in the CP437 section./
 25
 26#+begin_src nfo
 27            ║                                                   ║
 28
 29       █████║                                                   ║
 30       █████║                                                   ║
 31       █████▀███  █████▀███  █████▀███  █████▀███  █████▀███  ▀▀▀▀▀
 32       █████║████ █████ ████ █████ ████ █████ ████ █████ ████ █████
 33       █████║████ █████ ████ █████ ████ ▄▄▄▄▄▄████ █████▄████ ██████
 34       █████║████ █████ ████ █████ ████ █████ ▄▄▄▄ █████ ████ ██████
 35       █████ ████ █████ ████ █████ ████ █████ ████ █████ ████ ██████
 36       █████║████ █████ ████ █████ ████ █████ ████ █████ ████ ██████
 37       █████║████ █████ ████ █████ ████ █████ ████ █████ ████ ██████
 38       █████ ████ █████ ████ █████ ████ █████ ████ █████ ████ ██████
 39       ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀
 40            ║    ╓────────────────────────────────────────╖     ║
 41                 ║ ■ BONZAi ─ EVERYTHiNG BUT FRiENDSHiP ■ ║
 42            ║    ╙────────────────────────────────────────╜     ║
 43                             R E L E A S E D :
 44            ║                                                   ║
 45            ║     9O BYTES SAVE GAME PATCHER FOR LEMMiNGS III   ║
 46
 47            ║ FiLENAME .............. : ........... BNZ-L3T.ZiP ║
 48            ║ TYPE .................. : ............... TRAiNER ║
 49            ║ COMPANY ............... : ............. PSYGNOSiS ║
 50            ║ CODER .. .............. : ................ SCAMPY ║
 51            ║ PACKAGER .............. : ................ SCAMPY ║
 52            ║ DATE AT RELEASiNG ..... : ............ 11-27-1994 ║
 53            ║ TiME AT RELEASiNG ..... : .............. 15:OO PM ║
 54            ║ RELEASE NOTES : ..................... READ BELOW! ║
 55            ║                                                   ║
 56            ║            !!!*!!! IMPORTANT !!!*!!!              ║
 57            ║                                                   ║
 58            ║  NiCE PATCHER FOR A NiCE GAME - THiS PATCHER WAS  ║
 59            ║  KINDA DiFFiCULT BECAUSE OF THE MANY BYTES TO BE  ║
 60            ║  PATCHED. BUT PROBLEMS WiTH PATCHiNG TiME AND     ║
 61            ║  FiLE LENGTH HAVE BEEN SOLVED.                    ║
 62            ║                                                   ║
 63            ║  ENJOY PLAYiN' -SCAMPY!                           ║
 64            ║                                                   ║
 65            ║                                                   ║
 66            ║ MEMBERS OF BONZAi ARE . :                         ║
 67            ║                      MOTiV8- SCAMPY- SUM-1- DEViL ║
 68            ║                                                   ║
 69            ║ BULLETiN BOARD SYSTEMS  :                         ║
 70═  ═  ══ ═══╝                                  NONE! SEARCHiNG! ╚═══ ══  ═  ═
 71
 72                               GREETiNGS TO:
 73              TOXiC  HYBRiD  PENTAGRAM  PARADOX  THHG  G.T.A  TRSI
 74
 75     #TO GET iN TOUCH WiTH BNZ LEAVE MAiL TO US ANYWHERE YOU FiND US#
 76
 77    CURRENTLY WE ARE SEARCHiNG FOR ALL KiND OF MEMBERS.. LiKE TRADERS
 78                         SUPPLiERS.. SYSOPS.. ETC.
 79
 80═ ═   ══  ══════════════════════════════════════════════════════════ ══  ═  ═
 81#+end_src
 82
 83If you want to browse an archive of NFO files, head over to [[http://www.textfiles.com/piracy/NFO/][textfiles]].
 84
 85* The Aesthetic
 86** ASCII Art and Box Drawing
 87
 88As you can see in the example above, complex ASCII drawings, logos, and
 89structure are a staple of a good NFO file. Commonly, the ASCII art is used to
 90create recognizable logos for specific groups in the scene.
 91
 92This requires fixed-width fonts to be viewed appropriately. E.g., on Windows,
 93you could open an NFO file in Notepad and ensure a fixed-width font is selected,
 94such as Consolas. Proportional and custom fonts break the flow and structure of
 95these files, which is unfortunately the default in modern browsers (unless you
 96use code blocks, like I did above).
 97
 98** Codepage 437
 99
100One such character set was often used for NFO files: IBM's [[https://en.wikipedia.org/wiki/Code_page_437][CP437]] character set,
101released with the 1981 IBM PC. This set was popular because each character could
102be typed with an alt code in DOS, even if they were not on the user's keyboard.
103E.g., =Alt + 3= would result in the following character: =♥=.
104
105* Introducing: nfogen
106
107So, why am I writing about this now? Well, I was feeling nostalgic for NFO files
108recently. As part of this, I started using NFO files for my git repos under my
109new org and wanted a tool that could help me accelerate the generation of these
110files.
111
112Introducing [[https://github.com/krazywarez/nfogen][nfogen]], a small Python utility that can generate NFO files for you
113based on:
114- templates
115- profiles
116- configuration files
117- CLI flags
118- interactive CLI prompts
119
120This enables fast, customizable, and templatized NFO files you can generate on
121the fly.
122
123** Sample Output
124
125There's no better way to introduce a project than to show a demo, so here's a
126demo of how I generated the =README.nfo= for nfogen itself.
127
128First, I defined =nfogen.toml= so that I could use this as a configuration file
129during generation:
130
131#+begin_src toml
132group = "KRZ"
133site  = "krz.sh"
134width = 79
135style = "double"
136layout = "rows"
137
138banner      = true
139banner_text = "NFOGEN"
140
141footer      = true
142footer_text = "nfogen  //  krazy warez  //  krz.sh"
143
144title      = "nfogen 1.2.2"
145type       = "CLI / library"
146publisher  = "krazy warez"
147language   = "Python 3.11+  (any 3.x with a JSON config)"
148files      = "nfogen.py  --  single file, zero deps"
149url        = "krz.sh"
150
151notes = """
152nfogen builds scene-style ASCII .nfo files from CLI flags, a config
153file, interactive prompts, or any mix of the three.
154
155FEATURES
156  - CP437 or UTF-8 output; single / double / block box styles
157  - two-column release+game info panel, or simple rows
158  - group profiles: reusable logo, members, couriers, boards
159  - mediainfo import auto-fills video / audio / resolution / size
160  - built-in ASCII banner generator for header logos (this one!)
161  - bundled templates: razor, modern, minimal
162
163QUICKSTART
164  nfogen.py --title "Some.App.v1-KRZ" --type APP --banner
165  nfogen.py -c release.toml -p group.toml -o release.nfo
166  nfogen.py -t razor --title "..." --mediainfo dump.json
167  nfogen.py -i                          (prompt for every field)
168
169RESOLUTION ORDER
170  template < profile < config file < CLI flags < prompts
171
172See examples/ for full release nfos and a group profile, and
173templates/ for the bundled presets. This README.nfo was itself
174generated by nfogen:   nfogen.py -c nfogen.toml
175"""
176#+end_src
177
178Next, I ran the following command, which produced the NFO file below:
179
180#+begin_src shell
181nfogen.py -c nfogen.toml -o README.nfo
182#+end_src
183
184#+begin_src nfo
185                   #   #  #####   ###    ####  #####  #   #
186                   ##  #  #      #   #  #      #      ##  #
187                   # # #  ###    #   #  #  ##  ###    # # #
188                   #  ##  #      #   #  #   #  #      #  ##
189                   #   #  #       ###    ####  #####  #   #
190
191╔═════════════════════════════════════════════════════════════════════════════╗
192║                           N F O G E N   1 . 2 . 2                           ║
193║                               [ KRZ ]   krz.sh                              ║
194╠═════════════════════════════════════════════════════════════════════════════╣
195║   DATE       2026-08-08                                                     ║
196║   TYPE       CLI / library                                                  ║
197║   PUBLISHER  krazy warez                                                    ║
198║   LANGUAGE   Python 3.11+  (any 3.x with a JSON config)                     ║
199║   FILES      nfogen.py  --  single file, zero deps                          ║
200║   URL        krz.sh                                                         ║
201╠═════════════════════════════════════════════════════════════════════════════╣
202║   NOTES                                                                     ║
203║                                                                             ║
204║   nfogen builds scene-style ASCII .nfo files from CLI flags, a config       ║
205║   file, interactive prompts, or any mix of the three.                       ║
206║                                                                             ║
207║   FEATURES                                                                  ║
208║     - CP437 or UTF-8 output; single / double / block box styles             ║
209║     - two-column release+game info panel, or simple rows                    ║
210║     - group profiles: reusable logo, members, couriers, boards              ║
211║     - mediainfo import auto-fills video / audio / resolution / size         ║
212║     - built-in ASCII banner generator for header logos (this one!)          ║
213║     - bundled templates: razor, modern, minimal                             ║
214║                                                                             ║
215║   QUICKSTART                                                                ║
216║     nfogen.py --title "Some.App.v1-KRZ" --type APP --banner                 ║
217║     nfogen.py -c release.toml -p group.toml -o release.nfo                  ║
218║     nfogen.py -t razor --title "..." --mediainfo dump.json                  ║
219║     nfogen.py -i                          (prompt for every field)          ║
220║                                                                             ║
221║   RESOLUTION ORDER                                                          ║
222║     template < profile < config file < CLI flags < prompts                  ║
223║                                                                             ║
224║   See examples/ for full release nfos and a group profile, and              ║
225║   templates/ for the bundled presets. This README.nfo was itself            ║
226║   generated by nfogen:   nfogen.py -c nfogen.toml                           ║
227╠═════════════════════════════════════════════════════════════════════════════╣
228║                                                                             ║
229║                     nfogen  //  krazy warez  //  krz.sh                     ║
230║                                                                             ║
231╚═════════════════════════════════════════════════════════════════════════════╝
232#+end_src
233
234For more examples, browse the [[https://github.com/krazywarez/nfogen/tree/main/examples][examples]] directory within the repository.
235
236* Usage
237** Installation
238
239The fastest installation method is to fetch and use the Python file directly:
240
241#+begin_src shell
242curl -O https://raw.githubusercontent.com/krazywarez/nfogen/main/nfogen.py
243python3 nfogen.py -i
244#+end_src
245
246** Generating a File
247
248As noted in the README above, there are numerous ways to generate an NFO file:
249
250#+begin_src shell
251# CLI Flags
252nfogen.py --title "Some.App.v1-KRZ" --type APP --banner
253
254# TOML Configuration Files
255nfogen.py -c release.toml -p group.toml -o release.nfo
256
257# CLI Flags & JSON for mediainfo
258nfogen.py -t razor --title "..." --mediainfo dump.json
259
260# Interactive Mode
261nfogen.py -i
262#+end_src
263
264* How It Works
265
266nfogen itself works by parsing the various inputs, determining a style to use
267for the box-drawing/character sets, and rendering the characters individually by
268rows and columns.
269
270** Width
271
272The first issue I ran into was determining the width to use. I settled on 79
273characters, border characters included, because terminals were 80 columns wide
274and a line that hits exactly 80 wraps on its own. This means that my script
275needs to perform some math to calculate wrapping, padding, and centering of text
276throughout.
277
278The math is relatively simple because CP437 is single-byte and single-width,
279which means I can write functions that depend on that one-to-one correlation
280to calculate the spacing properly.
281
282=_Canvas.__init__= defines a few key variables for width:
283
284#+begin_src python
285self.width = width
286self.inner = width - 4        # single-column content width
287self.cl = (width - 7) // 2    # left panel content width
288self.cr = (width - 7) - self.cl
289self.rfill = width - self.cl - 5
290#+end_src
291
292At a width of 79 characters, this breaks down into:
293
294| Variable   | Value | What it covers                                          |
295|------------+-------+---------------------------------------------------------|
296| =self.width= |    79 | Total line width, borders included                      |
297| =self.inner= |    75 | Single-column content, minus two verticals and two pads |
298| =self.cl=    |    36 | Left panel content width                                |
299| =self.cr=    |    36 | Right panel content, taking the remainder               |
300| =self.rfill= |    38 | Rule segment right of the joint, =cr= plus its two pads   |
301
302These combine into a cohesive 79-character-width NFO file with consistent
303padding and alignment.
304
305** Character Sets
306
307=nfogen= currently supports two different character sets as outputs: UTF-8 and
308CP437. For example, there are =╔ ═ ║= characters in the UTF-8 example, but a real
309CP437 file contains the single bytes =0xC9 0xCD 0xBA=.
310
311However, this doesn't really change the core generation of the NFO file. The
312file itself is charset-independent and the charset only comes into play during
313the final encode of the file.
314
315Referring back to the =bonzai.nfo= file above, it was decoded improperly as
316ISO-8859-5, which resulted in Cyrillic characters instead of the proper charset.
317Defining an appropriate charset and encoding/decoding is critical to ensuring
318the longevity of NFO files.
319
320** Banner Generator
321
322The banner generator is a function by itself, which renders independent from the
323rest of the file and separate from the box geometry. This function defines a set
324of glyphs, takes an input text string from the user, and maps each character to
325its glyph. The result is rendered above the box and is centered, so it does not
326depend on the math applied to the canvas.
327
328If an explicit art file is defined, this generator does not run.
329
330#+begin_src python
331# A compact 5-row block font for the banner generator. Each glyph is five
332# rows joined by "/". Uppercase letters, digits, space, and a few symbols.
333_FONT = {
334    "A": " ### /#   #/#####/#   #/#   #", "B": "#### /#   #/#### /#   #/#### ",
335    "C": " ####/#    /#    /#    / ####", "D": "#### /#   #/#   #/#   #/#### ",
336    # ... through Z, 0-9, space, -, ., !, :
337}
338
339
340def banner(text: str) -> str:
341    """Render text as a 5-row ASCII banner using the built-in block font."""
342    rows = ["", "", "", "", ""]
343    for ch in text.upper():
344        glyph = _FONT.get(ch, _FONT[" "]).split("/")
345        for i in range(5):
346            rows[i] += glyph[i] + "  "
347    return "\n".join(r.rstrip() for r in rows)
348#+end_src
349
350** Resolution Order
351
352Finally, I defined a specific order to the inputs. As stated in the README, the
353order of importance is: =template < profile < config file < CLI flags < prompts=.
354
355This was an opinionated design choice, as I felt that if you were to run the
356script in interactive mode, that meant you wanted those to take precedence over
357all. Then again, specifying CLI flags directly when running the script should do
358the same. Finally, specifying configuration files, profiles, and templates will
359take precedence in descending order.
360
361* The Future
362
363I'd love to build this tool out to support more character sets, pre-built
364libraries for ASCII art and table layouts, and more customization options.
365
366For now, the tool works and is sufficient for my personal use cases, so I'm
367content with it.
368
369If you're interested in helping, feel free to reach out!