diff --git a/.gitignore b/.gitignore
index dd11a83f..f210024f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,15 @@
-/OutlawEditor/target/
\ No newline at end of file
+# Ignore vi swap files, MacOS extra files, etc.
+*.swp
+.DS_Store
+
+# Skip build directories
+**/build/
+/OutlawEditor/target/
+/Platform/Apple/tools/A2Copy/nbproject/private/
+/Platform/Apple/tools/A2Copy/dist/
+
+# Don't check in data specific to any particular game
+/Platform/Apple/virtual/data/images/*.bin
+
+# Only check in sample.build.props; each person's build.props will be different.
+/Platform/Apple/virtual/src/include/build.props
diff --git a/Platform/Apple/README.md b/Platform/Apple/README.md
new file mode 100644
index 00000000..7b1e561d
--- /dev/null
+++ b/Platform/Apple/README.md
@@ -0,0 +1,19 @@
+Building for the Apple II Platform
+==================================
+
+1. Build the A2Copy tool, which copies directories in/out of image files: `cd tools/A2Copy`
+ and then `ant`, and finally `cd ../..`
+
+2. Set the location of the "cc65" tool set. Make a copy of `virtual/src/include/sample.build.props`
+ and call it `build.props` (in that same directory). Edit the `CC65_BIN_DIR` path inside that file
+ to point at your cc65 installation. There should be "ca65", "cc65", "ld65" etc. in the directory
+ you point to.
+
+3. Add game data. Grab sample game data (images, maps, etc.) and put them into the `data` directory.
+ Image .bin files go in `data/images`, etc.
+
+4. Now build a complete disk image: `cd virtual` and then `ant`
+
+5. Boot up the resulting disk image `game.2mg` on your Apple II or emulator.
+
+6. To run the render demo, type `-RENDER` at the ProDOS Basic prompt.
diff --git a/Platform/Apple/tools/A2Copy/build.xml b/Platform/Apple/tools/A2Copy/build.xml
new file mode 100644
index 00000000..f29a3fd4
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/build.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+ Builds, tests, and runs the project A2Copy.
+
+
+
diff --git a/Platform/Apple/tools/A2Copy/lib/VERSIONS b/Platform/Apple/tools/A2Copy/lib/VERSIONS
new file mode 100644
index 00000000..084b131a
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/lib/VERSIONS
@@ -0,0 +1 @@
+ac.jar = AppleCommander-1.3.5.13-ac.jar with Martn's patch for proper sub-directory creation
diff --git a/Platform/Apple/tools/A2Copy/lib/ac.jar b/Platform/Apple/tools/A2Copy/lib/ac.jar
new file mode 100644
index 00000000..c5748fb2
Binary files /dev/null and b/Platform/Apple/tools/A2Copy/lib/ac.jar differ
diff --git a/Platform/Apple/tools/A2Copy/manifest.mf b/Platform/Apple/tools/A2Copy/manifest.mf
new file mode 100644
index 00000000..328e8e5b
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/Platform/Apple/tools/A2Copy/nbproject/build-impl.xml b/Platform/Apple/tools/A2Copy/nbproject/build-impl.xml
new file mode 100644
index 00000000..2d13575a
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/nbproject/build-impl.xml
@@ -0,0 +1,1402 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No tests executed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set JVM to use for profiling in profiler.info.jvm
+ Must set profiler agent JVM arguments in profiler.info.jvmargs.agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set profile.class
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+ Must select some files in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+ Must select one file in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Platform/Apple/tools/A2Copy/nbproject/genfiles.properties b/Platform/Apple/tools/A2Copy/nbproject/genfiles.properties
new file mode 100644
index 00000000..572891af
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=5246bcd2
+build.xml.script.CRC32=43b3eb1f
+build.xml.stylesheet.CRC32=8064a381@1.68.1.46
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=5246bcd2
+nbproject/build-impl.xml.script.CRC32=ca8bcaaf
+nbproject/build-impl.xml.stylesheet.CRC32=cdba79fa@1.68.1.46
diff --git a/Platform/Apple/tools/A2Copy/nbproject/project.properties b/Platform/Apple/tools/A2Copy/nbproject/project.properties
new file mode 100644
index 00000000..1c561b66
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/nbproject/project.properties
@@ -0,0 +1,75 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=A2Copy
+application.vendor=mhaye
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/A2Copy.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+file.reference.ac.jar=lib/ac.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.ac.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=1.7
+javac.target=1.7
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+main.class=a2copy.A2Copy
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/Platform/Apple/tools/A2Copy/nbproject/project.xml b/Platform/Apple/tools/A2Copy/nbproject/project.xml
new file mode 100644
index 00000000..61be540c
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/nbproject/project.xml
@@ -0,0 +1,15 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ A2Copy
+
+
+
+
+
+
+
+
+
diff --git a/Platform/Apple/tools/A2Copy/src/a2copy/A2Copy.java b/Platform/Apple/tools/A2Copy/src/a2copy/A2Copy.java
new file mode 100644
index 00000000..3517b590
--- /dev/null
+++ b/Platform/Apple/tools/A2Copy/src/a2copy/A2Copy.java
@@ -0,0 +1,281 @@
+
+package a2copy;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.webcodepro.applecommander.storage.DirectoryEntry;
+import com.webcodepro.applecommander.storage.Disk;
+import com.webcodepro.applecommander.storage.DiskFullException;
+import com.webcodepro.applecommander.storage.FileEntry;
+import com.webcodepro.applecommander.storage.FormattedDisk;
+import com.webcodepro.applecommander.storage.os.prodos.ProdosFileEntry;
+
+/*
+ * This class uses AppleCommander's command-line interface to extract an entire
+ * set of files and directories from an image, or build a whole image from
+ * files and directories.
+ *
+ * Has specific hard-coded addresses for Lawless Legends files; should fix this
+ * at some point to use a metadata configuration file or something like that.
+ */
+public class A2Copy
+{
+ /*
+ * Main command-line driver
+ */
+ public static void main(String[] args)
+ throws IOException, DiskFullException
+ {
+ try
+ {
+ if (args[0].equals("-get")) {
+ getDirFromImg(args[1], args[2], args[3]);
+ return;
+ }
+ if (args[0].equals("-put")) {
+ putDirToImg(args[1], args[2], args[3]);
+ return;
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ { }
+ System.err.format("Usage: A2copy [-get imgFile srcImgDir destLocalDir] | [-put imgFile dstImgDir srcLocalDir]\n");
+ System.err.format(" where srcImgDir/dstImgDir is a subdirectory in the image, or / for the root directory.\n");
+ System.exit(1);
+ }
+
+ /** Patterns used for parsing filenames and paths */
+ static Pattern extPat = Pattern.compile("^(.*)\\.([^.]+)$");
+ static Pattern hashPat = Pattern.compile("^(.*)#([^#]+$)");
+ static Pattern pathPat = Pattern.compile("^/?([^/]+)(.*$)");
+
+ /**
+ * Extract all the files and subdirectories from one directory in an image file, and
+ * write them to the local filesystem.
+ *
+ * @throws IOException if something goes wrong
+ * @throws DiskFullException this actually can't happen (because we're not creating dirs)
+ */
+ static void getDirFromImg(String imgFile, String srcImgDir, String dstLocalDir) throws IOException, DiskFullException
+ {
+ // Create the local dir if necessary.
+ File localDirFile = new File(dstLocalDir);
+ localDirFile.mkdirs();
+
+ // Open the image file and get the disk inside it.
+ FormattedDisk fd = new Disk(imgFile).getFormattedDisks()[0];
+
+ // Locate the right subdirectory on the disk.
+ DirectoryEntry imgDir = findSubDir(fd, srcImgDir, false);
+
+ // Recursively extract the files from that subdirectory.
+ getAllFiles((List)imgDir.getFiles(), localDirFile);
+ }
+
+ static DirectoryEntry findSubDir(DirectoryEntry imgDir, String subDirs, boolean create)
+ throws DiskFullException
+ {
+ Matcher m = pathPat.matcher(subDirs);
+ if (m.matches())
+ {
+ // Process next component of the directory path.
+ String subName = m.group(1);
+ String remaining = m.group(2);
+ for (FileEntry e : (List)imgDir.getFiles()) {
+ if (!e.isDeleted() && e.isDirectory() && e.getFilename().equalsIgnoreCase(subName))
+ return findSubDir((DirectoryEntry)e, remaining, create);
+ }
+
+ // Not found. If we're not allowed to create it, error out.
+ if (!create) {
+ System.err.format("Error: subdirectory '%s' not found.\n", subDirs);
+ System.exit(2);
+ }
+
+ // Create the subdirectory and continue to sub-sub-dirs.
+ return findSubDir(imgDir.createDirectory(subName.toUpperCase()), remaining, create);
+ }
+
+ return imgDir;
+ }
+
+ /**
+ * Helper for file/directory extraction.
+ * @param files set of files to extract
+ * @param dstTargetDir where to put them
+ * @throws IOException if something goes wrong
+ */
+ static void getAllFiles(List files, File dstTargetDir) throws IOException
+ {
+ // Ensure the target directory exists
+ dstTargetDir.mkdir();
+
+ // Make a map of the existing filesystem files so we can match them. This way,
+ // we can retain whatever case regime the user has established on the filesystem.
+ //
+ HashMap existingFiles = new HashMap<>();
+ HashMap baseMap = new HashMap<>();
+ for (File f: dstTargetDir.listFiles())
+ {
+ if (!f.isFile())
+ continue;
+ String name = f.getName();
+ existingFiles.put(name.toLowerCase(), f);
+
+ Matcher m = hashPat.matcher(name);
+ if (m.matches())
+ name = m.group(1);
+ existingFiles.put(name.toLowerCase(), f);
+
+ m = extPat.matcher(name);
+ if (m.matches())
+ name = m.group(1);
+ existingFiles.put(name.toLowerCase(), f);
+
+ baseMap.put(f.getName(), name);
+ }
+
+ // Process each entry in the list
+ for (FileEntry e : files)
+ {
+ // Skip deleted things
+ if (e.isDeleted())
+ continue;
+
+ // Determine the filename we should use locally. If there's a matching
+ // file already here, use its base.
+ //
+ String baseName = e.getFilename().toLowerCase();
+ if (existingFiles.containsKey(baseName)) {
+ File existingFile = existingFiles.get(baseName);
+ baseName = baseMap.get(existingFile.getName());
+ existingFile.delete();
+ }
+
+ // Recursively process sub-directories
+ if (e.isDirectory()) {
+ File subDir = new File(dstTargetDir, baseName);
+ getAllFiles(((DirectoryEntry)e).getFiles(), subDir);
+ continue;
+ }
+
+ // Add a hash for the file type.
+ String outName = baseName + "." + e.getFiletype().toLowerCase();
+
+ // Add a hash for the address if this kind of entry uses one.
+ if (e.needsAddress()) {
+ int auxType = ((ProdosFileEntry)e).getAuxiliaryType();
+ outName = outName + "#" + Integer.toHexString(auxType);
+ }
+
+ // Ready to copy the data.
+ byte[] data = e.getFileData();
+ try (FileOutputStream out = new FileOutputStream(new File(dstTargetDir, outName))) {
+ out.write(data);
+ }
+ }
+ }
+
+ /**
+ * Put a whole directory of files/subdirs from the local filesystem into a
+ * subdirectory of an image file.
+ *
+ * @param imgFilePath path to the image file
+ * @param dstImgDir subdirectory in the image file, or "/" for the root
+ * @param srcLocalDir directory containing files and subdirs
+ * @throws DiskFullException if the image file fills up
+ * @throws IOException if something else goes wrong
+ */
+ static void putDirToImg(String imgFilePath, String dstImgDir, String srcLocalDir)
+ throws IOException, DiskFullException
+ {
+ // Make sure the local dir exists.
+ File localDirFile = new File(srcLocalDir);
+ if (!localDirFile.isDirectory()) {
+ System.err.format("Error: Local directory '%s' not found.\n", srcLocalDir);
+ System.exit(2);
+ }
+
+ // Open the image file.
+ FormattedDisk fd = new Disk(imgFilePath).getFormattedDisks()[0];
+
+ // Get to the right sub-directory.
+ DirectoryEntry ent = findSubDir(fd, dstImgDir, true);
+
+ // And fill it up.
+ putAllFiles(fd, ent, localDirFile);
+ }
+
+ /**
+ * Helper for image creation.
+ *
+ * @param fd disk to insert files into
+ * @param targetDir directory within the disk
+ * @param srcDir filesystem directory to read
+ * @throws DiskFullException if the image file fills up
+ * @throws IOException if something else goes wrong
+ */
+ private static void putAllFiles(FormattedDisk fd, DirectoryEntry targetDir, File srcDir)
+ throws DiskFullException, IOException
+ {
+ // Process each file in the source directory
+ for (File srcFile : srcDir.listFiles())
+ {
+ if (srcFile.isDirectory()) {
+ DirectoryEntry subDir = targetDir.createDirectory(srcFile.getName().toUpperCase());
+ putAllFiles(fd, subDir, srcFile);
+ continue;
+ }
+
+ // Parse and strip the hash (address) and extension if any
+ String name = srcFile.getName();
+ String hash = "0";
+ Matcher m = hashPat.matcher(name);
+ if (m.matches()) {
+ name = m.group(1);
+ hash = m.group(2);
+ }
+
+ String ext = "0";
+ m = extPat.matcher(name);
+ if (m.matches()) {
+ name = m.group(1);
+ ext = m.group(2);
+ }
+
+ // Create a new entry in the disk image for this file.
+ FileEntry ent = targetDir.createFile();
+ ent.setFilename(name.toUpperCase());
+
+ // Set the file type using the extension we parsed above.
+ ent.setFiletype(ext);
+
+ // Set the address if we have one and this kind of file wants one.
+ if (ent.needsAddress())
+ {
+ try {
+ ent.setAddress(Integer.parseInt(hash, 16));
+ }
+ catch (NumberFormatException e) { /*pass*/ }
+ }
+
+ // Copy the file data
+ FileInputStream in = new FileInputStream(srcFile);
+ byte[] buf = new byte[(int) srcFile.length()];
+ int nRead = in.read(buf);
+ if (nRead != srcFile.length())
+ throw new IOException(String.format("Error reading file '%s'", srcFile.toString()));
+ ent.setFileData(buf);
+
+ // And save the new entry.
+ fd.save();
+ }
+ }
+}
diff --git a/Platform/Apple/virtual/build.xml b/Platform/Apple/virtual/build.xml
new file mode 100644
index 00000000..f405d74c
--- /dev/null
+++ b/Platform/Apple/virtual/build.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Building raycast.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Platform/Apple/virtual/data/disks/base.2mg.bz2 b/Platform/Apple/virtual/data/disks/base.2mg.bz2
new file mode 100644
index 00000000..dbbcab77
Binary files /dev/null and b/Platform/Apple/virtual/data/disks/base.2mg.bz2 differ
diff --git a/Platform/Apple/virtual/data/tables/precast.bin b/Platform/Apple/virtual/data/tables/precast.bin
new file mode 100644
index 00000000..616c6d7c
Binary files /dev/null and b/Platform/Apple/virtual/data/tables/precast.bin differ
diff --git a/Platform/Apple/virtual/src/include/sample.build.props b/Platform/Apple/virtual/src/include/sample.build.props
new file mode 100644
index 00000000..57c4fd1b
--- /dev/null
+++ b/Platform/Apple/virtual/src/include/sample.build.props
@@ -0,0 +1,8 @@
+# Put your local development paths here
+# Take note of the path delimiters (ie: the / )
+
+# Required entries
+# CC65_BIN_DIR
+
+# Examples
+CC65_BIN_DIR=/opt/local/bin
diff --git a/Platform/Apple/virtual/src/raycast/build.xml b/Platform/Apple/virtual/src/raycast/build.xml
new file mode 100644
index 00000000..b8157e1c
--- /dev/null
+++ b/Platform/Apple/virtual/src/raycast/build.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Platform/Apple/virtual/src/raycast/main.s b/Platform/Apple/virtual/src/raycast/main.s
deleted file mode 100644
index e69de29b..00000000
diff --git a/Platform/Apple/virtual/src/raycast/render.s b/Platform/Apple/virtual/src/raycast/render.s
new file mode 100644
index 00000000..be289a7e
--- /dev/null
+++ b/Platform/Apple/virtual/src/raycast/render.s
@@ -0,0 +1,1149 @@
+
+CODEBEG = *
+
+ .pc02 ; Enable 65c02 ops
+
+; This code is written bottom-up. That is,
+; simple routines first, then routines that
+; call those to build complexity. The main
+; code is at the very end. We jump to it now.
+ JMP TEST
+
+; Conditional assembly flags
+DBLBUF = 0 ; whether to double-buffer
+DEBUG = 0 ; turn on verbose logging
+
+; Constants
+TOPLINE = $2000
+NLINES = 126
+SKYCOL = $11 ; blue
+GROUNDCOL = $2 ; orange / black
+
+; My zero page
+LINECT = $3 ; len 1
+BUMP = $4 ; len 1
+TXCOLNUM = $5 ; len 1
+PLINE = $6 ; len 2
+PDST = $8 ; len 2
+PTEX = $A ; len 2
+PBUMP = $C ; len 2
+PIXNUM = $E ; len 1
+BYTENUM = $F ; len 1
+PTMP = $10 ; len 2
+BACKBUF = $12 ; len 1 (value 0 or 1)
+FRONTBUF = $13 ; len 1 (value 0 or 1)
+PCAST = $14 ; len 2
+
+; Monitor zero page
+STARTADDR = $3C
+ENDADDR = $3E
+DESTADDR = $42
+
+; Place to stick ProDOS names temporarily
+NAMEBUF = $280
+
+; Tables and buffers
+SH0101 = $1000
+SH0123 = $1100
+SH0145 = $1200
+SH0156 = $1300
+SH0157 = $1400
+SH4501 = $1500
+SH4523 = $1600
+SH4545 = $1700
+SH4556 = $1800
+SH4557 = $1900
+BLITIDXL = $1A00 ; size $80
+BLITIDXH = $1A80 ; size $80
+DCMIDXL = $1B00 ; size $40 (one entry per two lines)
+DCMIDXH = $1B40 ; size $40
+X1B80 = $1B80 ; unused
+DCMROLL = $1C00 ; size 11*(126/2) = 693 = $2B5, plus 1 for rts
+CBLITROLL = $1F00 ; size 3*(126/2) = 189 = $BD, plus 2 for tya & rts
+
+PRODOSBUF = $1000 ; temporary, before tbls built
+SCREEN = $2000
+
+TEXTURES = $4000 ; size $5550 (5460 bytes x 4 textures)
+TEXSIZE = 5460
+TEX0 = TEXTURES
+TEX1 = TEX0+TEXSIZE
+TEX2 = TEX1+TEXSIZE
+TEX3 = TEX2+TEXSIZE
+UN9550 = $9550 ; unused
+BLITROLL = $A000 ; size 29*126 = 3654 = $E80, plus 1 for rts
+BUMPS = $AF00 ; len 64*64 = $1000
+GLOBALPG = $BF00 ; ProDOS global page
+MLI = GLOBALPG ; also the call point for ProDOS MLI
+MEMMAP = $BF58
+
+; I/O locations
+KBD = $C000
+CLRAUXRD = $C002
+SETAUXRD = $C003
+CLRAUXWR = $C004
+SETAUXWR = $C005
+CLRAUXZP = $C008
+SETAUXZP = $C009
+KBDSTRB = $C010
+CLRTEXT = $C050
+SETTEXT = $C051
+CLRMIXED = $C052
+SETMIXED = $C053
+PAGE1 = $C054
+PAGE2 = $C055
+CLRHIRES = $C056
+SETHIRES = $C057
+
+; ROM routines
+AUXMOVE = $C311
+PRNTAX = $F941
+RDKEY = $FD0C
+CROUT = $FD8E
+PRBYTE = $FDDA
+COUT = $FDED
+PRERR = $FF2D
+MONITOR = $FF69
+
+; Pixel offsets for even and odd blit lines
+BLITOFFE: .byte 5,8,11,1,17,20,24
+BLITOFFO: .byte 34,37,40,30,46,49,53
+; texture addresses
+TEXADRL: .byte TEX0,>TEX1,>TEX2,>TEX3
+ ; mip level offsets
+MIPOFFL: .byte <0,<4096,<5120,<5376,<5440,<5456,<5460
+MIPOFFH: .byte >0,>4096,>5120,>5376,>5440,>5456,>5460
+
+NEXTLINE:
+ LDA PLINE+1 ; Hi byte of line
+ CLC
+ ADC #4 ; Next line is 1K up
+ TAX
+ EOR PLINE+1
+ AND #$20 ; Past end of screen?
+ BEQ @DONE ; If not, we're done
+ TXA
+ SEC
+ SBC #$20 ; Back to start
+ TAX
+ LDA PLINE ; Lo byte
+ CLC
+ ADC #$80 ; Inner blks offset by 128 bytes
+ STA PLINE
+ BCC @DONE
+ INX ; Next page
+ TXA
+ AND #7
+ CMP #4 ; Still inside inner blk?
+ BNE @DONE ; If so we're done
+ TXA
+ SEC
+ SBC #4 ; Back to start of inner blk
+ TAX
+ LDA PLINE
+ CLC
+ ADC #$28 ; Outer blks offset by 40 bytes
+ STA PLINE
+@DONE:
+ STX PLINE+1
+ RTS
+
+; Template for blitting code
+
+BLITTPL: ; comments show byte offset
+; even rows
+ LDA SH0157 ; 0: pixel 3
+ ASL ; 3: save half of pix 3 in carry
+ ORA SH0101 ; 4: pixel 0
+ ORA SH0123 ; 7: pixel 1
+ ORA SH0145 ; 10: pixel 2
+ STA (0),Y ; 13: even column
+ INY ; 15: prep for odd
+ LDA SH0101 ; 16: pixel 4
+ ORA SH0123 ; 19: pixel 5
+ ROL ; 22: recover half of pix 3
+ ORA SH0156 ; 23: pixel 6 - after rol to ensure right hi bit
+ STA (0),Y ; 26: odd column
+ DEY ; 28: prep for even
+; odd rows
+ LDA SH4557 ; 29: pixel 3
+ ASL ; 32: save half of pix 3 in carry
+ ORA SH4501 ; 33: pixel 0
+ ORA SH4523 ; 36: pixel 1
+ ORA SH4545 ; 39: pixel 2
+ STA (2),Y ; 42: even column
+ INY ; 44: prep for odd
+ LDA SH4501 ; 45: pixel 4
+ ORA SH4523 ; 48: pixel 5
+ ROL ; 51: recover half of pix 3
+ ORA SH4556 ; 52: pixel 6 - after rol to ensure right hi bit
+ STA (2),Y ; 55: odd column
+ DEY ; 57: prep for even
+ ; 58: total
+
+; Create the unrolled blit code
+MAKEBLIT:
+ LDA #0 ; Start with line zero
+ STA LINECT
+ LDA #TOPLINE
+ STA PLINE+1
+ LDA #BLITROLL
+ STA PDST+1
+@LINELUP:
+; Copy the template
+ LDY #57
+@COPY:
+ LDA BLITTPL,Y
+ STA (PDST),Y
+ DEY
+ BPL @COPY
+ ; Record the address for the even line
+ JSR @STIDX
+; Set the even line pointers
+ LDY #14
+ JSR @STLINE
+ LDY #27
+ JSR @STLINE
+ ; Get ready for odd line
+ JSR @ADVANCE
+ ; Record the address for the odd line
+ JSR @STIDX
+; Set the odd line pointers
+ LDY #14
+ JSR @STLINE
+ LDY #27
+ JSR @STLINE
+ ; Prepare for next iteration
+ JSR @ADVANCE
+; Loop until all lines are done
+ LDA LINECT
+ CMP #NLINES
+ BNE @LINELUP
+ JSR @STIDX ; Last addr to index
+ JMP STRTS ; Finish with RTS for cleanliness
+@STLINE: ; Subroutine to store PLINE to PDST
+ LDA LINECT
+ ASL
+ STA (PDST),Y
+ RTS
+@STIDX: ; Subroutine to store tbl ptr to index
+ LDY LINECT
+ LDA PDST
+ STA BLITIDXL,Y
+ LDA PDST+1
+ STA BLITIDXH,Y
+ RTS
+@ADVANCE: ; Subroutine to go to next unroll
+ LDA #29
+ JSR ADVPDST
+ INC LINECT
+ JMP NEXTLINE
+
+; Create code to clear the blit
+MAKECBLIT:
+ LDX #0
+ LDY #0
+@LUP:
+ LDA @ST
+ STA CBLITROLL,X
+ INX
+ LDA BLITIDXL,Y
+ STA CBLITROLL,X
+ INX
+ LDA BLITIDXH,Y
+@ST:
+ STA CBLITROLL,X
+ INX
+ INY
+ INY
+ CPY #64
+ BNE @NOSWITCH
+ LDA @TYA ; switch from sky color to ground color
+ STA CBLITROLL,X
+ INX
+@NOSWITCH:
+ CPY #NLINES
+ BNE @LUP
+ LDA @RTS
+ STA CBLITROLL,X
+@RTS:
+ RTS
+@TYA:
+ TYA
+
+; Clear the blit
+CLRBLIT:
+ LDY #GROUNDCOL
+CLRBLIT2:
+ LDX BLITOFFE+0
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+1
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+2
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+3
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+4
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+5
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFE+6
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+0
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+1
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+2
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+3
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+4
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+5
+ LDA #SKYCOL
+ JSR CBLITROLL
+ LDX BLITOFFO+6
+ LDA #SKYCOL
+ JMP CBLITROLL
+
+; Construct the shift tables
+MAKESHIFT:
+ LDX #0
+@SH01:
+ TXA
+ AND #3
+@SH0101:
+ STA SH0101,X
+@SH0123:
+ ASL
+ ASL
+ STA SH0123,X
+@SH0145:
+ ASL
+ ASL
+ ORA #$80
+ STA SH0145,X
+@SH0156:
+ ASL
+ ORA #$80
+ STA SH0156,X
+@SH0157:
+ ASL
+ ASL
+ PHP
+ LSR
+ PLP
+ ROR
+ STA SH0157,X
+@SH45:
+ TXA
+ LSR
+ LSR
+ LSR
+ LSR
+ AND #3
+@SH4501:
+ ORA #$80
+ STA SH4501,X
+@SH4523:
+ ASL
+ ASL
+ ORA #$80
+ STA SH4523,X
+@SH4545:
+ ASL
+ ASL
+ ORA #$80
+ STA SH4545,X
+@SH4556:
+ ASL
+ ORA #$80
+ STA SH4556,X
+@SH4557:
+ ASL
+ ASL
+ PHP
+ LSR
+ PLP
+ ROR
+ STA SH4557,X
+@NEXT:
+ INX
+ BNE @SH01
+ RTS
+
+; Template for decimation. Offsets in comments
+DCMTPL:
+ LDA (PTEX),Y ; 0
+ STA BLITROLL,X ; 2
+ STA BLITROLL+29,X ; 5
+ LDA (PBUMP),Y ; 8
+ TAY ; 10
+ ; 11
+
+; Unroll the decimation code
+MAKEDCM:
+ LDX #0 ; Line counter
+ LDA #DCMROLL
+ STA PDST+1
+@ONESET:
+; Save address to the index
+ JSR @STIDX
+ LDY #11 ; Copy the template
+@COPYSET:
+ LDA DCMTPL,Y
+ STA (PDST),Y
+ DEY
+ BPL @COPYSET
+ LDY #3
+ JSR @STBLIT
+ LDY #6
+ JSR @STBLIT
+ LDA #11
+ JSR ADVPDST
+@MORE:
+ ; Loop until all lines done
+ CPX #NLINES
+ BCC @ONESET
+ JSR @STIDX ; Last addr to index
+ JMP STRTS ; Finish with an RTS for cleanliness
+@STBLIT: ; Store current blit addr
+ LDA BLITIDXL,X
+ STA (PDST),Y
+ INY
+ LDA BLITIDXH,X
+ STA (PDST),Y
+ INX ; Next line
+ RTS
+@STIDX:
+ TXA
+ LSR ; One entry per two lines
+ TAY
+ LDA PDST
+ STA DCMIDXL,Y
+ LDA PDST+1
+ STA DCMIDXH,Y
+ RTS
+
+STRTS:
+ LDA #$60 ; Store an RTS at PDST
+ LDY #0
+ STA (PDST),Y
+ RTS
+ADVPDST: ; Add A to PDST
+ CLC
+ ADC PDST
+ STA PDST
+ BCC @RTS
+ INC PDST+1
+@RTS:
+ RTS
+
+; Clear all the memory we're going to fill
+CLRMEM:
+ LDX #$10
+ LDA #$BE
+ JMP CLRSCR2
+
+; Clear the screens
+CLRSCR:
+ LDX #>SCREEN
+ .if DBLBUF
+ LDA #>SCREEN + $40 ; both hi-res screens
+ .else
+ LDA #>SCREEN + $20 ; one hi-res screen
+ .endif
+CLRSCR2:
+ STA @LIMIT+1
+ LDY #0
+ STY PDST
+ TYA
+@OUTER:
+ STX PDST+1
+@INNER:
+ STA (PDST),Y
+ INY
+ BNE @INNER
+ INX
+@LIMIT:
+ CPX #>SCREEN + $20
+ BNE @OUTER
+ RTS
+
+; Make a simple texture with alternating colors.
+; Input: Y = tex num
+; A, X: color numbers, 0-3
+SIMPLETEX:
+ STA @LD1+1
+ TXA
+ ASL
+ ASL
+ ASL
+ ASL
+ STA @LD2+1
+ LDA TEXADRL,Y
+ STA PDST
+ LDA TEXADRH,Y
+ STA PDST+1
+ LDX #>TEXSIZE
+ LDY #0
+ STY @LIM+1
+@OUTER:
+@LD1:
+ LDA #0
+@LD2:
+ ORA #0
+@LUP:
+ STA (PDST),Y
+ INY
+@LIM:
+ CPY #0
+ BNE @LUP
+ INC PDST+1
+ DEX
+ BMI @DONE
+ BNE @OUTER
+ LDA #BUMPS
+ STA PDST+1
+ LDA #0
+ STA @RATIOL
+ LDA #1
+ STA @RATIOH
+; Goal is to make ratio = 63 divided by targetSize.
+; The calculation is cool & weird, but I verified
+; in Python that the logic actually works. You
+; start with hi=1, lo=0. To calculate the next
+; step, add hi to low and take the sum mod the next
+; target size. To use the ratio, increment by hi
+; and lo. Whenever the low byte goes beyond the
+; target size, add an extra to hi.
+@ONEPASS:
+ LDA LINECT ; Init A with the lo byte = target size
+ LSR ; ...div 2
+ LDX #0 ; Hi byte always starts at zero
+ LDY #0 ; Location to store at
+@BUMPLUP:
+ CLC ; increment lo byte by ratio
+ ADC @RATIOL
+ CMP LINECT ; if we wrap around, need extra hi-byte bump
+ BCC @NOBM
+ SEC
+ SBC LINECT
+ INX
+@NOBM:
+ PHA ; save lo byte
+ TXA ; now work on hi byte
+ CLC
+ ADC @RATIOH
+ TAX
+ STA (PDST),Y ; store to the table
+ TAY ; next loc to store
+ CPX #63 ; check for end of column
+ PLA ; get lo byte back
+ BCC @BUMPLUP ; loop until whole column is done
+ LDA #64
+ JSR ADVPDST ; advance dst to next column
+@NEXT:
+ DEC LINECT ; all columns complete?
+ BEQ @DONE
+ LDA @RATIOL ; next ratio calculation (see explanation above)
+ CLC
+ ADC @RATIOH
+@MODLUP:
+ CMP LINECT
+ BCC @NOMOD
+ INC @RATIOH
+ SEC
+ SBC LINECT
+ BNE @MODLUP ; this must indeed be a loop
+@NOMOD:
+ STA @RATIOL
+ JMP @ONEPASS ; next column
+@DONE:
+ RTS
+@RATIOL: .byte 0
+@RATIOH: .byte 0
+
+; Decimate a column of the texture
+; Input: Y - texture number
+; TXCOLNUM - src column num in the texture
+; PIXNUM - dst pixel num in the blit roll
+; LINECT - height to render, in dbl lines
+; The output will be vertically centered.
+DCMCOL:
+ ; if height is zero, render nothing
+ LDA LINECT
+ BNE @NOTZERO
+ RTS
+@NOTZERO:
+ ; determine mip level in X reg
+ LDX #0
+ LDA LINECT
+ STA @ADJHT
+ LDA TXCOLNUM
+ STA @ADJCOL
+ LDA #32
+@MIPLUP:
+ CMP LINECT
+ BCC @GOTMIP
+ INX
+ ASL @ADJHT
+ LSR @ADJCOL
+ LSR
+ CMP #2
+ BCS @MIPLUP
+@GOTMIP:
+ .if DEBUG
+ LDA #"t"
+ JSR COUT
+ TYA
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+
+ LDA #"h"
+ JSR COUT
+ LDA LINECT
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+
+ LDA #"m"
+ JSR COUT
+ TXA
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+ .endif
+
+ ; calc addr of tex
+ LDA TEXADRL,Y
+ CLC
+ ADC MIPOFFL,X
+ STA PDST
+ LDA TEXADRH,Y
+ ADC MIPOFFH,X
+ STA PDST+1
+
+ .if DEBUG
+ LDA #"a"
+ JSR COUT
+ LDA PDST+1
+ JSR PRBYTE
+ LDA PDST
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+ .endif
+
+@CALCOFF: ; calc offset within tex
+ LDA #0
+ STA PTEX+1
+ LDA @ADJCOL
+@SHIFT:
+ ASL
+ ROL PTEX+1
+ INX ; Note: destroys mip level
+ CPX #6
+ BNE @SHIFT
+
+ .if DEBUG
+ PHA
+ LDA #"x"
+ JSR COUT
+ LDA @ADJCOL
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+
+ LDA #"o"
+ JSR COUT
+ LDA PTEX+1
+ JSR PRBYTE
+ PLA
+ PHA
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+ PLA
+ .endif
+
+ CLC
+ ADC PDST
+ STA PTEX
+ LDA PTEX+1
+ ADC PDST+1
+ STA PTEX+1
+; calculate bump table ptr
+ LDX @ADJHT
+ JSR CALCBUMP
+ ; figure first line in decim unroll
+ LDA #63
+ SEC
+ SBC LINECT ; height 63 is first in decim tbl
+ LSR
+ TAX
+ LDA DCMIDXL,X
+ STA @CALL+1
+ LDA DCMIDXH,X
+ STA @CALL+2
+ ; figure last line of decim unroll
+ TXA
+ CLC
+ ADC LINECT
+ TAX
+ LDA DCMIDXL,X
+ STA PTMP
+ LDA DCMIDXH,X
+ STA PTMP+1
+; determine blit offset for writing
+ LDY PIXNUM
+ LDX BLITOFFE,Y
+ ; store RTS so decim returns @ right moment
+ LDY #0
+ LDA (PTMP),Y ; save existing byte
+ PHA
+ LDA @RTS
+ STA (PTMP),Y
+
+ .if DEBUG
+ PHX
+ PHY
+ JSR RDKEY
+ PHA
+ JSR CROUT
+ PLA
+ PLY
+ PLX
+ CMP #$9B
+ BNE @NOTESC
+ BRK
+@NOTESC:
+ NOP
+ .endif
+
+@CALL:
+ JSR DCMROLL
+; fix RTS to what it was before
+ LDY #0
+ PLA
+ STA (PTMP),Y
+
+ .if DEBUG
+ LDY BYTENUM ; to see results early
+ STA SETAUXZP
+ JSR BLITROLL
+ STA CLRAUXZP
+ .endif
+
+@RTS:
+ RTS
+@ADJHT: .byte 0
+@ADJCOL: .byte 0
+
+; Calc pointer into the bump table
+; Input: X - height to render in dbl lines
+CALCBUMP:
+ STX @SUB+1
+ LDA #0
+ STA PBUMP+1
+ LDA #63 ; bump 63 is actually first
+ SEC
+@SUB:
+ SBC #0
+ BPL @NOTNEG
+ LDA #0
+@NOTNEG:
+
+ .if DEBUG
+ PHA
+ LDA #"b"
+ JSR COUT
+ PLA
+ PHA
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+ PLA
+ .endif
+
+ LDX #6
+@LUP:
+ ASL
+ ROL PBUMP+1
+ DEX
+ BNE @LUP
+ CLC
+ ADC #BUMPS
+ STA PBUMP+1
+
+ .if DEBUG
+ LDA #"p"
+ JSR COUT
+ LDA PBUMP+1
+ JSR PRBYTE
+ LDA PBUMP
+ JSR PRBYTE
+ LDA #" "
+ JSR COUT
+ .endif
+
+ RTS
+
+; Build table of screen line pointers
+; on aux zero-page
+MAKELINES:
+ LDA #0
+ STA LINECT
+ LDA #TOPLINE
+ STA PLINE+1
+@LUP:
+ LDA LINECT
+ ASL
+ TAX
+ LDA PLINE
+ LDY PLINE+1
+ STA SETAUXZP
+ STA 0,X
+ STY 1,X
+ STA CLRAUXZP
+ JSR NEXTLINE
+ INC LINECT
+ LDA LINECT
+ CMP #NLINES
+ BNE @LUP
+ RTS
+
+; Set screen lines to current back buf
+SETBKBUF:
+; calculate screen start
+ LDA BACKBUF
+ ASL
+ ASL
+ ASL
+ ASL
+ ASL
+ CLC
+ ADC #$20
+ STA SETAUXZP
+ STA $FF
+ LDX #0
+@LUP:
+ LDA 1,X
+ AND #$1F
+ ORA $FF
+ STA 1,X
+ INX
+ INX
+ BNE @LUP
+ STA CLRAUXZP
+ RTS
+
+; Load file, len-prefixed name in A/X (hi/lo), to addr on stack
+; (push hi byte first, then push lo byte)
+BLOAD:
+ STX @MLICMD+1 ; filename lo
+ STA @MLICMD+2 ; filename hi
+ LDA #PRODOSBUF
+ STA @MLICMD+4
+ LDA #$C8 ; open
+ LDX #3
+ JSR @DOMLI
+ LDA @MLICMD+5 ; get handle and put it in place
+ STA @MLICMD+1
+ PLY ; save ret addr
+ PLX
+ PLA
+ STA @MLICMD+2 ; load addr lo
+ PLA
+ STA @MLICMD+3 ; load addr hi
+ PHX ; restore ret addr
+ PHY
+ LDA #$CA ; read
+ STA @MLICMD+5 ; also length (more than enough)
+ LDX #4
+ JSR @DOMLI
+@CLOSE:
+ STZ @MLICMD+1 ; close all
+ LDA #$CC
+ LDX #1
+ ; fall through
+@DOMLI:
+ STA @MLIOP
+ STX @MLICMD
+ JSR MLI
+@MLIOP: .byte 0
+ .addr @MLICMD
+ BCS @ERR
+ RTS
+@ERR:
+ JSR PRBYTE
+ JSR PRERR
+ LDX #$FF
+ TXS
+ JMP MONITOR
+@MLICMD: .res 10 ; 10 bytes should be plenty
+
+; Copy X pages starting at pg Y to aux mem
+CPTOAUX:
+ STA SETAUXWR
+ STY PDST+1
+ LDY #0
+ STY PDST
+@LUP:
+ LDA (PDST),Y
+ STA (PDST),Y
+ INY
+ BNE @LUP
+ INC PDST+1
+ DEX
+ BNE @LUP
+ STA CLRAUXWR
+ RTS
+
+; Fetch the next byte from the pre-raycasted data
+; Note this routine needs to be copied to aux mem.
+GETCAST:
+ LDY #0
+ STA SETAUXRD
+ LDA (PCAST),Y
+ STA CLRAUXRD
+ INC PCAST
+ BNE @DONE
+ INC PCAST+1
+@DONE:
+ RTS
+
+; Test code to see if things really work
+TEST:
+ ; clear ProDOS mem map so it lets us load
+ LDX #$18
+ LDA #1
+@MEMLUP:
+ STA MEMMAP-1,X
+ LDA #0
+ DEX
+ BNE @MEMLUP
+; load the pre-raycast data
+ LDA #$20 ; addr hi
+ PHA
+ LDA #0 ; addr lo
+ PHA
+ LDX #<@PRECASTNM
+ LDA #>@PRECASTNM
+ JSR BLOAD
+; copy it to aux mem
+ LDY #$20
+ LDX #$60
+ JSR CPTOAUX
+ LDA #0 ; set ptr to it
+ STA PCAST
+ LDA #$20
+ STA PCAST+1
+; copy our code to aux mem
+ LDY #>CODEBEG
+ LDX #>CODEEND - >CODEBEG + 1
+ JSR CPTOAUX
+; set up everything else
+ JSR CLRMEM
+ ; load the textures
+ LDA #>TEX0
+ PHA
+ LDA #@TEX0NAME
+ JSR BLOAD
+
+ LDA #>TEX1
+ PHA
+ LDA #@TEX1NAME
+ JSR BLOAD
+
+ LDA #>TEX2
+ PHA
+ LDA #@TEX2NAME
+ JSR BLOAD
+
+ LDA #>TEX3
+ PHA
+ LDA #@TEX3NAME
+ JSR BLOAD
+; build all the unrolls and tables
+ JSR MAKEBLIT
+ JSR MAKECBLIT
+ JSR MAKESHIFT
+ JSR MAKEDCM
+ JSR MAKEBUMPS
+ JSR MAKELINES
+ JSR CLRSCR
+; set up front and back buffers
+ LDA #0
+ STA FRONTBUF
+ .if DBLBUF
+ LDA #1
+ .endif
+ STA BACKBUF
+
+ BIT CLRTEXT
+ BIT SETHIRES
+
+ LDA #63
+ STA LINECT
+ LDA #1
+ STA @DIR
+ JSR CLRBLIT
+@ONELVL:
+ LDA #0
+ STA PIXNUM
+ STA BYTENUM
+ .if DBLBUF
+ JSR SETBKBUF
+ .endif
+
+ .if DEBUG
+ LDA PCAST+1
+ JSR PRBYTE
+ LDA PCAST
+ JSR PRBYTE
+ JSR CROUT
+ .endif
+
+@ONECOL:
+ JSR GETCAST ; first byte is height
+ CMP #$FF
+ BNE @NORESET
+; found end of cast data, start over
+ LDA #0
+ STA PCAST
+ LDA #$20
+ STA PCAST+1
+ JSR GETCAST
+@NORESET:
+ CMP #63
+ BCC @HTOK
+ LDA #62
+@HTOK:
+ STA LINECT
+ JSR GETCAST ; second byte is tex num and tex col
+ PHA
+ AND #$3F
+ CMP #63
+ BCC @COLOK
+ LDA #62
+@COLOK:
+ STA TXCOLNUM
+ PLA
+ LSR ; div by 64
+ LSR
+ LSR
+ LSR
+ LSR
+ LSR
+ TAY ; Y now holds tex num
+ JSR DCMCOL
+ INC PIXNUM
+ LDA PIXNUM
+ CMP #7
+ BNE @ONECOL
+@FLUSH:
+ LDY BYTENUM
+ STA SETAUXZP
+ JSR BLITROLL
+ STA CLRAUXZP
+ JSR CLRBLIT
+ LDA #0
+ STA PIXNUM
+ INC BYTENUM
+ INC BYTENUM
+ LDA BYTENUM
+ CMP #18
+ BNE @ONECOL
+@NEXTLVL:
+; flip onto the screen
+ .if DBLBUF
+ LDX BACKBUF
+ LDA FRONTBUF
+ STA BACKBUF
+ STX FRONTBUF
+ LDA PAGE1,X
+ .endif
+ ; adv past FE in cast data
+ JSR GETCAST
+ CMP #$FE
+ BNE @ERR
+ JSR GETCAST
+ CMP #$FE
+ BEQ @INCDEC
+@ERR:
+ BRK
+@INCDEC:
+ LDA KBD ; stop if ESC is pressed
+ CMP #$9B
+ BEQ @DONE
+ JMP @ONELVL
+@DONE:
+ STA KBDSTRB ; eat the keypress
+ BIT SETTEXT
+ BIT PAGE1
+ RTS
+@DIR: .byte 1
+@TEX0NAME: .byte 21
+ .byte "/LL/ASSETS/BUILDING01"
+@TEX1NAME: .byte 21
+ .byte "/LL/ASSETS/BUILDING02"
+@TEX2NAME: .byte 21
+ .byte "/LL/ASSETS/BUILDING03"
+@TEX3NAME: .byte 21
+ .byte "/LL/ASSETS/BUILDING04"
+@PRECASTNM: .byte 18
+ .byte "/LL/ASSETS/PRECAST"
+
+CODEEND = *
+