From 5e72a68c421e588d75e8a41b8b9dfc5cdfa4aae3 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 29 Oct 2023 22:43:32 -0500 Subject: [PATCH] Adding a "wrapper" capability to help with DOS rewriting application. #24 --- api/README-TOKENIZER.md | 23 +++++--- .../bastools/api/visitors/ByteVisitor.java | 1 + tools/bt/README.md | 54 +++++++++++-------- .../bastools/tools/bt/Main.java | 30 ++++++++--- 4 files changed, 71 insertions(+), 37 deletions(-) diff --git a/api/README-TOKENIZER.md b/api/README-TOKENIZER.md index f478929..b1cb40c 100644 --- a/api/README-TOKENIZER.md +++ b/api/README-TOKENIZER.md @@ -8,14 +8,6 @@ Generally, the usage pattern is: ## Code snippets -```java -Configuration config = Configuration.builder() - .sourceFile(this.sourceFile) - .build(); -``` - -The `Configuration` class also allows the BASIC start address to be set (defaults to `0x801`), set the maximum line length (this is in bytes, and defaults to `255`, but feel free to experiment). Some of the classes report output via the debug stream, which defaults to a simple null stream (no output) - replace with `System.out` or another `PrintStream`. - ```java Queue tokens = TokenReader.tokenize(config.sourceFile); ``` @@ -29,6 +21,21 @@ Program program = parser.parse(); The `Program` is now the parsed version of the BASIC program. Various `Visitor`s may be used to report, gather information, or manipulate the tree in various ways. +```java +Configuration config = Configuration.builder() + .sourceFile(this.sourceFile) + .build(); +``` + +The `Configuration` class also allows the BASIC start address to be set (defaults to `0x801`), set the maximum line length (this is in bytes, and defaults to `255`, but feel free to experiment). Some of the classes report output via the debug stream, which defaults to a simple null stream (no output) - replace with `System.out` or another `PrintStream`. + +```java +ByteVisitor byteVisitor = Visitors.byteVisitor(config); +byte[] programData = byteVisitor.dump(program); +``` + +Finally, the ByteVisitor will transform the program into the tokenized form. + ## Directives The framework allows embedding of directives. diff --git a/api/src/main/java/io/github/applecommander/bastools/api/visitors/ByteVisitor.java b/api/src/main/java/io/github/applecommander/bastools/api/visitors/ByteVisitor.java index 6cfb27d..ea819d3 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/visitors/ByteVisitor.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/visitors/ByteVisitor.java @@ -63,6 +63,7 @@ public class ByteVisitor implements Visitor { ByteArrayOutputStream os = stack.peek(); os.write(0x00); os.write(0x00); + this.address += 2; return program; } diff --git a/tools/bt/README.md b/tools/bt/README.md index 31eacb8..2213e3e 100644 --- a/tools/bt/README.md +++ b/tools/bt/README.md @@ -1,47 +1,47 @@ ## Usage ```shell -$ bt -Missing required parameter: +$ bt --help Usage: bt [-chOVx] [--addresses] [--applesingle] [--debug] [--list] [--pretty] - [--stdout] [--tokens] [--variables] - [--max-line-length=] [-a=
] [-o=] + [--stdout] [--tokens] [--variables] [--wrapper] [-a=
] + [--max-line-length=] [-o=] [-f=[,...]]... Transforms an AppleSoft program from text back to its tokenized state. AppleSoft BASIC program to process. Options: - --addresses Dump line number addresses out. - --applesingle Write output in AppleSingle format - --debug Print debug output. - --list List structure as bastools understands it. - --max-line-length= - Maximum line length for generated lines. - Default: 255 - --pretty Pretty print structure as bastools understands it. - --stdout Send binary output to stdout. - --tokens Dump token list to stdout for debugging. - --variables Generate a variable report -a, --address=
Base address for program Default: 2049 - -c, --copy Generate a copy/paste form of output for testing in an - emulator. - -f= [,...] + --addresses Dump line number addresses out. + --applesingle Write output in AppleSingle format + -c, --copy Generate a copy/paste form of output for testing in + an emulator. + --debug Print debug output. + -f=[,...] Enable specific optimizations. * remove-empty-statements - Strip out all '::'-like statements. * remove-rem-statements - Remove all REM statements. - * shorten-variable-names - Ensure all variables are 1 or - 2 characters long. - * extract-constant-values - Assign all constant values - first. + * shorten-variable-names - Ensure all variables are + 1 or 2 characters long. + * extract-constant-values - Assign all constant + values first. * merge-lines - Merge lines. * renumber - Renumber program. -h, --help Show this help message and exit. + --list List structure as bastools understands it. + --max-line-length= + Maximum line length for generated lines. + Default: 255 -o, --output= Write binary output to file. -O, --optimize Apply all optimizations. + --pretty Pretty print structure as bastools understands it. + --stdout Send binary output to stdout. + --tokens Dump token list to stdout for debugging. -V, --version Print version information and exit. + --variables Generate a variable report + --wrapper Wrap the Applesoft program (DOS 3.3). -x, --hex Generate a binary hex dump for debugging. ``` @@ -153,3 +153,13 @@ demo.dsk /DEMO/ ProDOS format; 139,264 bytes free; 4,096 bytes used. ``` + +## Wrapping the application + +DOS 3.3 (but not ProDOS) seems to rewrite the application linked list when an Applesoft program is loaded; this rewrites the pointers and impacts any embedded (via `$embed`) machine code. With the wrapper, the application is "wrapped" with a startup Applesoft program that prevents the rewrite. The wrapper is just a simple program: + +```basic +10 POKE 103,24:POKE 104,8:RUN +``` + +This is a valid program that resets to Applesoft pointer to just after the current program and runs that other program. diff --git a/tools/bt/src/main/java/io/github/applecommander/bastools/tools/bt/Main.java b/tools/bt/src/main/java/io/github/applecommander/bastools/tools/bt/Main.java index 49828b4..4120bca 100644 --- a/tools/bt/src/main/java/io/github/applecommander/bastools/tools/bt/Main.java +++ b/tools/bt/src/main/java/io/github/applecommander/bastools/tools/bt/Main.java @@ -1,10 +1,6 @@ package io.github.applecommander.bastools.tools.bt; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; +import java.io.*; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; @@ -74,6 +70,9 @@ public class Main implements Callable { @Option(names = "--max-line-length", description = "Maximum line length for generated lines.", showDefaultValue = Visibility.ALWAYS) private int maxLineLength = 255; + + @Option(names = "--wrapper", description = "Wrap the Applesoft program (DOS 3.3).") + private boolean wrapProgram; @Option(names = "-f", converter = OptimizationTypeConverter.class, split = ",", description = { "Enable specific optimizations.", @@ -174,17 +173,34 @@ public class Main implements Callable { } ByteVisitor byteVisitor = Visitors.byteVisitor(config); - byte[] data = byteVisitor.dump(program); + byte[] wrapperData = new byte[0]; + if (wrapProgram) { + Queue wrapperTokens = TokenReader.tokenize(new ByteArrayInputStream( + "10 POKE 103,24:POKE 104,8:RUN".getBytes())); + Parser wrapperParser = new Parser(wrapperTokens); + Program wrapperProgram = wrapperParser.parse(); + wrapperData = byteVisitor.dump(wrapperProgram); + } + + byte[] programData = byteVisitor.dump(program); if (showLineAddresses) { byteVisitor.getLineAddresses().forEach((l,a) -> System.out.printf("%5d ... $%04x\n", l, a)); } + + // Merge both programs together. Note that wrapperData may be a 0 byte array. + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(wrapperData); + output.write(programData); + output.flush(); + byte[] data = output.toByteArray(); + if (hexFormat) { HexDumper.standard().dump(address, data); } if (copyFormat) { HexDumper.apple2().dump(address, data); } - + saveResults(data); }