https://github.com/HackTechDev/YadexEditor
Yadex is a Doom/Doom II/Heretic/Hexen/Strife/ZDoom level (WAD) editor for
Unix/X11, forked from DEU 5.21. It is written in a mixture of C (mostly
atclib/ and compat/) and C++11 (src/), talks to X11 directly via Xlib
(no toolkit), and predates most modern C++ conventions — expect a lot of
global state, raw pointers, and old-style structs alongside newer,
string/vector-based code.
Requires GNU make (the makefile is named GNUmakefile on purpose — BSD make
won't work), a C89 compiler, and a C++11 compiler, plus X11 dev headers
(/usr/X11R6/{lib,include} by default; adjusted automatically for
AIX/Solaris in the GNUmakefile).
./configure # writes obj/0/Makefile.config and config.h
make # builds everything (doc, yadex, .ygd files)
make yadex # just build the binary -> obj/0/yadex
make test A='-g heretic foo.wad' # build (if needed) and run obj/0/yadex with args
Useful ./configure flags: --prefix PATH, --cc COMPILER, --cxx COMPILER.
Debug build (preferred while hacking — this is the maintainer's own habit):
make dyadex # debug binary -> dobj/0/yadex
make dtest A='-g heretic foo.wad' # run it, also writes gprof.out
make dg # run dobj/0/yadex under gdb (use "set args ..." at the prompt)
make dd # same, under ddd
make showconf prints the resolved CC/CXX/CFLAGS/paths/etc — check this
first when a build behaves unexpectedly. make clean removes build output
for the current $SYSTEM variant; make dclean removes only the debug
objects. There is no make check/unit test target — test/ holds WAD
fixture data (levels, lumps, graphics, sounds) used for manual/interactive
testing via make test/make dtest, not an automated test suite.
Object files are split per source tree into obj/<SYSTEM>/ and
dobj/<SYSTEM>/ (symlinked from obj/0 and dobj/0), where SYSTEM is
derived from uname, so multiple platforms can be built from one checkout
without clobbering each other.
GNUmakefile also drives documentation and release packaging (make doc,
make dist, make changes) — irrelevant for ordinary code changes.
The project has an explicit style guide in docsrc/hackers_guide.html
("Coding standards" section); the essentials, still enforced by convention
even though some of it predates C++ in this codebase:
snake_case for variables/functions, UPPER_CASE for enums/most macros.colour, not color).strcmp()
results/error-code ints — e.g. if (fopen(...) == NULL), not
if (!fopen(...)).y_snprintf() over sprintf(), strlcpy/strlcat (via
compat/) over strcpy/strcat.const for by-reference args that aren't mutated; static for
functions/variables that aren't used outside their file.char */C-string APIs to
std::string/std::vector (see recent git log: "More s/char/string/",
"Less char, more string") and away from compat/compat.h includes that
are no longer needed — when touching a file, prefer continuing that
direction rather than reintroducing raw C-string plumbing, but don't
do drive-by rewrites of unrelated code.Like Doom itself, Yadex never reads WAD files directly by name at the point
of use. It maintains an in-memory master directory: a linked list of
MasterDirectory entries (global MasterDir), each pointing back to the
Wad_file/WadFileInfo it came from (global WadFileList, one entry per
open IWAD/PWAD). OpenMainWad() builds the directory from the IWAD;
OpenPatchWad() layers a PWAD on top — if the same lump name exists in
several wads, only the entry from the most-recently-loaded wad survives.
CloseUnusedWadFiles() closes wads no longer referenced by any directory
entry; there is deliberately no "unload a single pwad" operation (see
docsrc/hackers_guide.html for why this is a known wart). Look up a lump
with FindMasterDir(). See src/wadfile.h, src/wads.cc/wads2.cc,
src/lumpdir.cc.
Wad endianness is always little-endian on disk; in-core integers are
native-endian. All multi-byte WAD I/O must go through Wad_file::read_*
(or the free wad_read_i16()/wad_read_i32() equivalents) to stay
portable to big-endian hosts.
Level data (things, linedefs, sidedefs, vertices, sectors) lives in global
arrays/counts declared in src/levels.h and defined in src/levels.cc
(Things/NumThings, LineDefs/NumLineDefs, SideDefs (now a
vector<SideDef>), Vertices/NumVertices, Sectors/NumSectors, plus
MadeChanges/MadeMapChanges, map bounds, etc.). This is legacy global
state, not a Level object (a long-standing FIXME — see the comment at the
top of levels.h — is that it should become a class so multiple levels
could be edited at once; don't assume that refactor has happened).
Invariants that must hold at all times: Num* counts stay accurate, and
vertex/sidedef/sector references are always either OBJ_NO_NONE or a valid
index. SEGS/SSECTORS/NODES/BLOCKMAP/REJECT are not loaded into memory; on
save, they're either copied byte-for-byte from the source file (if
MadeMapChanges is false) or zeroed out (nodes need external rebuilding).
EditorLoop() (in src/editloop.cc) is the heart of the interactive
editor: refresh display → wait for an event → process the event, repeat.
Display is driven from edisplay_c::refresh() (see src/edisplay.h),
which walks a set of widgets: decide if a full redraw is needed
(need_to_clear()), clear()/undraw top-down or draw bottom-up, then
update_display(). To reduce flicker on full redraws, output is buffered
to an offscreen pixmap and blitted with XCopyArea() (gfx.cc switches
transparently between window and pixmap targets); this can be disabled
(no_pixmap) for slow/low-memory setups.
The selection is a singly linked list of SelectionList nodes holding just
an objnum (the object type is implicit from the current edit mode — a
selection can't mix object types). Manipulated via SelectObject(),
UnSelectObject(), select_unselect_obj() (toggle), IsSelected(),
ForgetSelection() in src/selectn.cc/selectn.h. There is deliberately
no iterator helper; callers walk the linked list themselves.
Img (src/img.h) is the in-memory representation of all game graphics
(flats, patches, sprites, textures): a palette-indexed rectangular pixel
buffer (img_pixel_t = one byte = index into PLAYPAL). Textures are
composed by pasting patches onto a buffer. Loading goes through
LoadPicture() (pictures) / DisplayFloorTexture() (flats) into an Img,
then display_img() converts palette indices to the actual X11 pixel
format and blits. Screenshots go the other way: window_to_rgbbmp() then
rgbbmp_to_ppm().
Use GetMemory()/FreeMemory() (and the GetFarMemory()/
FreeFarMemory() pair, mostly a DOS-era relic) instead of raw
malloc/free. GetMemory() calls fatal_error() internally on
failure, so callers don't need to check its return value; memory it
allocates is safe to free() directly if needed.
atclib/ — a bundled subset of Atclib (LGPL, by André Majorel),
low-level C helpers (list/array primitives, string utilities). Treat as
vendored; don't restyle it to match src/ conventions.compat/ — small portability shims (strlcpy/strlcat,
arc4random) only compiled in when ./configure determines the host
libc lacks them (HAVE_STRL, HAVE_ARC4RANDOM in
obj/0/Makefile.config).ygd/ — game definition files (doom.ygd, heretic.ygd, hexen.ygd,
zdoom.ygd, etc.) describing per-game thing types, linedef flags,
textures, etc.; parsed at runtime, not compiled in.GNUmakefile (MODULES_YADEX, MODULES_ATCLIB, MODULES_COMPAT) —
when adding a new .cc/.c file under src/, atclib/, or compat/,
it must be added to the corresponding list or it will silently not build.src/config.cc / src/config.h are generated by ./configure (copied
from obj/0/) — don't hand-edit them.src/credits.cc and src/version.cc are generated by make from
docsrc/copyright and VERSION respectively — don't hand-edit them.