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 = * +