From 76242f80865e29d985ece491be84b7061bcb4111 Mon Sep 17 00:00:00 2001
From: 4am <a2_4am@sent.com>
Date: Sat, 23 Oct 2021 00:53:23 -0400
Subject: [PATCH] precompute gSearchStore

---
 Makefile                 | 460 ++++++++++++++++++++-------------------
 bin/builddisplaynames.py |  14 ++
 bin/buildokvs.sh         |   4 +-
 res/ATTRACT/AO           |   7 +
 res/ATTRACT/CHIP.OUT     |   7 +
 res/ATTRACT/VV           |   7 +
 res/_FileInformation.txt |   4 +
 src/4cade.init.a         |  13 ++
 src/constants.a          |   4 +-
 src/okvs.a               | 103 ---------
 src/parse.common.a       | 103 +++++++++
 src/prodos.path.a        |   9 +
 src/textrank.a           |  30 +--
 13 files changed, 408 insertions(+), 357 deletions(-)
 create mode 100755 bin/builddisplaynames.py
 create mode 100644 res/ATTRACT/AO
 create mode 100644 res/ATTRACT/CHIP.OUT
 create mode 100644 res/ATTRACT/VV

diff --git a/Makefile b/Makefile
index a5f66433b..0ea94ec74 100644
--- a/Makefile
+++ b/Makefile
@@ -1,224 +1,236 @@
-#
-# 4cade Makefile
-# assembles source code, optionally builds a disk image and mounts it
-# note: Windows users should probably use winmake.bat instead
-#
-# original by Quinn Dunki on 2014-08-15
-# One Girl, One Laptop Productions
-# http://www.quinndunki.com/blondihacks
-#
-# adapted by 4am on 2018-08-19
-#
-
-DISK=4cade.hdv
-VOLUME=TOTAL.REPLAY
-
-# third-party tools required to build
-
-# https://sourceforge.net/projects/acme-crossass/
-# version 0.96.3 or later
-ACME=acme
-
-# https://github.com/mach-kernel/cadius
-# version 1.4.0 or later
-CADIUS=cadius
-
-# https://bitbucket.org/magli143/exomizer/wiki/Home
-# version 3.1.0 or later
-EXOMIZER=exomizer mem -q -P23 -lnone
-
-dsk: asm
-	cp res/blank.hdv build/"$(DISK)" >>build/log
-	cp res/_FileInformation.txt build/ >>build/log
-	$(CADIUS) ADDFILE build/"$(DISK)" "/$(VOLUME)/" build/LAUNCHER.SYSTEM >>build/log
-	cp res/PREFS.CONF build/PREFS.CONF >>build/log
-	bin/padto.sh 512 build/PREFS.CONF >>build/log
-#
-# precompute binary data structure for mega-attract mode configuration file
-#
-	bin/buildokvs.sh res/ATTRACT.CONF build/ATTRACT.IDX >>build/log
-#
-# precompute binary data structure and substitute special characters
-# in game help and other all-text pages
-#
-	bin/converthelp.sh res/HELPTEXT build/HELPTEXT >>build/log
-	bin/converthelp.sh res/CREDITS build/CREDITS >>build/log
-	for f in res/GAMEHELP/*; do \
-	    bin/converthelp.sh "$$f" build/GAMEHELP/"$$(basename $$f)" >>build/log; \
-	done
-#
-# create distribution version of GAMES.CONF without comments or blank lines
-#
-	awk '!/^$$|^#/' < res/GAMES.CONF > build/GAMES.CONF
-#
-# create a sorted list of game filenames, without metadata or display names
-#
-	awk -F, '/,/ { print $$2 }' < build/GAMES.CONF | awk -F= '{ print $$1 }' | sort > build/GAMES.SORTED
-#
-# precompute indexed files for prelaunch
-# note: prelaunch must be first in TOTAL.DATA due to a hack in LoadStandardPrelaunch
-# note 2: these can not be padded because they are loaded at $0106 and padding would clobber the stack
-#
-	bin/buildindexedfile.sh build/GAMES.SORTED build/PRELAUNCH.IDX build/TOTAL.DATA build/PRELAUNCH.INDEXED >>build/log
-#
-# precompute indexed files for game help
-#
-	bin/buildindexedfile.sh -p -a build/GAMES.SORTED build/GAMEHELP.IDX build/TOTAL.DATA build/GAMEHELP >>build/log
-#
-# precompute indexed files for slideshows
-#
-	(for f in res/SS/*; do \
-	    bin/buildokvs.sh "$$f" "build/SS/$$(basename $$f)"; \
-	    echo "$$(basename $$f)"; \
-	done) > build/SSDIR
-	bin/buildindexedfile.sh -p -a build/SSDIR build/SLIDESHOW.IDX build/TOTAL.DATA build/SS >>build/log
-	(for f in res/ATTRACT/*; do \
-	    bin/buildokvs.sh "$$f" "build/ATTRACT/$$(basename $$f)"; \
-	    echo "$$(basename $$f)"; \
-	done) > build/ATTRACTDIR
-	bin/buildindexedfile.sh -p -a build/ATTRACTDIR build/MINIATTRACT.IDX build/TOTAL.DATA build/ATTRACT >>build/log
-#
-# precompute indexed files for graphic effects
-#
-	bin/buildindexedfile.sh -p -a res/FX.CONF build/FX.IDX build/TOTAL.DATA build/FX.INDEXED >>build/log
-	bin/buildindexedfile.sh -p -a res/DFX.CONF build/DFX.IDX build/TOTAL.DATA build/FX.INDEXED >>build/log
-#
-# precompute indexed files for HGR action screenshots
-# note: these can not be padded because they are compressed and the decompressor needs the exact size
-#
-	(for f in res/ACTION.HGR/[ABCD]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR0
-	(for f in res/ACTION.HGR/[EFGH]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR1
-	(for f in res/ACTION.HGR/[IJKL]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR2
-	(for f in res/ACTION.HGR/[MNOP]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR3
-	(for f in res/ACTION.HGR/[QRST]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR4
-	(for f in res/ACTION.HGR/[UVWX]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR5
-	(for f in res/ACTION.HGR/[YZ]*;   do echo "$$(basename $$f)"; done) > build/ACTIONHGR6
-	bin/buildindexedfile.sh -a build/ACTIONHGR0 build/HGR0.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR1 build/HGR1.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR2 build/HGR2.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR3 build/HGR3.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR4 build/HGR4.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR5 build/HGR5.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-	bin/buildindexedfile.sh -a build/ACTIONHGR6 build/HGR6.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
-#
-# precompute indexed files for SHR artwork
-# note: these can not be padded because they are compressed and the decompressor needs the exact size
-#
-	(for f in res/ARTWORK.SHR/*; do \
-	    echo "$$(basename $$f)"; \
-	done) > build/ARTWORKDIR
-	bin/buildindexedfile.sh -a build/ARTWORKDIR build/ARTWORK.IDX build/TOTAL.DATA res/ARTWORK.SHR >>build/log
-#
-# create _FileInformation.txt files for subdirectories
-#
-	bin/buildfileinfo.sh res/TITLE.HGR "06" "4000" >>build/log
-	bin/buildfileinfo.sh res/TITLE.DHGR "06" "4000" >>build/log
-	bin/buildfileinfo.sh res/ACTION.DHGR "06" "3FF8" >>build/log
-	bin/buildfileinfo.sh res/ACTION.GR "06" "6000" >>build/log
-	bin/buildfileinfo.sh res/ICONS "CA" "0000" >>build/log
-	bin/buildfileinfo.sh build/FX "06" "6000" >>build/log
-	bin/buildfileinfo.sh build/PRELAUNCH "06" "0106" >>build/log
-#
-# add everything to the disk
-#
-	for f in \
-		build/TOTAL.DATA \
-		res/TITLE \
-		res/COVER \
-		res/HELP \
-		build/GAMES.CONF \
-		build/PREFS.CONF \
-		build/CREDITS \
-		build/HELPTEXT \
-		build/ATTRACT.IDX \
-		build/FX.IDX \
-		build/DFX.IDX \
-		build/GAMEHELP.IDX \
-		build/SLIDESHOW.IDX \
-		build/MINIATTRACT.IDX \
-		build/PRELAUNCH.IDX \
-		build/ARTWORK.IDX \
-		build/HGR0.IDX \
-		build/HGR1.IDX \
-		build/HGR2.IDX \
-		build/HGR3.IDX \
-		build/HGR4.IDX \
-		build/HGR5.IDX \
-		build/HGR6.IDX \
-		res/DECRUNCH \
-		res/JOYSTICK \
-		res/Finder.Data \
-		res/Finder.Root; do \
-	    $(CADIUS) ADDFILE build/"$(DISK)" "/$(VOLUME)/" "$$f" >>build/log; \
-	done
-	for f in \
-		res/TITLE.HGR \
-		res/TITLE.DHGR \
-		res/ACTION.DHGR \
-		res/ACTION.GR \
-                res/DEMO \
-                res/TITLE.ANIMATED \
-                res/ICONS \
-		build/FX \
-		build/PRELAUNCH; do \
-            rm -f "$$f"/.DS_Store; \
-            $(CADIUS) ADDFOLDER build/"$(DISK)" "/$(VOLUME)/$$(basename $$f)" "$$f" >>build/log; \
-        done
-	for i in 1 2 3 4 5 6; do \
-		$(CADIUS) RENAMEFILE build/"$(DISK)" "/$(VOLUME)/DEMO/SPCARTOON.$${i}$${i}" "SPCARTOON.$${i}." >>build/log; \
-	done
-	for f in res/dsk/*.po; do \
-	    $(CADIUS) EXTRACTVOLUME "$${f}" build/X/ >>build/log; \
-	done
-	rm -f build/X/**/.DS_Store build/X/**/PRODOS* build/X/**/LOADER.SYSTEM*
-	$(CADIUS) CREATEFOLDER build/"$(DISK)" "/$(VOLUME)/X/" >>build/log
-	for f in build/X/*; do \
-	    $(CADIUS) ADDFOLDER build/"$(DISK)" "/$(VOLUME)/X/$$(basename $$f)" "$$f" >>build/log; \
-	done
-	bin/changebootloader.sh build/"$(DISK)" build/proboothd
-
-asm: asmlauncher asmfx asmprelaunch asmproboot
-
-asmlauncher: md
-	$(ACME) -DBUILDNUMBER=`git rev-list --count HEAD` src/4cade.a 2>build/relbase.log
-	$(ACME) -r build/4cade.lst -DBUILDNUMBER=`git rev-list --count HEAD` -DRELBASE=`cat build/relbase.log | grep "RELBASE =" | cut -d"=" -f2 | cut -d"(" -f2 | cut -d")" -f1` src/4cade.a
-
-asmfx: md
-	for f in src/fx/*.a; do \
-	    grep "^\!to" $${f} >/dev/null && $(ACME) $${f} >> build/log || true; \
-	done
-
-asmprelaunch: md
-	for f in src/prelaunch/*.a; do \
-	    grep "^\!to" $${f} >/dev/null && $(ACME) $${f} >> build/log; \
-	done
-
-asmproboot: md
-	$(ACME) -r build/proboothd.lst src/proboothd/proboothd.a >> build/log
-
-#
-# |compress| and |attract| must be called separately because they are slow.
-# They create files in the repository which can then be checked in.
-#
-compress: md
-	for f in res/ACTION.HGR.UNCOMPRESSED/*; do  o=res/ACTION.HGR/$$(basename $$f);  [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x4000 -o "$$o" >>build/log; done
-	for f in res/ACTION.DHGR.UNCOMPRESSED/*; do o=res/ACTION.DHGR/$$(basename $$f); [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x4000 -o "$$o" >>build/log; done
-	for f in res/ARTWORK.SHR.UNCOMPRESSED/*; do o=res/ARTWORK.SHR/$$(basename $$f); [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x2000 -o "$$o" >>build/log; done
-
-attract: compress
-	bin/check-attract-mode.sh
-	bin/generate-mini-attract-mode.sh
-
-mount: dsk
-	osascript bin/V2Make.scpt "`pwd`" bin/4cade.vii build/"$(DISK)"
-
-md:
-	mkdir -p build/X build/FX.INDEXED build/FX build/PRELAUNCH.INDEXED build/PRELAUNCH build/ATTRACT build/SS build/GAMEHELP
-	touch build/log
-
-clean:
-	rm -rf build/ || rm -rf build
-
-all: clean dsk mount
-
-al: all
+#
+# 4cade Makefile
+# assembles source code, optionally builds a disk image and mounts it
+# note: Windows users should probably use winmake.bat instead
+#
+# original by Quinn Dunki on 2014-08-15
+# One Girl, One Laptop Productions
+# http://www.quinndunki.com/blondihacks
+#
+# adapted by 4am on 2018-08-19
+#
+
+DISK=4cade.hdv
+VOLUME=TOTAL.REPLAY
+
+# third-party tools required to build
+
+# https://sourceforge.net/projects/acme-crossass/
+# version 0.96.3 or later
+ACME=acme
+
+# https://github.com/mach-kernel/cadius
+# version 1.4.0 or later
+CADIUS=cadius
+
+# https://bitbucket.org/magli143/exomizer/wiki/Home
+# version 3.1.0 or later
+EXOMIZER=exomizer mem -q -P23 -lnone
+
+dsk: asm
+	cp res/blank.hdv build/"$(DISK)" >>build/log
+	cp res/_FileInformation.txt build/ >>build/log
+	$(CADIUS) ADDFILE build/"$(DISK)" "/$(VOLUME)/" build/LAUNCHER.SYSTEM >>build/log
+	cp res/PREFS.CONF build/PREFS.CONF >>build/log
+	bin/padto.sh 512 build/PREFS.CONF >>build/log
+#
+# precompute binary data structure for mega-attract mode configuration file
+#
+	bin/buildokvs.sh build/ATTRACT.IDX < res/ATTRACT.CONF >>build/log
+#
+# precompute binary data structure and substitute special characters
+# in game help and other all-text pages
+#
+	bin/converthelp.sh res/HELPTEXT build/HELPTEXT >>build/log
+	bin/converthelp.sh res/CREDITS build/CREDITS >>build/log
+	for f in res/GAMEHELP/*; do \
+	    bin/converthelp.sh "$$f" build/GAMEHELP/"$$(basename $$f)" >>build/log; \
+	done
+#
+# create distribution version of GAMES.CONF without comments or blank lines
+#
+	awk '!/^$$|^#/' < res/GAMES.CONF > build/GAMES.CONF
+#
+# create a sorted list of game filenames, without metadata or display names
+#
+	awk -F, '/,/ { print $$2 }' < build/GAMES.CONF | awk -F= '{ print $$1 }' | sort > build/GAMES.SORTED
+#
+# create search indexes
+#
+	bin/builddisplaynames.py < res/GAMES.CONF > build/DISPLAY.CONF
+	grep "^00" < build/DISPLAY.CONF | cut -d"," -f2 | bin/buildokvs.sh build/SEARCH00.IDX
+	grep "^0" < build/DISPLAY.CONF | cut -d"," -f2 | bin/buildokvs.sh build/SEARCH01.IDX
+	grep "^.0" < build/DISPLAY.CONF | cut -d"," -f2 | bin/buildokvs.sh build/SEARCH10.IDX
+	cat build/DISPLAY.CONF | cut -d"," -f2 | bin/buildokvs.sh build/SEARCH11.IDX
+#
+# precompute indexed files for prelaunch
+# note: prelaunch must be first in TOTAL.DATA due to a hack in LoadStandardPrelaunch
+# note 2: these can not be padded because they are loaded at $0106 and padding would clobber the stack
+#
+	bin/buildindexedfile.sh build/GAMES.SORTED build/PRELAUNCH.IDX build/TOTAL.DATA build/PRELAUNCH.INDEXED >>build/log
+#
+# precompute indexed files for game help
+#
+	bin/buildindexedfile.sh -p -a build/GAMES.SORTED build/GAMEHELP.IDX build/TOTAL.DATA build/GAMEHELP >>build/log
+#
+# precompute indexed files for slideshows
+#
+	(for f in res/SS/*; do \
+	    bin/buildokvs.sh "build/SS/$$(basename $$f)" < "$$f"; \
+	    echo "$$(basename $$f)"; \
+	done) > build/SSDIR
+	bin/buildindexedfile.sh -p -a build/SSDIR build/SLIDESHOW.IDX build/TOTAL.DATA build/SS >>build/log
+	(for f in res/ATTRACT/*; do \
+	    bin/buildokvs.sh "build/ATTRACT/$$(basename $$f)" < "$$f"; \
+	    echo "$$(basename $$f)"; \
+	done) > build/ATTRACTDIR
+	bin/buildindexedfile.sh -p -a build/ATTRACTDIR build/MINIATTRACT.IDX build/TOTAL.DATA build/ATTRACT >>build/log
+#
+# precompute indexed files for graphic effects
+#
+	bin/buildindexedfile.sh -p -a res/FX.CONF build/FX.IDX build/TOTAL.DATA build/FX.INDEXED >>build/log
+	bin/buildindexedfile.sh -p -a res/DFX.CONF build/DFX.IDX build/TOTAL.DATA build/FX.INDEXED >>build/log
+#
+# precompute indexed files for HGR action screenshots
+# note: these can not be padded because they are compressed and the decompressor needs the exact size
+#
+	(for f in res/ACTION.HGR/[ABCD]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR0
+	(for f in res/ACTION.HGR/[EFGH]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR1
+	(for f in res/ACTION.HGR/[IJKL]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR2
+	(for f in res/ACTION.HGR/[MNOP]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR3
+	(for f in res/ACTION.HGR/[QRST]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR4
+	(for f in res/ACTION.HGR/[UVWX]*; do echo "$$(basename $$f)"; done) > build/ACTIONHGR5
+	(for f in res/ACTION.HGR/[YZ]*;   do echo "$$(basename $$f)"; done) > build/ACTIONHGR6
+	bin/buildindexedfile.sh -a build/ACTIONHGR0 build/HGR0.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR1 build/HGR1.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR2 build/HGR2.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR3 build/HGR3.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR4 build/HGR4.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR5 build/HGR5.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+	bin/buildindexedfile.sh -a build/ACTIONHGR6 build/HGR6.IDX build/TOTAL.DATA res/ACTION.HGR >>build/log
+#
+# precompute indexed files for SHR artwork
+# note: these can not be padded because they are compressed and the decompressor needs the exact size
+#
+	(for f in res/ARTWORK.SHR/*; do \
+	    echo "$$(basename $$f)"; \
+	done) > build/ARTWORKDIR
+	bin/buildindexedfile.sh -a build/ARTWORKDIR build/ARTWORK.IDX build/TOTAL.DATA res/ARTWORK.SHR >>build/log
+#
+# create _FileInformation.txt files for subdirectories
+#
+	bin/buildfileinfo.sh res/TITLE.HGR "06" "4000" >>build/log
+	bin/buildfileinfo.sh res/TITLE.DHGR "06" "4000" >>build/log
+	bin/buildfileinfo.sh res/ACTION.DHGR "06" "3FF8" >>build/log
+	bin/buildfileinfo.sh res/ACTION.GR "06" "6000" >>build/log
+	bin/buildfileinfo.sh res/ICONS "CA" "0000" >>build/log
+	bin/buildfileinfo.sh build/FX "06" "6000" >>build/log
+	bin/buildfileinfo.sh build/PRELAUNCH "06" "0106" >>build/log
+#
+# add everything to the disk
+#
+	for f in \
+		build/TOTAL.DATA \
+		res/TITLE \
+		res/COVER \
+		res/HELP \
+		build/GAMES.CONF \
+		build/PREFS.CONF \
+		build/CREDITS \
+		build/HELPTEXT \
+		build/ATTRACT.IDX \
+		build/SEARCH00.IDX \
+		build/SEARCH01.IDX \
+		build/SEARCH10.IDX \
+		build/SEARCH11.IDX \
+		build/FX.IDX \
+		build/DFX.IDX \
+		build/GAMEHELP.IDX \
+		build/SLIDESHOW.IDX \
+		build/MINIATTRACT.IDX \
+		build/PRELAUNCH.IDX \
+		build/ARTWORK.IDX \
+		build/HGR0.IDX \
+		build/HGR1.IDX \
+		build/HGR2.IDX \
+		build/HGR3.IDX \
+		build/HGR4.IDX \
+		build/HGR5.IDX \
+		build/HGR6.IDX \
+		res/DECRUNCH \
+		res/JOYSTICK \
+		res/Finder.Data \
+		res/Finder.Root; do \
+	    $(CADIUS) ADDFILE build/"$(DISK)" "/$(VOLUME)/" "$$f" >>build/log; \
+	done
+	for f in \
+		res/TITLE.HGR \
+		res/TITLE.DHGR \
+		res/ACTION.DHGR \
+		res/ACTION.GR \
+                res/DEMO \
+                res/TITLE.ANIMATED \
+                res/ICONS \
+		build/FX \
+		build/PRELAUNCH; do \
+            rm -f "$$f"/.DS_Store; \
+            $(CADIUS) ADDFOLDER build/"$(DISK)" "/$(VOLUME)/$$(basename $$f)" "$$f" >>build/log; \
+        done
+	for i in 1 2 3 4 5 6; do \
+		$(CADIUS) RENAMEFILE build/"$(DISK)" "/$(VOLUME)/DEMO/SPCARTOON.$${i}$${i}" "SPCARTOON.$${i}." >>build/log; \
+	done
+	for f in res/dsk/*.po; do \
+	    $(CADIUS) EXTRACTVOLUME "$${f}" build/X/ >>build/log; \
+	done
+	rm -f build/X/**/.DS_Store build/X/**/PRODOS* build/X/**/LOADER.SYSTEM*
+	$(CADIUS) CREATEFOLDER build/"$(DISK)" "/$(VOLUME)/X/" >>build/log
+	for f in build/X/*; do \
+	    $(CADIUS) ADDFOLDER build/"$(DISK)" "/$(VOLUME)/X/$$(basename $$f)" "$$f" >>build/log; \
+	done
+	bin/changebootloader.sh build/"$(DISK)" build/proboothd
+
+asm: asmlauncher asmfx asmprelaunch asmproboot
+
+asmlauncher: md
+	$(ACME) -DBUILDNUMBER=`git rev-list --count HEAD` src/4cade.a 2>build/relbase.log
+	$(ACME) -r build/4cade.lst -DBUILDNUMBER=`git rev-list --count HEAD` -DRELBASE=`cat build/relbase.log | grep "RELBASE =" | cut -d"=" -f2 | cut -d"(" -f2 | cut -d")" -f1` src/4cade.a
+
+asmfx: md
+	for f in src/fx/*.a; do \
+	    grep "^\!to" $${f} >/dev/null && $(ACME) $${f} >> build/log || true; \
+	done
+
+asmprelaunch: md
+	for f in src/prelaunch/*.a; do \
+	    grep "^\!to" $${f} >/dev/null && $(ACME) $${f} >> build/log; \
+	done
+
+asmproboot: md
+	$(ACME) -r build/proboothd.lst src/proboothd/proboothd.a >> build/log
+
+#
+# |compress| and |attract| must be called separately because they are slow.
+# They create files in the repository which can then be checked in.
+#
+compress: md
+	for f in res/ACTION.HGR.UNCOMPRESSED/*; do  o=res/ACTION.HGR/$$(basename $$f);  [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x4000 -o "$$o" >>build/log; done
+	for f in res/ACTION.DHGR.UNCOMPRESSED/*; do o=res/ACTION.DHGR/$$(basename $$f); [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x4000 -o "$$o" >>build/log; done
+	for f in res/ARTWORK.SHR.UNCOMPRESSED/*; do o=res/ARTWORK.SHR/$$(basename $$f); [ -f "$$o" ] || ${EXOMIZER} "$$f"@0x2000 -o "$$o" >>build/log; done
+
+attract: compress
+	bin/check-attract-mode.sh
+	bin/generate-mini-attract-mode.sh
+
+mount: dsk
+	osascript bin/V2Make.scpt "`pwd`" bin/4cade.vii build/"$(DISK)"
+
+md:
+	mkdir -p build/X build/FX.INDEXED build/FX build/PRELAUNCH.INDEXED build/PRELAUNCH build/ATTRACT build/SS build/GAMEHELP
+	touch build/log
+
+clean:
+	rm -rf build/ || rm -rf build
+
+all: clean dsk mount
+
+al: all
diff --git a/bin/builddisplaynames.py b/bin/builddisplaynames.py
new file mode 100755
index 000000000..cdc2f7b57
--- /dev/null
+++ b/bin/builddisplaynames.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+# usage:
+# builddisplaynames.py < res/GAMES.CONF > build/GAMES.CONF.WITH.NAMES
+
+import sys
+
+for line in sys.stdin:
+    if "," not in line: continue
+    if line.startswith("#"): continue
+    if "=" not in line:
+        prefix, key = line.split(",")
+        line = prefix + "," + key.strip() + "=" + key.replace(".", " ").title().replace(" Ii", " II")
+    print(line, end="")
diff --git a/bin/buildokvs.sh b/bin/buildokvs.sh
index 6d78145f6..e88ab1327 100755
--- a/bin/buildokvs.sh
+++ b/bin/buildokvs.sh
@@ -4,7 +4,7 @@
 
 # make temp file with just the key/value pairs (strip blank lines, comments, eof marker)
 records=$(mktemp)
-grep -v "^$" < "$1" | grep -v "^#" | grep -v "^\[" > "$records"
+grep -v "^$" | grep -v "^#" | grep -v "^\[" > "$records"
 
 # make temp assembly source file that represents the binary OKVS data structure
 source=$(mktemp)
@@ -19,7 +19,7 @@ source=$(mktemp)
  done < "$records") > "$source"
 
 # assemble temp source file to create binary OKVS data structure
-acme -o "$2" "$source"
+acme -o "$1" "$source"
 
 # clean up
 rm "$source"
diff --git a/res/ATTRACT/AO b/res/ATTRACT/AO
new file mode 100644
index 000000000..1c5504b47
--- /dev/null
+++ b/res/ATTRACT/AO
@@ -0,0 +1,7 @@
+#
+# Attract mode for AO
+# This file is automatically generated
+#
+
+
+[eof]
diff --git a/res/ATTRACT/CHIP.OUT b/res/ATTRACT/CHIP.OUT
new file mode 100644
index 000000000..0e4649833
--- /dev/null
+++ b/res/ATTRACT/CHIP.OUT
@@ -0,0 +1,7 @@
+#
+# Attract mode for CHIP.OUT
+# This file is automatically generated
+#
+
+
+[eof]
diff --git a/res/ATTRACT/VV b/res/ATTRACT/VV
new file mode 100644
index 000000000..7457d9800
--- /dev/null
+++ b/res/ATTRACT/VV
@@ -0,0 +1,7 @@
+#
+# Attract mode for VV
+# This file is automatically generated
+#
+
+
+[eof]
diff --git a/res/_FileInformation.txt b/res/_FileInformation.txt
index 148f4017d..9bc4bdf11 100644
--- a/res/_FileInformation.txt
+++ b/res/_FileInformation.txt
@@ -19,6 +19,10 @@ HGR3.IDX=Type(06),AuxType(4000),Access(C3)
 HGR4.IDX=Type(06),AuxType(4000),Access(C3)
 HGR5.IDX=Type(06),AuxType(4000),Access(C3)
 HGR6.IDX=Type(06),AuxType(4000),Access(C3)
+SEARCH00.IDX=Type(06),AuxType(8200),Access(C3)
+SEARCH01.IDX=Type(06),AuxType(8200),Access(C3)
+SEARCH10.IDX=Type(06),AuxType(8200),Access(C3)
+SEARCH11.IDX=Type(06),AuxType(8200),Access(C3)
 COVER=Type(06),AuxType(2000),Access(C3)
 TITLE=Type(06),AuxType(2000),Access(C3)
 HELP=Type(06),AuxType(2000),Access(C3)
diff --git a/src/4cade.init.a b/src/4cade.init.a
index 10802cc72..d45c9e11a 100644
--- a/src/4cade.init.a
+++ b/src/4cade.init.a
@@ -413,6 +413,19 @@ CopyDevs
          +READ_RAM1_WRITE_RAM1
 }
 
+         ldx   #$30
+         lda   MachineStatus
+         and   #HAS_JOYSTICK
+         beq   +
+         inx
++        stx   gSearchHasJoystick
+         ldx   #$30
+         lda   MachineStatus
+         and   #HAS_128K
+         beq   +
+         inx
++        stx   gSearchHas128K
+
          +LDADDR gGamesListStore
          jsr   okvs_len
          +LD16 WCOUNT
diff --git a/src/constants.a b/src/constants.a
index 85fc88a5a..664ea1b85 100644
--- a/src/constants.a
+++ b/src/constants.a
@@ -7,7 +7,7 @@
 ;
 ; LC RAM BANK 1
 ; D000..E7D2 - persistent data structures (gGlobalPrefsStore, gGamesListStore)
-; E92D..FFEE - main program code
+; E9E8..FFEE - main program code
 ; FFEF..FFF9 - API functions and global constants available for main program
 ;              code, prelaunchers, transition effects, &c.
 ;              (LoadFileDirect, Wait/UnwaitForVBL, MockingboardStuff, MachineStatus)
@@ -149,7 +149,7 @@ CHEATS_ENABLED = %00001000
 iCurBlockLo    = $D401               ; constant
 iCurBlockHi    = $D403               ; constant
 launchpatch    = $D661               ; glue.launch.a
-iAddToPath     = $FEA5               ; Roger Rabbit, avoid, use Infiltrator 2 style instead
+iAddToPath     = $FE98               ; Roger Rabbit, avoid, use Infiltrator 2 style instead
 itraverse      = $D958               ; Roger Rabbit, avoid, use Infiltrator 2 style instead
 ldrlo          = $55                 ; constant
 ldrhi          = $56                 ; constant
diff --git a/src/okvs.a b/src/okvs.a
index 455e54c9f..4ad11413e 100644
--- a/src/okvs.a
+++ b/src/okvs.a
@@ -118,109 +118,6 @@ okvs_len_imm
          ora   WCOUNT
          rts
 
-;------------------------------------------------------------------------------
-; okvs_append
-;
-; in:    stack contains 7 bytes of parameters:
-;          +1 [word] handle to storage space
-;          +3 [word] address of key
-;          +5 [word] address of value
-;          +7 [byte] maximum length of value (or 0 to fit)
-; out:   (new record count is not returned because no one cares)
-;        all registers clobbered
-;        $00/$01 clobbered
-;        $02/$03 clobbered
-;        $04/$05 has the address of the next available byte after the new record
-;        $08/$09 clobbered
-;------------------------------------------------------------------------------
-okvs_append
-         +PARAMS_ON_STACK 7
-         jsr   GetStoreAddress
-                                     ; PTR -> store
-                                     ; Y = 0
-         lda   (PTR),y               ; A = number of keys in store
-         sta   WINDEX
-         iny
-         lda   (PTR), y
-         sta   WINDEX+1
-         inc   WINDEX
-         bne   +
-         inc   WINDEX+1
-+
-         dey
-         lda   WINDEX
-         sta   (PTR),y               ; increment number of keys
-         lda   WINDEX+1
-         iny
-         sta   (PTR),y
-         iny
-         lda   (PTR),y               ; get address of next free space
-         tax
-         iny
-         lda   (PTR),y
-         sta   PTR+1
-         sta   SAVE+1
-         stx   PTR
-         stx   SAVE
-                                     ; PTR -> new record
-                                     ; SAVE -> new record
-         jsr   incptr
-                                     ; PTR -> space for new key
-         +LDPARAMPTR 3, SRC          ; SRC -> new key to copy
-         ldy   #0
-         lda   (SRC),y
-         tay
-         tax
--        lda   (SRC),y               ; copy new key
-         sta   (PTR),y
-         dey
-         cpy   #$FF
-         bne   -
-
-         ;;sec
-         txa
-         adc   PTR                   ; update PTR to byte after copied key
-         sta   PTR
-         bcc   +
-         inc   PTR+1
-+                                    ; PTR -> space for new value
-         +LDPARAMPTR 5, SRC          ; SRC -> new value to copy
-         iny ;;ldy   #7
-         lda   (PARAM),y             ; get max length of value
-         tax
-         bne   +
-         tay
-         lda   (SRC),y               ; no max, use actual length instead
-         tax
-+        inx
-         tay
--        lda   (SRC),y
-         sta   (PTR),y
-         dey
-         cpy   #$FF
-         bne   -
-
-         txa
-         clc
-         adc   PTR
-         tax
-         lda   PTR+1
-         adc   #0
-         pha
-+        jsr   GetStoreAddress
-                                     ; PTR -> store
-         pla
-         ldy   #3
-         sta   (PTR),y               ; update next-free-space pointer in head
-         dey
-         txa
-         sta   (PTR),y
-         sec
-         sbc   SAVE
-         ldy   #0
-         sta   (SAVE),y              ; set record length
-         rts
-
 ;------------------------------------------------------------------------------
 ; okvs_find / okvs_get
 ;
diff --git a/src/parse.common.a b/src/parse.common.a
index 742a8f1a6..6ec7d5e99 100644
--- a/src/parse.common.a
+++ b/src/parse.common.a
@@ -135,3 +135,106 @@ IncAndGetChar
          beq   IncAndGetChar
 .parseKeyValueDone
          rts
+
+;------------------------------------------------------------------------------
+; okvs_append
+;
+; in:    stack contains 7 bytes of parameters:
+;          +1 [word] handle to storage space
+;          +3 [word] address of key
+;          +5 [word] address of value
+;          +7 [byte] maximum length of value (or 0 to fit)
+; out:   (new record count is not returned because no one cares)
+;        all registers clobbered
+;        $00/$01 clobbered
+;        $02/$03 clobbered
+;        $04/$05 has the address of the next available byte after the new record
+;        $08/$09 clobbered
+;------------------------------------------------------------------------------
+okvs_append
+         +PARAMS_ON_STACK 7
+         jsr   GetStoreAddress
+                                     ; PTR -> store
+                                     ; Y = 0
+         lda   (PTR),y               ; A = number of keys in store
+         sta   WINDEX
+         iny
+         lda   (PTR), y
+         sta   WINDEX+1
+         inc   WINDEX
+         bne   +
+         inc   WINDEX+1
++
+         dey
+         lda   WINDEX
+         sta   (PTR),y               ; increment number of keys
+         lda   WINDEX+1
+         iny
+         sta   (PTR),y
+         iny
+         lda   (PTR),y               ; get address of next free space
+         tax
+         iny
+         lda   (PTR),y
+         sta   PTR+1
+         sta   SAVE+1
+         stx   PTR
+         stx   SAVE
+                                     ; PTR -> new record
+                                     ; SAVE -> new record
+         jsr   incptr
+                                     ; PTR -> space for new key
+         +LDPARAMPTR 3, SRC          ; SRC -> new key to copy
+         ldy   #0
+         lda   (SRC),y
+         tay
+         tax
+-        lda   (SRC),y               ; copy new key
+         sta   (PTR),y
+         dey
+         cpy   #$FF
+         bne   -
+
+         ;;sec
+         txa
+         adc   PTR                   ; update PTR to byte after copied key
+         sta   PTR
+         bcc   +
+         inc   PTR+1
++                                    ; PTR -> space for new value
+         +LDPARAMPTR 5, SRC          ; SRC -> new value to copy
+         iny ;;ldy   #7
+         lda   (PARAM),y             ; get max length of value
+         tax
+         bne   +
+         tay
+         lda   (SRC),y               ; no max, use actual length instead
+         tax
++        inx
+         tay
+-        lda   (SRC),y
+         sta   (PTR),y
+         dey
+         cpy   #$FF
+         bne   -
+
+         txa
+         clc
+         adc   PTR
+         tax
+         lda   PTR+1
+         adc   #0
+         pha
++        jsr   GetStoreAddress
+                                     ; PTR -> store
+         pla
+         ldy   #3
+         sta   (PTR),y               ; update next-free-space pointer in head
+         dey
+         txa
+         sta   (PTR),y
+         sec
+         sbc   SAVE
+         ldy   #0
+         sta   (SAVE),y              ; set record length
+         rts
diff --git a/src/prodos.path.a b/src/prodos.path.a
index 4232b6c8d..a35561247 100644
--- a/src/prodos.path.a
+++ b/src/prodos.path.a
@@ -190,3 +190,12 @@ kDecrunchFile
 kJoystickFile
          !byte 8
          !raw  "JOYSTICK"
+
+kSearchIndexFile
+         !byte 12
+         !raw  "SEARCH"
+gSearchHasJoystick
+         !raw  "_"
+gSearchHas128K
+         !raw  "_"
+         !raw  ".IDX"
diff --git a/src/textrank.a b/src/textrank.a
index 61ebe76c6..42df7b7ed 100644
--- a/src/textrank.a
+++ b/src/textrank.a
@@ -36,32 +36,10 @@ InputBuffer
 ; out:   gSearchStore populated
 ;------------------------------------------------------------------------------
 BuildSearchStore
-         jsr   SwitchToBank2
-         jsr   EnableAcceleratorAndSwitchToBank1
-         +LDADDR gSearchStore
-         jsr   okvs_init
-
-         jsr   okvs_iter
-         !word gGamesListStore
-         !word @callback
-
-         jsr   SwitchToBank2
-         jmp   DisableAcceleratorAndSwitchToBank1
-@callback
-; callback called by okvs_iter on gGamesListStore
-
-; in:    A/Y contains address of filename
-;        $WINDEX contains 0-based index of the current record in gGamesListStore (word)
-; out:   all registers and flags clobbered
-         +ST16 @key
-         jsr   GetGameDisplayName
-         +ST16 @value
-@append
-         jsr   okvs_append
-         !word gSearchStore
-@key     !word $FDFD                 ; SMC
-@value   !word $FDFD                 ; SMC
-         !byte 0
+         jsr   LoadFile              ; load appropriate search index into $8200
+         !word kRootDirectory
+         !word kSearchIndexFile
+         !word $8200
          rts
 
 ;------------------------------------------------------------------------------