├── Makefile ├── bmp_bitmap.h ├── apple_gcr.h ├── bitmap.h ├── LICENSE ├── woz_image.h ├── buffered_reader.h ├── bitmap.c ├── README.md ├── buffered_reader.c ├── apple_gcr.c ├── woz_image.c ├── bmp_bitmap.c └── main.c /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | TARGET=picturedsk 3 | SOURCES=main.c apple_gcr.c bitmap.c bmp_bitmap.c buffered_reader.c woz_image.c 4 | CFLAGS=-O3 5 | LFLAGS=-lm 6 | 7 | OBJS=$(SOURCES:.c=.o) 8 | 9 | # the target is obtained linking all .o files 10 | all: $(SOURCES) $(TARGET) 11 | 12 | $(TARGET): $(OBJS) 13 | $(CC) $(LFLAGS) $(OBJS) -o $(TARGET) 14 | 15 | purge: clean 16 | rm -f $(TARGET) 17 | 18 | clean: 19 | rm -f *.o -------------------------------------------------------------------------------- /bmp_bitmap.h: -------------------------------------------------------------------------------- 1 | // 2 | // bmp_bitmap.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | // This module provides basic functionality for reading Windows-style BMP bitmap 7 | // image files and producing a plain RGBA buffer of pixel values. Only a subset of 8 | // BMP formats are supported: 1, 4, 8, 24, 32 bits per pixel, uncompressed, in the 9 | // BMP v3 or v4 file format styles. This covers most standard generic BMP conversion 10 | // output. 11 | // 12 | 13 | #ifndef bmp_bitmap_h 14 | #define bmp_bitmap_h 15 | 16 | #include 17 | #include "bitmap.h" 18 | 19 | bitmap * load_bmp_into_bitmap(const char * bmp_path); 20 | 21 | #endif /* bmp_bitmap_h */ 22 | -------------------------------------------------------------------------------- /apple_gcr.h: -------------------------------------------------------------------------------- 1 | // 2 | // apple_gcr.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // Portions of this module are (c) 2018 Thomas Harte (offered under the same LICENSE). 6 | // 7 | // This module implements Apple Group Coded Recording (GCR) encoding for floppy disk data. 8 | // 9 | 10 | #ifndef apple_gcr_h 11 | #define apple_gcr_h 12 | 13 | #include 14 | #include 15 | 16 | #define GCR_RAW_TRACK_SIZE (16 * 256) // Input to encode is expected to be this size 17 | #define GCR_ENCODED_TRACK_SIZE (13 * 512) // Output buffer should be at least this large 18 | 19 | typedef enum _dsk_sector_format { 20 | dsk_sector_format_dos_3_3 = 0, 21 | dsk_sector_format_prodos = 1 22 | } dsk_sector_format; 23 | 24 | size_t gcr_encode_bits_for_track(uint8_t * dest, uint8_t * src, int track_number, dsk_sector_format sector_format); 25 | 26 | #endif /* apple_gcr_h */ 27 | -------------------------------------------------------------------------------- /bitmap.h: -------------------------------------------------------------------------------- 1 | // 2 | // bitmap.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | // This module provides a generic RGBA "bitmap" object, with the ability to sample 7 | // the image in the manner of a texture map. 8 | // 9 | 10 | #ifndef bitmap_h 11 | #define bitmap_h 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | // 18 | // Generic bitmap structure, in basic y rows of x pixels, stored as packed RBGA quads. 19 | // 20 | 21 | typedef struct _bitmap { 22 | int width; 23 | int height; 24 | uint8_t rgba_pixels[0]; 25 | } bitmap; 26 | 27 | #define BITMAP_BYTES_PER_LINE(b) ((b)->width * 4) 28 | #define BITMAP_PIXEL_BASE(b, x, y) (((y) * BITMAP_BYTES_PER_LINE(b)) + (x) * 4) 29 | 30 | bitmap * create_bitmap(int width, int height); 31 | double sample_bitmap_greyscale(bitmap * bitmap, float u, float v); 32 | void free_bitmap(bitmap * bitmap); 33 | 34 | #endif /* bitmap_h */ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ben Zotto 4 | 5 | This program incorporates routines which are copyright (c) 2018 Thomas Harte 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /woz_image.h: -------------------------------------------------------------------------------- 1 | // 2 | // woz_image.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | // This module supports (a subset of) WOZ 2.0 disk image format build and write. 7 | // Please see https://applesaucefdc.com/woz/reference2/ 8 | // 9 | 10 | #ifndef woz_image_h 11 | #define woz_image_h 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | typedef struct _woz_chunk { 19 | char name[4]; 20 | size_t mark; 21 | size_t buffer_size; 22 | uint8_t * data; 23 | } woz_chunk; 24 | 25 | typedef struct _woz_file { 26 | woz_chunk * info; 27 | woz_chunk * tmap; 28 | woz_chunk * trks; 29 | woz_chunk * writ; 30 | } woz_file; 31 | 32 | woz_file * create_empty_woz_file(void); 33 | void free_woz_file(woz_file * woz); 34 | int write_woz_to_file(woz_file * woz, const char * path); 35 | 36 | woz_chunk * create_woz_chunk(const char * name); 37 | void free_chunk(woz_chunk * chunk); 38 | size_t chunk_size_on_disk(woz_chunk * chunk); 39 | void chunk_write_uint8(woz_chunk * chunk, uint8_t u8); 40 | void chunk_write_uint16(woz_chunk * chunk, uint16_t u16); 41 | void chunk_write_uint32(woz_chunk * chunk, uint32_t u32); 42 | void chunk_write_utf8(woz_chunk * chunk, const char * utf8string, int n); 43 | void chunk_write_bytes(woz_chunk * chunk, const uint8_t * bytes, size_t n); 44 | void chunk_set_mark(woz_chunk * chunk, size_t mark); 45 | void chunk_advance_mark(woz_chunk * chunk, int offset); 46 | 47 | uint32_t woz_crc32(const void *buf, size_t size); 48 | 49 | #endif /* woz_image_h */ 50 | -------------------------------------------------------------------------------- /buffered_reader.h: -------------------------------------------------------------------------------- 1 | // 2 | // buffered_reader.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | // This module provides a general buffered reader that allows reading values with either 7 | // endianness. Useful for parsing packed formatted file data. 8 | // 9 | 10 | #ifndef buffered_reader_h 11 | #define buffered_reader_h 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // This buffer size can be changed, but don't make it pathologically small (ie < 8 bytes). 19 | // There are assumptions in the logic you will end up violating if you insist on doing so. 20 | #define BUFFER_SIZE 1024 21 | 22 | typedef enum _file_endianness { 23 | file_endianness_little, 24 | file_endianness_big 25 | } file_endianness; 26 | 27 | typedef struct _buffered_reader { 28 | FILE * file; 29 | file_endianness endianness; 30 | size_t total_size; 31 | size_t offset; 32 | size_t mark; 33 | size_t valid; 34 | uint8_t buffer[BUFFER_SIZE]; 35 | } buffered_reader; 36 | 37 | buffered_reader * open_buffered_reader(const char * path, file_endianness endianness); 38 | int buffered_reader_ensure_remaining(buffered_reader * reader, size_t ensure); 39 | void buffered_reader_advance_to_offset(buffered_reader * reader, size_t offset); 40 | void close_buffered_reader(buffered_reader * reader); 41 | 42 | uint8_t read_uint8(buffered_reader * reader); 43 | uint16_t read_uint16(buffered_reader * reader); 44 | uint32_t read_uint32(buffered_reader * reader); 45 | int8_t read_int8(buffered_reader * reader); 46 | int16_t read_int16(buffered_reader * reader); 47 | int32_t read_int32(buffered_reader * reader); 48 | void read_bytes(buffered_reader * reader, uint8_t * dest, size_t count); 49 | 50 | #endif /* buffered_reader_h */ 51 | -------------------------------------------------------------------------------- /bitmap.c: -------------------------------------------------------------------------------- 1 | // 2 | // bitmap.c 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | 7 | #include "bitmap.h" 8 | #include 9 | 10 | static double sRGB_to_linear(double x); 11 | static double linear_to_sRGB(double x); 12 | 13 | bitmap * create_bitmap(int width, int height) 14 | { 15 | bitmap * bitmap = calloc(sizeof(bitmap) + (width * height * 4), 1); 16 | if (bitmap) { 17 | bitmap->width = width; 18 | bitmap->height = height; 19 | } 20 | return bitmap; 21 | } 22 | 23 | double sample_bitmap_greyscale(bitmap * bitmap, float u, float v) 24 | { 25 | // u, v are texcoords in the [0, 1] range. tex coords at 1.0 don't overflow, they 26 | // get clamped to the final value on that axis, and are equivalent to 1-epsilon. 27 | if (u < 0.0) { u = 0.0; } 28 | if (u > 1.0) { u = 1.0; } 29 | if (v < 0.0) { v = 0.0; } 30 | if (v > 1.0) { v = 1.0; } 31 | int x = (int)(u * bitmap->width); 32 | if (x == bitmap->width) { x = bitmap->width - 1; } 33 | int y = (int)(v * bitmap->height); 34 | if (y == bitmap->height) { y = bitmap->height - 1; } 35 | 36 | // Alpha channel is just ignored in this operation. 37 | int base = BITMAP_PIXEL_BASE(bitmap, x, y); 38 | uint8_t r = bitmap->rgba_pixels[base]; 39 | uint8_t g = bitmap->rgba_pixels[base + 1]; 40 | uint8_t b = bitmap->rgba_pixels[base + 2]; 41 | 42 | double r_linear = sRGB_to_linear(r/255.0); 43 | double g_linear = sRGB_to_linear(g/255.0); 44 | double b_linear = sRGB_to_linear(b/255.0); 45 | double grey_linear = 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear; 46 | 47 | return round(linear_to_sRGB(grey_linear)); 48 | } 49 | 50 | void free_bitmap(bitmap * bitmap) 51 | { 52 | free(bitmap); 53 | } 54 | 55 | // 56 | // Private colorspace gamma conversion, see 57 | // https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 58 | // 59 | 60 | static 61 | double sRGB_to_linear(double x) { 62 | if (x < 0.04045) { 63 | return x / 12.92; 64 | } 65 | return pow((x + 0.055) / 1.055, 2.4); 66 | } 67 | 68 | static 69 | double linear_to_sRGB(double y) { 70 | if (y <= 0.0031308) { 71 | return 12.92 * y; 72 | } 73 | return 1.055 * pow(y, 1.0 / 2.4) - 0.055; 74 | } 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # picturedsk 2 | `picturedsk` generates a custom Apple II 5.25" disk image in the WOZ format that "imprints" an arbitrary input image into the magnetic flux of the disk. It also incorporates onto the disk a small bootable screen image and optional message. You can try this program and boot the output image using any WOZ-compatible emulator, but for most impressive results, you should have the [Applesauce](https://applesaucefdc.com) application and hardware to visualize and write a physical floppy. 3 | 4 | ![Some palm trees in life and on floppy](http://decaf.co/picturedsk_palm.png) 5 | 6 | ### Requires 7 | 8 | - An input image, in BMP (Windows Bitmap) format. Any dimensions are OK, but if it's not square, the result will get squished into a square. The input can be colored, but bear in mind that the output is only 1-bit, so something low-detail and high-contrast will look best. 9 | - The output WOZ file will boot on various contemporary emulators (like [Virtual II](http://www.virtualii.com)), but is really designed to be written onto physical media with a full Applesauce setup (controller and drive with sync sensor). The resulting disk will boot on a real machine and will also have an extremely cool flux image. 10 | - You need some basic C compiler setup to build the program (see below). 11 | 12 | ### How to build, run, and see the results 13 | 14 | 1. There are no dependencies besides the standard C libraries. The included `Makefile` will build the source files into a command line utility called `picturedsk`: 15 | 16 | `make` 17 | 18 | 2. Run the program by giving it an input image file (supports BMP format only) and an output file name. There is an optional message string, and if you supply one as the final argument, it will appear on the screen when the disk image boots: 19 | 20 | `./picturedsk my_image.bmp output.woz "HELLO FLOPPY"` 21 | 22 | 3. Take the resulting .WOZ file, and open it in the Applesauce application's _Disk Writer_ mode. Write it to a fresh 5.25" floppy (make sure "Force Track Synchronization" is checked). 23 | 24 | 4. Try booting the disk you just made. Then use Applesauce's _Flux Imager_ to image the disk and see what your image looks like as concentric flux circles. 25 | 26 | ### What is this actually useful for? 27 | 28 | This is really more art project than useful product. Maybe create custom holiday gifts for your retro-computing friends? 29 | 30 | ![Palms on the screen](http://decaf.co/picturedsk_screen.png) 31 | 32 | ### Thanks 33 | 34 | The Apple GCR encoding algorithm was cribbed from [dsk2woz](https://github.com/TomHarte/dsk2woz) by Tom Harte. Some inspiration comes from Antoine Vignau's [graffidisk](http://www.brutaldeluxe.fr/products/apple2/graffidisk/). Thanks, of course, to John K. Morris, the prime mover behind the Applesauce project and a perennially helpful fellow. 35 | 36 | ## How does it work? 37 | 38 | The WOZ image and Applesauce allows for the representation of arbitrary nibble streams on arbitrary quarter-tracks. This program produces a custom WOZ disk image filled mostly with a "texture mapped" sampling of the input image. The disk image also contains a single valid, bootable track (outer track 0) which displays a version of that same image on-screen if you boot it. 39 | 40 | Track 0 has a valid boot sector, a small image loading program, and an encoding of the input image in the Apple HGR format. This fills up almost the whole track. The boot sector loads the track starting at $B000, and then jumps there. That next bit of code copies the image data (which starts at $B100) to the interleaved HGR memory for display, prints a text message, and then infinite-loops. The bitmap displayed is smaller than full-screen, using the constrained dimensions to allow its data to fit entirely within track 0. 41 | 42 | The rest of the tracks are created by using a polar coordinate system to sample the same input image at every nibble location around the track. The sampling both here and for the HGR version as above are done using greyscale luma threshold to transform 24-bit RGB to 1-bit monochrome. These tracks are specified to be written at every third quarter-track (rather than every fourth) to get a higher "resolution" in the flux image; since they're not readable anyway there is no data safety concern. 43 | -------------------------------------------------------------------------------- /buffered_reader.c: -------------------------------------------------------------------------------- 1 | // 2 | // buffered_reader.c 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | 7 | #include "buffered_reader.h" 8 | 9 | // 10 | // Private declarations 11 | // 12 | 13 | static size_t ensure_minimum_bytes_available(buffered_reader * reader, size_t count); 14 | 15 | // 16 | // Public routines 17 | // 18 | 19 | buffered_reader * open_buffered_reader(const char * path, file_endianness endianness) 20 | { 21 | FILE * file = fopen(path, "rb"); 22 | if (!file) { 23 | return NULL; 24 | } 25 | 26 | buffered_reader * reader = malloc(sizeof(buffered_reader)); 27 | if (!reader) { 28 | fclose(file); 29 | return NULL; 30 | } 31 | 32 | fseek(file, 0L, SEEK_END); 33 | size_t size = ftell(file); 34 | fseek(file, 0L, SEEK_SET); 35 | 36 | reader->file = file; 37 | reader->endianness = endianness; 38 | reader->total_size = size; 39 | reader->offset = 0; 40 | reader->mark = 0; 41 | 42 | reader->valid = fread(&reader->buffer[0], 1, BUFFER_SIZE, file); 43 | 44 | return reader; 45 | } 46 | 47 | int buffered_reader_ensure_remaining(buffered_reader * reader, size_t ensure) 48 | { 49 | return (reader->total_size - (reader->offset + reader->mark)) >= ensure; 50 | } 51 | 52 | void buffered_reader_advance_to_offset(buffered_reader * reader, size_t offset) 53 | { 54 | if (offset > reader->total_size) { 55 | // Invalid offset 56 | return; 57 | } 58 | if (offset <= reader->offset + reader->mark) { 59 | // We don't rewind this reader, so if the proposed offset is earlier 60 | // than the mark, ignore this request. 61 | return; 62 | } 63 | // If the requested offset is within the valid buffer, just move the mark. 64 | if (offset < reader->offset + reader->valid) { 65 | reader->mark = offset - reader->offset; 66 | return; 67 | } 68 | // Otherwise dump all the currently buffered, bytes and move the mark and 69 | // offset appropriately. Seek the file handle to the new offset. 70 | fseek(reader->file, offset, SEEK_SET); 71 | reader->offset = offset; 72 | reader->mark = 0; 73 | reader->valid = 0; 74 | } 75 | 76 | void close_buffered_reader(buffered_reader * reader) 77 | { 78 | fclose(reader->file); 79 | free(reader); 80 | } 81 | 82 | uint8_t read_uint8(buffered_reader * reader) 83 | { 84 | if (!ensure_minimum_bytes_available(reader, sizeof(uint8_t))) { 85 | return 0; 86 | } 87 | uint8_t u8 = reader->buffer[reader->mark++]; 88 | return u8; 89 | } 90 | 91 | uint16_t read_uint16(buffered_reader * reader) 92 | { 93 | if (!ensure_minimum_bytes_available(reader, sizeof(uint16_t))) { 94 | return 0; 95 | } 96 | 97 | uint8_t one = reader->buffer[reader->mark++]; 98 | uint8_t two = reader->buffer[reader->mark++]; 99 | uint16_t u16; 100 | if (reader->endianness == file_endianness_little) { 101 | u16 = (two << 8) | one; 102 | } else { 103 | u16 = (one << 8) | two; 104 | } 105 | return u16; 106 | } 107 | 108 | uint32_t read_uint32(buffered_reader * reader) 109 | { 110 | if (!ensure_minimum_bytes_available(reader, sizeof(uint32_t))) { 111 | return 0; 112 | } 113 | uint8_t one = reader->buffer[reader->mark++]; 114 | uint8_t two = reader->buffer[reader->mark++]; 115 | uint8_t three = reader->buffer[reader->mark++]; 116 | uint8_t four = reader->buffer[reader->mark++]; 117 | uint32_t u32; 118 | if (reader->endianness == file_endianness_little) { 119 | u32 = (four << 24) | (three << 16) | (two << 8) | one; 120 | } else { 121 | u32 = (one << 24) | (two << 16) | (three << 8) | four; 122 | } 123 | return u32; 124 | } 125 | 126 | int8_t read_int8(buffered_reader * reader) 127 | { 128 | return read_uint8(reader); 129 | } 130 | 131 | int16_t read_int16(buffered_reader * reader) 132 | { 133 | return (int16_t)read_uint16(reader); 134 | } 135 | 136 | int32_t read_int32(buffered_reader * reader) 137 | { 138 | return (int32_t)read_uint32(reader); 139 | } 140 | 141 | void read_bytes(buffered_reader * reader, uint8_t * dest, size_t count) 142 | { 143 | // Copy out anything buffered that is still available 144 | size_t remaining = reader->valid - reader->mark; 145 | if (remaining > count) { 146 | memcpy(dest, &reader->buffer[reader->mark], count); 147 | reader->mark += count; 148 | return; 149 | } 150 | memcpy(dest, &reader->buffer[reader->mark], remaining); 151 | reader->offset += reader->valid; 152 | reader->mark = 0; 153 | reader->valid = 0; 154 | reader->offset += fread(dest + remaining, 1, count - remaining, reader->file); 155 | } 156 | 157 | static 158 | size_t ensure_minimum_bytes_available(buffered_reader * reader, size_t count) 159 | { 160 | size_t remaining = reader->valid - reader->mark; 161 | if (remaining >= count) { 162 | return remaining; 163 | } 164 | // Are there enough in the whole file to fulfill the request? 165 | if (reader->total_size - (reader->offset + reader->mark) < count) { 166 | return 0; 167 | } 168 | // Shift remaining valid bytes to the top of the buffer, and refill the rest. 169 | memmove(&reader->buffer[0], &reader->buffer[reader->mark], remaining); 170 | reader->offset += reader->mark; 171 | reader->valid = remaining; 172 | reader->mark = 0; 173 | reader->valid += fread(&reader->buffer[reader->valid], 1, BUFFER_SIZE - reader->valid, reader->file); 174 | return reader->valid; 175 | } 176 | -------------------------------------------------------------------------------- /apple_gcr.c: -------------------------------------------------------------------------------- 1 | // 2 | // apple_gcr.h 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // Portions of this module are (c) 2018 Thomas Harte (offered under the same LICENSE). 6 | // 7 | 8 | #include "apple_gcr.h" 9 | #include 10 | 11 | #define DOS_VOLUME_NUMBER 254 12 | #define TRACK_LEADER_SYNC_COUNT 64 13 | #define SECTORS_PER_TRACK 16 14 | #define BYTES_PER_SECTOR 256 15 | #define GCR_SECTOR_ENCODED_SIZE 343 16 | 17 | static size_t bits_write_byte(uint8_t * buffer, size_t index, int value); 18 | static size_t bits_write_4_and_4(uint8_t * buffer, size_t index, int value); 19 | static size_t bits_write_sync(uint8_t * buffer, size_t index); 20 | static void encode_6_and_2(uint8_t * dest, const uint8_t * src); 21 | 22 | // 23 | // Track encoding and writing routines 24 | // 25 | 26 | size_t gcr_encode_bits_for_track(uint8_t * dest, uint8_t * src, int track_number, dsk_sector_format sector_format) 27 | { 28 | size_t bit_index = 0; 29 | memset(dest, 0, GCR_ENCODED_TRACK_SIZE); 30 | 31 | // Write 64 sync words 32 | for (int i = 0; i < TRACK_LEADER_SYNC_COUNT; i++) { 33 | bit_index = bits_write_sync(dest, bit_index); 34 | } 35 | 36 | // Write out the sectors in physical order. We will select the appopriate logical 37 | // input data for each physical output sector. 38 | for (int s = 0; s < SECTORS_PER_TRACK; s++) { 39 | 40 | // 41 | // Sector header 42 | // 43 | 44 | // Prologue 45 | bit_index = bits_write_byte(dest, bit_index, 0xD5); 46 | bit_index = bits_write_byte(dest, bit_index, 0xAA); 47 | bit_index = bits_write_byte(dest, bit_index, 0x96); 48 | 49 | // Volume, track, sector and checksum, all in 4-and-4 format 50 | bit_index = bits_write_4_and_4(dest, bit_index, DOS_VOLUME_NUMBER); 51 | bit_index = bits_write_4_and_4(dest, bit_index, track_number); 52 | bit_index = bits_write_4_and_4(dest, bit_index, s); 53 | bit_index = bits_write_4_and_4(dest, bit_index, DOS_VOLUME_NUMBER ^ track_number ^ s); 54 | 55 | // Epilogue 56 | bit_index = bits_write_byte(dest, bit_index, 0xDE); 57 | bit_index = bits_write_byte(dest, bit_index, 0xAA); 58 | bit_index = bits_write_byte(dest, bit_index, 0xEB); 59 | 60 | // Write 7 sync words. 61 | for (int i = 0; i < 7; i++) { 62 | bit_index = bits_write_sync(dest, bit_index); 63 | } 64 | 65 | // 66 | // Sector body 67 | // 68 | 69 | // Prologue 70 | bit_index = bits_write_byte(dest, bit_index, 0xD5); 71 | bit_index = bits_write_byte(dest, bit_index, 0xAA); 72 | bit_index = bits_write_byte(dest, bit_index, 0xAD); 73 | 74 | // Figure out which logical sector goes into this physical sector. 75 | int logical_sector; 76 | if (s == 0x0F) { 77 | logical_sector = 0x0F; 78 | } else { 79 | int multiplier = (sector_format == dsk_sector_format_prodos) ? 8 : 7; 80 | logical_sector = (s * multiplier) % 15; 81 | } 82 | 83 | // Finally, the actual contents! Encode the buffer, then write them. 84 | uint8_t encoded_contents[GCR_SECTOR_ENCODED_SIZE]; 85 | encode_6_and_2(encoded_contents, &src[logical_sector * BYTES_PER_SECTOR]); 86 | for (int i = 0; i < GCR_SECTOR_ENCODED_SIZE; i++) { 87 | bit_index = bits_write_byte(dest, bit_index, encoded_contents[i]); 88 | } 89 | 90 | // Epilogue 91 | bit_index = bits_write_byte(dest, bit_index, 0xDE); 92 | bit_index = bits_write_byte(dest, bit_index, 0xAA); 93 | bit_index = bits_write_byte(dest, bit_index, 0xEB); 94 | 95 | // Conclude the track 96 | if (s < (SECTORS_PER_TRACK - 1)) { 97 | // Write 16 sync words 98 | for (int i = 0; i < 16; i++) { 99 | bit_index = bits_write_sync(dest, bit_index); 100 | } 101 | } else { 102 | bit_index = bits_write_byte(dest, bit_index, 0xFF); 103 | } 104 | } 105 | 106 | // Any remaining bytes in the destination buffer will remain cleared to zero and 107 | // function as padding to the nearest 512-byte block. 108 | 109 | // Return the current bit index, which is equal to the number of valid written bits 110 | return bit_index; 111 | } 112 | 113 | // 114 | // Helper routines. 115 | // 116 | 117 | static 118 | size_t bits_write_byte(uint8_t * buffer, size_t index, int value) 119 | { 120 | size_t shift = index & 7; 121 | size_t byte_position = index >> 3; 122 | 123 | buffer[byte_position] |= value >> shift; 124 | if (shift) { 125 | buffer[byte_position + 1] |= value << (8 - shift); 126 | } 127 | 128 | return index + 8; 129 | } 130 | 131 | // Writes a byte in 4-and-4 132 | static 133 | size_t bits_write_4_and_4(uint8_t * buffer, size_t index, int value) 134 | { 135 | index = bits_write_byte(buffer, index, (value >> 1) | 0xAA); 136 | index = bits_write_byte(buffer, index, value | 0xAA); 137 | return index; 138 | } 139 | 140 | // Writes a 6-and-2 sync word 141 | static 142 | size_t bits_write_sync(uint8_t * buffer, size_t index) 143 | { 144 | index = bits_write_byte(buffer, index, 0xFF); 145 | return index + 2; // Skip two bits, i.e. leave them as 0s. 146 | } 147 | 148 | // Encodes a 256-byte sector buffer into a 343 byte 6-and-2 encoding of same 149 | static 150 | void encode_6_and_2(uint8_t * dest, const uint8_t * src) 151 | { 152 | const uint8_t six_and_two_mapping[] = { 153 | 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, 154 | 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, 155 | 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 156 | 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, 157 | 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 158 | 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, 159 | 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 160 | 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff 161 | }; 162 | 163 | // Fill in byte values: the first 86 bytes contain shuffled 164 | // and combined copies of the bottom two bits of the sector 165 | // contents; the 256 bytes afterwards are the remaining 166 | // six bits. 167 | const uint8_t bit_reverse[] = {0, 2, 1, 3}; 168 | for (int c = 0; c < 84; c++) { 169 | dest[c] = 170 | bit_reverse[src[c] & 3] | 171 | (bit_reverse[src[c + 86] & 3] << 2) | 172 | (bit_reverse[src[c + 172] & 3] << 4); 173 | } 174 | dest[84] = 175 | (bit_reverse[src[84] & 3] << 0) | 176 | (bit_reverse[src[170] & 3] << 2); 177 | dest[85] = 178 | (bit_reverse[src[85] & 3] << 0) | 179 | (bit_reverse[src[171] & 3] << 2); 180 | 181 | for (int c = 0; c < 256; c++) { 182 | dest[86+c] = src[c] >> 2; 183 | } 184 | 185 | // Exclusive OR each byte with the one before it. 186 | dest[342] = dest[341]; 187 | int location = 342; 188 | while (location > 1) { 189 | location--; 190 | dest[location] ^= dest[location-1]; 191 | } 192 | 193 | // Map six-bit values up to full bytes. 194 | for (int c = 0; c < 343; c++) { 195 | dest[c] = six_and_two_mapping[dest[c]]; 196 | } 197 | } 198 | 199 | -------------------------------------------------------------------------------- /woz_image.c: -------------------------------------------------------------------------------- 1 | // 2 | // woz_image.c 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | 7 | #include "woz_image.h" 8 | 9 | #define CHUNK_INITIAL_BUFFER 4096 10 | #define WOZ_HEADER_SIZE 12 11 | // 12 | // Private routine declarations. 13 | // 14 | static void verify_writable_buffer(woz_chunk * chunk, size_t min); 15 | static size_t serialize_chunk_to_buffer(woz_chunk * chunk, uint8_t * dest); 16 | 17 | // 18 | // Public routines. 19 | // 20 | 21 | woz_file * create_empty_woz_file(void) 22 | { 23 | woz_file * woz = malloc(sizeof(woz_file)); 24 | if (woz) { 25 | woz->info = create_woz_chunk("INFO"); 26 | woz->tmap = create_woz_chunk("TMAP"); 27 | woz->trks = create_woz_chunk("TRKS"); 28 | woz->writ = create_woz_chunk("WRIT"); 29 | } 30 | if (!woz || !woz->info || !woz->tmap || !woz->writ) { 31 | goto Error; 32 | } 33 | 34 | goto Done; 35 | Error: 36 | if (woz) { 37 | free_woz_file(woz); 38 | } 39 | woz = NULL; 40 | 41 | Done: 42 | return woz; 43 | } 44 | 45 | int write_woz_to_file(woz_file * woz, const char * path) 46 | { 47 | FILE * file = fopen(path, "wb"); 48 | if (!file) { 49 | printf("Failed to open output file %s", path); 50 | return -1; 51 | } 52 | 53 | // Calculate the total size needed to write each actual chunk. 54 | size_t total_file_size = WOZ_HEADER_SIZE; 55 | total_file_size += chunk_size_on_disk(woz->info); 56 | total_file_size += chunk_size_on_disk(woz->tmap); 57 | total_file_size += chunk_size_on_disk(woz->trks); 58 | total_file_size += chunk_size_on_disk(woz->writ); 59 | 60 | uint8_t * file_buffer = malloc(total_file_size); 61 | if (!file_buffer) { 62 | printf("Out of memory.\n"); 63 | fclose(file); 64 | return -2; 65 | } 66 | 67 | file_buffer[0] = 'W'; 68 | file_buffer[1] = 'O'; 69 | file_buffer[2] = 'Z'; 70 | file_buffer[3] = '2'; 71 | file_buffer[4] = 0xFF; 72 | file_buffer[5] = '\n'; 73 | file_buffer[6] = '\r'; 74 | file_buffer[7] = '\n'; 75 | // (CRC goes here at offset 8) 76 | size_t byte_index = WOZ_HEADER_SIZE; 77 | byte_index += serialize_chunk_to_buffer(woz->info, &file_buffer[byte_index]); 78 | byte_index += serialize_chunk_to_buffer(woz->tmap, &file_buffer[byte_index]); 79 | byte_index += serialize_chunk_to_buffer(woz->trks, &file_buffer[byte_index]); 80 | /* byte_index += */ serialize_chunk_to_buffer(woz->writ, &file_buffer[byte_index]); 81 | 82 | // Compute the overall CRC of everthing after the header, and write it in. 83 | uint32_t crc = woz_crc32(&file_buffer[WOZ_HEADER_SIZE], total_file_size - WOZ_HEADER_SIZE); 84 | file_buffer[8] = crc & 0xFF; 85 | file_buffer[9] = (crc >> 8) & 0xFF; 86 | file_buffer[10] = (crc >> 16) & 0xFF; 87 | file_buffer[11] = (crc >> 24) & 0xFF; 88 | 89 | // Write to disk. 90 | size_t bytes_written = fwrite(file_buffer, 1, total_file_size, file); 91 | 92 | fclose(file); 93 | free(file_buffer); 94 | 95 | if (bytes_written != total_file_size) { 96 | printf("Error writing woz output.\n"); 97 | return -1; 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | void free_woz_file(woz_file * woz) 104 | { 105 | if (woz) { 106 | if (woz->info) { free_chunk(woz->info); } 107 | if (woz->tmap) { free_chunk(woz->tmap); } 108 | if (woz->trks) { free_chunk(woz->trks); } 109 | if (woz->writ) { free_chunk(woz->writ); } 110 | free(woz); 111 | } 112 | } 113 | 114 | woz_chunk * create_woz_chunk(const char * name) 115 | { 116 | woz_chunk * chunk = malloc(sizeof(woz_chunk)); 117 | if (!chunk) { 118 | return NULL; 119 | } 120 | chunk->data = calloc(CHUNK_INITIAL_BUFFER, 1); 121 | if (!chunk->data) { 122 | free(chunk); 123 | return NULL; 124 | } 125 | memcpy(chunk->name, name, 4); 126 | chunk->buffer_size = CHUNK_INITIAL_BUFFER; 127 | chunk->mark = 0; 128 | return chunk; 129 | } 130 | 131 | void free_chunk(woz_chunk * chunk) 132 | { 133 | free(chunk->data); 134 | free(chunk); 135 | } 136 | 137 | size_t chunk_size_on_disk(woz_chunk * chunk) 138 | { 139 | // 4 bytes for the name characters, 4 for the length value. 140 | return 4 + 4 + chunk->mark; 141 | } 142 | 143 | void chunk_write_uint8(woz_chunk * chunk, uint8_t u8) 144 | { 145 | verify_writable_buffer(chunk, sizeof(uint8_t)); 146 | chunk->data[chunk->mark++] = u8; 147 | } 148 | 149 | void chunk_write_uint16(woz_chunk * chunk, uint16_t u16) 150 | { 151 | verify_writable_buffer(chunk, sizeof(uint16_t)); 152 | chunk->data[chunk->mark++] = u16 & 0x00FF; 153 | chunk->data[chunk->mark++] = (u16 >> 8) & 0x00FF; 154 | } 155 | 156 | void chunk_write_uint32(woz_chunk * chunk, uint32_t u32) 157 | { 158 | verify_writable_buffer(chunk, sizeof(uint32_t)); 159 | chunk->data[chunk->mark++] = u32 & 0x000000FF; 160 | chunk->data[chunk->mark++] = (u32 >> 8) & 0x000000FF; 161 | chunk->data[chunk->mark++] = (u32 >> 16) & 0x000000FF; 162 | chunk->data[chunk->mark++] = (u32 >> 24) & 0x000000FF; 163 | } 164 | 165 | // This routine expects utf8string to be both a valid UTF string and 166 | // NUL terminated. If the string is longer than n character, it will be 167 | // truncated. The resulting string will not be NUL terminated but will be 168 | // padded with space characters. 169 | void chunk_write_utf8(woz_chunk * chunk, const char * utf8string, int n) 170 | { 171 | verify_writable_buffer(chunk, n); 172 | size_t len = strlen(utf8string); 173 | uint8_t * dest = &chunk->data[chunk->mark]; 174 | for (int i = 0; i < n; i++) { 175 | dest[i] = (i < len) ? utf8string[i] : 0x20; 176 | } 177 | chunk->mark += n; 178 | } 179 | 180 | void chunk_write_bytes(woz_chunk * chunk, const uint8_t * bytes, size_t n) 181 | { 182 | verify_writable_buffer(chunk, n); 183 | memcpy(&chunk->data[chunk->mark], bytes, n); 184 | chunk->mark += n; 185 | } 186 | 187 | void chunk_set_mark(woz_chunk * chunk, size_t mark) 188 | { 189 | if (mark <= chunk->mark) { 190 | chunk->mark = mark; 191 | return; 192 | } 193 | verify_writable_buffer(chunk, mark - chunk->mark); 194 | chunk->mark = mark; 195 | } 196 | 197 | void chunk_advance_mark(woz_chunk * chunk, int offset) 198 | { 199 | size_t new_mark = chunk->mark + offset; 200 | chunk_set_mark(chunk, new_mark); 201 | } 202 | 203 | // 204 | // Private helper routines. 205 | // 206 | 207 | static 208 | void verify_writable_buffer(woz_chunk * chunk, size_t min) 209 | { 210 | if (chunk->mark + min > chunk->buffer_size) { 211 | size_t new_size = chunk->buffer_size * 2; 212 | if (chunk->mark + min > new_size) { 213 | new_size += min; 214 | } 215 | uint8_t * new_buffer = calloc(new_size, 1); 216 | if (!new_buffer) { 217 | // We don't handle this error which should never happen... 218 | // Will crash on the memcpy that comes next... 219 | printf("Out of memory expanding chunk buffer."); 220 | } 221 | memcpy(new_buffer, chunk->data, chunk->buffer_size); 222 | free(chunk->data); 223 | chunk->data = new_buffer; 224 | chunk->buffer_size = new_size; 225 | } 226 | } 227 | 228 | static 229 | size_t serialize_chunk_to_buffer(woz_chunk * chunk, uint8_t * dest) 230 | { 231 | memcpy(dest, &chunk->name, 4); 232 | size_t size = chunk->mark; 233 | dest[4] = size & 0xFF; 234 | dest[5] = size >> 8 & 0xFF; 235 | dest[6] = size >> 16 & 0xFF; 236 | dest[7] = size >> 24 & 0xFF; 237 | memcpy(&dest[8], chunk->data, chunk->mark); 238 | return 4 + 4 + chunk->mark; 239 | } 240 | 241 | 242 | // 243 | // CRC routine and table. 244 | // Gary S. Brown, 1986. 245 | // Copied from https://applesaucefdc.com/woz/reference2/ 246 | // 247 | 248 | static uint32_t crc32_tab[] = { 249 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 250 | 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 251 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 252 | 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 253 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 254 | 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 255 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 256 | 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 257 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 258 | 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 259 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 260 | 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 261 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 262 | 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 263 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 264 | 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 265 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 266 | 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 267 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 268 | 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 269 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 270 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 271 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 272 | 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 273 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 274 | 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 275 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 276 | 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 277 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 278 | 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 279 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 280 | 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 281 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 282 | 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 283 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 284 | 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 285 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 286 | 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 287 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 288 | 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 289 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 290 | 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 291 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 292 | }; 293 | 294 | uint32_t woz_crc32(const void *buf, size_t size) 295 | { 296 | const uint8_t * p = buf; 297 | uint32_t crc = 0 ^ ~0U; 298 | while (size--) { 299 | crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); 300 | } 301 | return crc ^ ~0U; 302 | } 303 | -------------------------------------------------------------------------------- /bmp_bitmap.c: -------------------------------------------------------------------------------- 1 | // 2 | // bmp_bitmap.c 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | 7 | #include 8 | #include "bmp_bitmap.h" 9 | #include "buffered_reader.h" 10 | 11 | typedef struct _bmp_file_header { 12 | uint16_t file_type; 13 | uint32_t file_size; 14 | uint16_t reserved0; 15 | uint16_t reserved1; 16 | uint32_t bitmap_offset; 17 | } bmp_file_header; 18 | 19 | typedef enum _bmp_compression { 20 | bmp_compression_none, 21 | bmp_compression_rle8, 22 | bmp_compression_rle4, 23 | bmp_compression_bitfields 24 | } bmp_compression; 25 | 26 | #define BMP_HEADER_SIZE_V3 40 27 | #define BMP_HEADER_SIZE_V4 108 28 | #define BMP_HEADER_SIZE_V5 124 29 | 30 | typedef struct _bmp_header { 31 | uint32_t size; 32 | int32_t width; 33 | int32_t height; 34 | uint16_t planes; 35 | uint16_t bits_per_pixel; 36 | bmp_compression compression; 37 | uint32_t size_of_bitmap; 38 | int32_t horz_resolution; 39 | int32_t vert_resolution; 40 | uint32_t colors_used; 41 | uint32_t colors_important; 42 | // Fields below this line were added in v4 (Win95/NT4) 43 | uint32_t red_mask; 44 | uint32_t green_mask; 45 | uint32_t blue_mask; 46 | uint32_t alpha_mask; 47 | uint32_t cs_type; 48 | int32_t red_x; 49 | int32_t red_y; 50 | int32_t red_z; 51 | int32_t green_x; 52 | int32_t green_y; 53 | int32_t green_z; 54 | int32_t blue_x; 55 | int32_t blue_y; 56 | int32_t blue_z; 57 | uint32_t gamma_red; 58 | uint32_t gamma_green; 59 | uint32_t gamma_blue; 60 | // Fields below this line were added in v5 (Win98/NT5) 61 | uint32_t intent; 62 | uint32_t profile_data; 63 | uint32_t profile_size; 64 | uint32_t reserved; 65 | } bmp_header; 66 | 67 | typedef struct _bmp_palette_element { 68 | uint8_t blue; 69 | uint8_t green; 70 | uint8_t red; 71 | uint8_t reserved; 72 | } bmp_palette_element; 73 | 74 | bitmap * load_bmp_into_bitmap(const char * bmp_path) 75 | { 76 | bitmap * bitmap = NULL; 77 | uint8_t * raw_bitmap_data = NULL; 78 | 79 | buffered_reader * reader = open_buffered_reader(bmp_path, file_endianness_little); 80 | if (!reader) { 81 | printf("Could not open file %s\n", bmp_path); 82 | return NULL; 83 | } 84 | 85 | // Ensure the file header 86 | if (!buffered_reader_ensure_remaining(reader, 18)) { 87 | printf("Invalid BMP file\n"); 88 | goto Error; 89 | } 90 | 91 | bmp_file_header file_header; 92 | file_header.file_type = read_uint16(reader); 93 | file_header.file_size = read_uint32(reader); 94 | file_header.reserved0 = read_uint16(reader); 95 | file_header.reserved1 = read_uint16(reader); 96 | file_header.bitmap_offset = read_uint32(reader); 97 | 98 | // Ensure the entire file size 99 | if (!buffered_reader_ensure_remaining(reader, file_header.file_size - 18)) { 100 | printf("Invalid BMP file\n"); 101 | goto Error; 102 | } 103 | 104 | // "BM" (as chars, not a little-endian uint16, so compare to a flipped version) 105 | if (file_header.file_type != 0x4D42 || file_header.file_size != reader->total_size) { 106 | printf("Invalid BMP file\n"); 107 | goto Error; 108 | } 109 | 110 | uint32_t bitmap_header_size = read_uint32(reader); 111 | if (bitmap_header_size != BMP_HEADER_SIZE_V3 && 112 | bitmap_header_size != BMP_HEADER_SIZE_V4 && 113 | bitmap_header_size != BMP_HEADER_SIZE_V5) { 114 | printf("Unsupported BMP version\n"); 115 | goto Error; 116 | } 117 | 118 | bmp_header header; 119 | 120 | // Read the v3 fields 121 | header.size = bitmap_header_size; 122 | header.width = read_int32(reader); 123 | header.height = read_int32(reader); 124 | header.planes = read_uint16(reader); 125 | header.bits_per_pixel = read_uint16(reader); 126 | header.compression = read_uint32(reader); 127 | header.size_of_bitmap = read_uint32(reader); 128 | header.horz_resolution = read_int32(reader); 129 | header.vert_resolution = read_int32(reader); 130 | header.colors_used = read_uint32(reader); 131 | header.colors_important = read_uint32(reader); 132 | 133 | // v3 bitmaps can specify bitfield encoding, in which case these RGB mask 134 | // fields appear immediately after the header. 135 | if (bitmap_header_size == BMP_HEADER_SIZE_V3 && 136 | header.compression == bmp_compression_bitfields ) { 137 | header.red_mask = read_uint32(reader); 138 | header.green_mask = read_uint32(reader); 139 | header.blue_mask = read_uint32(reader); 140 | header.alpha_mask = 0; // no alpha mask in v3 bitfields. 141 | } 142 | 143 | // Read v4 fields 144 | if (bitmap_header_size >= BMP_HEADER_SIZE_V4) { 145 | header.red_mask = read_uint32(reader); 146 | header.green_mask = read_uint32(reader); 147 | header.blue_mask = read_uint32(reader); 148 | header.alpha_mask = read_uint32(reader); 149 | header.cs_type = read_uint32(reader); 150 | header.red_x = read_int32(reader); 151 | header.red_y = read_int32(reader); 152 | header.red_z = read_int32(reader); 153 | header.green_x = read_int32(reader); 154 | header.green_y = read_int32(reader); 155 | header.green_z = read_int32(reader); 156 | header.blue_x = read_int32(reader); 157 | header.blue_y = read_int32(reader); 158 | header.blue_z = read_int32(reader); 159 | header.gamma_red = read_uint32(reader); 160 | header.gamma_green = read_uint32(reader); 161 | header.gamma_blue = read_uint32(reader); 162 | } 163 | 164 | // Read v5 fields 165 | if (bitmap_header_size == BMP_HEADER_SIZE_V5) { 166 | header.intent = read_uint32(reader); 167 | header.profile_data = read_uint32(reader); 168 | header.profile_size = read_uint32(reader); 169 | header.reserved = read_uint32(reader); 170 | } 171 | 172 | if (header.bits_per_pixel != 1 && header.bits_per_pixel != 4 && 173 | header.bits_per_pixel != 8 && header.bits_per_pixel != 24 && 174 | header.bits_per_pixel != 32) { 175 | printf("%d-bit BMP not supported\n", header.bits_per_pixel); 176 | goto Error; 177 | } 178 | 179 | // Currently we only suppport uncompressed bitmaps, plus one (the only?) common 180 | // 32-bit "bitfields" format. 181 | if (header.compression == bmp_compression_bitfields) { 182 | if (header.bits_per_pixel != 32) { 183 | printf("Unsupported BMP format (%d-bit bitfields)\n", header.bits_per_pixel); 184 | goto Error; 185 | } 186 | if (header.red_mask != 0x00FF0000 || header.green_mask != 0x0000FF00 || 187 | header.blue_mask != 0x000000FF || header.alpha_mask != 0xFF000000) { 188 | printf("Unsupported BMP format (unordered bitfields)\n"); 189 | goto Error; 190 | } 191 | } else if (header.compression != bmp_compression_none) { 192 | printf("Only uncompressed BMP formats supported\n"); 193 | goto Error; 194 | } 195 | 196 | // Load the palette if applicable. 197 | int palette_entries = 0; 198 | if (header.bits_per_pixel < 16) { 199 | if (header.colors_used == 0) { 200 | palette_entries = 1 << header.bits_per_pixel; 201 | } else { 202 | palette_entries = header.colors_used; 203 | } 204 | } 205 | 206 | // 256 is the max number of possible palette entries (= 8pp), 207 | // only palette_entries will be valid. 208 | bmp_palette_element palette[256]; 209 | for (int i = 0; i < palette_entries; i++) { 210 | palette[i].blue = read_uint8(reader); 211 | palette[i].green = read_uint8(reader); 212 | palette[i].red = read_uint8(reader); 213 | palette[i].reserved = read_uint8(reader); 214 | } 215 | 216 | // Fast forward to the bitmap data itself. We're usually already pointing at it, 217 | // but on the off chance this is v5 bitmap that has some embedded color profile 218 | // data, and they chose to stick it between the header and the bitmap data, well, 219 | // this should skip over it. 220 | buffered_reader_advance_to_offset(reader, file_header.bitmap_offset); 221 | 222 | // We are now pointing at the bitmap data itself. Walk the lines, and unpack 223 | // indexed colors as necessary. 224 | int width = header.width; 225 | int height = (header.height > 0) ? header.height : header.height * -1; 226 | int is_flipped = header.height < 0; 227 | 228 | // Figure out how many bytes in one "scan line" (stride) of the image data. Always 229 | // aligned to 4-byte boundaries. 230 | int bits_per_line = header.bits_per_pixel * width; 231 | if (bits_per_line % 32 != 0) { 232 | bits_per_line += (32 - bits_per_line % 32); 233 | } 234 | 235 | int bytes_per_line = bits_per_line / 8; 236 | 237 | // Final sanity check to make sure that enough bytes remain in the file to meet 238 | // our needs here. 239 | size_t raw_bitmap_size = bytes_per_line * height; 240 | if (!buffered_reader_ensure_remaining(reader, raw_bitmap_size)) { 241 | printf("Invalid BMP file\n"); 242 | close_buffered_reader(reader); 243 | return NULL; 244 | } 245 | 246 | // Yank out the whole bitmap region. 247 | raw_bitmap_data = malloc(raw_bitmap_size); 248 | if (!raw_bitmap_data) { 249 | printf("Failed to allocate bitmap data\n"); 250 | goto Error; 251 | 252 | } 253 | read_bytes(reader, raw_bitmap_data, raw_bitmap_size); 254 | 255 | bitmap = create_bitmap(width, height); 256 | if (!bitmap) { 257 | printf("Failed to allocate bitmap\n"); 258 | goto Error; 259 | } 260 | 261 | // Loop through the bitmap. Note that this is looping through the *output* pixels, 262 | // and the bitmap file may not be "flipped" (ie, first line first) 263 | for (int y = 0; y < height; y++) { 264 | uint8_t * line_base = is_flipped ? (raw_bitmap_data + (y * bytes_per_line)) : 265 | (raw_bitmap_data + ((height - 1 - y) * bytes_per_line)); 266 | uint8_t * next_pixel_start = line_base; 267 | int x = 0; 268 | while (x < width) { 269 | uint8_t byte = *next_pixel_start; 270 | uint8_t pixel_palette_indexes[8]; // only used for 1, 4, 8 bit 271 | uint8_t rgba[4]; // only used for 24, 32 bit. 272 | switch (header.bits_per_pixel) { 273 | case 1: 274 | { 275 | for (int p = 7; p >= 0; p--) { 276 | pixel_palette_indexes[p] = (byte >> p) & 0x01; 277 | } 278 | next_pixel_start++; 279 | break; 280 | } 281 | case 4: 282 | { 283 | pixel_palette_indexes[0] = (byte >> 4) & 0x0F; 284 | pixel_palette_indexes[1] = byte & 0x0F; 285 | next_pixel_start++; 286 | break; 287 | } 288 | case 8: 289 | { 290 | pixel_palette_indexes[0] = byte; 291 | next_pixel_start++; 292 | break; 293 | } 294 | case 24: 295 | { 296 | // Stored in BGR order 297 | rgba[3] = 0xFF; 298 | rgba[2] = *(next_pixel_start + 0); 299 | rgba[1] = *(next_pixel_start + 1); 300 | rgba[0] = *(next_pixel_start + 2); 301 | next_pixel_start += 3; 302 | break; 303 | } 304 | case 32: 305 | { 306 | // We expect this to be in BGRA order, which is the default in the non-compressed 307 | // format as well as the only "bitfields" ordering we support. 308 | rgba[2] = *(next_pixel_start + 0); 309 | rgba[1] = *(next_pixel_start + 1); 310 | rgba[0] = *(next_pixel_start + 2); 311 | rgba[3] = *(next_pixel_start + 3); 312 | next_pixel_start += 4; 313 | break; 314 | } 315 | default: 316 | // Unreachable. We already validated the value set above. 317 | break; 318 | } 319 | 320 | if (header.bits_per_pixel < 16) { 321 | // We have one or more palette indexes to indirect through. 322 | for (int i = 0; i < (8 / header.bits_per_pixel); i++) { 323 | uint8_t index = pixel_palette_indexes[i]; 324 | if (index < palette_entries) { 325 | int pixel_base = BITMAP_PIXEL_BASE(bitmap, x, y); 326 | bitmap->rgba_pixels[pixel_base] = palette[index].red; 327 | bitmap->rgba_pixels[pixel_base + 1] = palette[index].green; 328 | bitmap->rgba_pixels[pixel_base + 2] = palette[index].blue; 329 | bitmap->rgba_pixels[pixel_base + 3] = 0xFF; 330 | } 331 | // Advance the destination past this pixel and continue with this 332 | // cluster. 333 | x++; 334 | } 335 | } else { 336 | // We have computed the value of one pixel, write it. 337 | int pixel_base = BITMAP_PIXEL_BASE(bitmap, x, y); 338 | bitmap->rgba_pixels[pixel_base] = rgba[0]; 339 | bitmap->rgba_pixels[pixel_base + 1] = rgba[1]; 340 | bitmap->rgba_pixels[pixel_base + 2] = rgba[2]; 341 | bitmap->rgba_pixels[pixel_base + 3] = rgba[3]; 342 | x++; 343 | } 344 | } 345 | } 346 | 347 | goto Done; 348 | 349 | Error: 350 | if (bitmap) { 351 | free_bitmap(bitmap); 352 | } 353 | Done: 354 | if (raw_bitmap_data) { 355 | free(raw_bitmap_data); 356 | } 357 | close_buffered_reader(reader); 358 | return bitmap; 359 | } 360 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // 4 | // Copyright (c) 2021 by Ben Zotto 5 | // 6 | 7 | #include 8 | #include 9 | #include 10 | #include "bmp_bitmap.h" 11 | #include "apple_gcr.h" 12 | #include "woz_image.h" 13 | 14 | #define SCREEN_BITMAP_DIMENSION 147 15 | #define SCREEN_BITMAP_STRIDE_BYTES (SCREEN_BITMAP_DIMENSION / 7) 16 | #define DISPLAY_MESSAGE_OFFSET 177 17 | 18 | #define CREATOR_NAME "PictureDSK" 19 | #define MAX_MESSAGE_LEN 40 20 | 21 | #define TRACKS_PER_DISK 46 22 | #define SECTORS_PER_TRACK 16 23 | #define BYTES_PER_SECTOR 256 24 | #define BYTES_PER_TRACK (SECTORS_PER_TRACK * BYTES_PER_SECTOR) 25 | 26 | #define BITS_BLOCKS_PER_TRACK 13 27 | #define BITS_BLOCK_SIZE 512 28 | #define BITS_TRACK_SIZE (BITS_BLOCKS_PER_TRACK * BITS_BLOCK_SIZE) 29 | #define BITS_SECTOR_CONTENTS_SIZE 343 30 | 31 | #define DOS_VOLUME_NUMBER 254 32 | #define TRACK_LEADER_SYNC_COUNT 64 33 | 34 | // 35 | // Helper types and routines. 36 | // 37 | 38 | typedef struct _track_data { 39 | size_t data_length; 40 | int block_count; 41 | uint8_t data[0]; 42 | } track_data; 43 | 44 | static track_data * create_track_data(size_t length); 45 | static void free_track_data(track_data * data); 46 | 47 | static uint8_t boot_1_sector_0[BYTES_PER_SECTOR]; 48 | static uint8_t boot_2_sector_F[BYTES_PER_SECTOR]; 49 | 50 | // 51 | // Main program. 52 | // 53 | 54 | int main(int argc, const char * argv[]) 55 | { 56 | if (argc < 3 || argc > 4) { 57 | printf("USAGE: picturedsk image.bmp output.woz [message] \n"); 58 | return -1; 59 | } 60 | 61 | // Load the input bitmap. 62 | bitmap * image = load_bmp_into_bitmap(argv[1]); 63 | if (!image) { 64 | // That routine will print its own granular error. 65 | return -2; 66 | } 67 | 68 | // 69 | // Sample the bitmap to create a version in the Apple high-res format. 70 | // 71 | 72 | uint8_t a2_high_res_image[SCREEN_BITMAP_STRIDE_BYTES * SCREEN_BITMAP_DIMENSION]; 73 | uint8_t * a2_dest_ptr = &a2_high_res_image[0]; 74 | uint8_t shiftreg = 0x80; 75 | int shiftreg_valid = 0; 76 | for (int y = 0; y < SCREEN_BITMAP_DIMENSION; y++) { 77 | for (int x = 0; x < SCREEN_BITMAP_DIMENSION; x++) { 78 | float u = x / (float)SCREEN_BITMAP_DIMENSION; 79 | float v = y / (float)SCREEN_BITMAP_DIMENSION; 80 | double grey = sample_bitmap_greyscale(image, u, v); 81 | uint8_t bit = 1 << shiftreg_valid; 82 | if (grey >= 0.5) { 83 | shiftreg |= bit; 84 | } 85 | if (++shiftreg_valid == 7) { 86 | *a2_dest_ptr++ = shiftreg; 87 | shiftreg = 0x80; 88 | shiftreg_valid = 0; 89 | } 90 | } 91 | } 92 | 93 | // 94 | // Build the .DSK-format data for the first (and sole valid) track on the disk. 95 | // Shuffle the image data into the interleaved disk sectors, so it'll end 96 | // up loaded consecutively at $B100. The boot1 boot loader goes in 97 | // sector 0, and the boot2 code goes in sector F. 98 | // 99 | 100 | uint8_t track_0[SECTORS_PER_TRACK * BYTES_PER_SECTOR]; 101 | memset(track_0, 0, SECTORS_PER_TRACK * BYTES_PER_SECTOR); 102 | memcpy(&track_0[0x000], boot_1_sector_0, BYTES_PER_SECTOR); 103 | memcpy(&track_0[0x800], &a2_high_res_image[0x000], BYTES_PER_SECTOR); 104 | memcpy(&track_0[0x100], &a2_high_res_image[0x100], BYTES_PER_SECTOR); 105 | memcpy(&track_0[0x900], &a2_high_res_image[0x200], BYTES_PER_SECTOR); 106 | memcpy(&track_0[0x200], &a2_high_res_image[0x300], BYTES_PER_SECTOR); 107 | memcpy(&track_0[0xA00], &a2_high_res_image[0x400], BYTES_PER_SECTOR); 108 | memcpy(&track_0[0x300], &a2_high_res_image[0x500], BYTES_PER_SECTOR); 109 | memcpy(&track_0[0xB00], &a2_high_res_image[0x600], BYTES_PER_SECTOR); 110 | memcpy(&track_0[0x400], &a2_high_res_image[0x700], BYTES_PER_SECTOR); 111 | memcpy(&track_0[0xC00], &a2_high_res_image[0x800], BYTES_PER_SECTOR); 112 | memcpy(&track_0[0x500], &a2_high_res_image[0x900], BYTES_PER_SECTOR); 113 | memcpy(&track_0[0xD00], &a2_high_res_image[0xA00], BYTES_PER_SECTOR); 114 | memcpy(&track_0[0x600], &a2_high_res_image[0xB00], BYTES_PER_SECTOR); 115 | memcpy(&track_0[0xE00], &a2_high_res_image[0xC00], 15); // This is how many valid bytes are left in the image. 116 | memcpy(&track_0[0xF00], boot_2_sector_F, BYTES_PER_SECTOR); 117 | 118 | // Fixup the custom display string if one is supplied 119 | if (argc == 4) { 120 | int message_len = (int)strlen(argv[3]); 121 | if (message_len > MAX_MESSAGE_LEN) message_len = MAX_MESSAGE_LEN; 122 | char * message_base = (char *)&track_0[0xF00 + DISPLAY_MESSAGE_OFFSET]; 123 | for (int i = 0; i < message_len; i++) { 124 | char ch = argv[3][i]; 125 | if (ch >= 'a' && ch <= 'z') { 126 | ch -= 0x20; 127 | } 128 | if (ch < ' ' || ch > '_') { 129 | ch = ' '; 130 | } 131 | message_base[i] = ch; 132 | } 133 | // Two newlines and the terminal nul. 134 | message_base[message_len] = 0x0D; 135 | message_base[message_len + 1] = 0x0D; 136 | message_base[message_len + 2] = 0x00; 137 | } 138 | 139 | // 140 | // Prepare the raw data for all of the disk's tracks. 141 | // 142 | 143 | track_data * tracks[TRACKS_PER_DISK]; 144 | 145 | // Encode the one "valid" outer track. 146 | tracks[0] = create_track_data(BITS_TRACK_SIZE); 147 | gcr_encode_bits_for_track(tracks[0]->data, track_0, 0, dsk_sector_format_dos_3_3); 148 | 149 | // Encode the remaining tracks by using a polar coordinate texture sampling of the 150 | // input bitmap image. All tracks on the disk are the same size (13 WOZ blocks). 151 | 152 | // This is based on the output PNG files from the current version of Applesauce. 153 | float radius_per_track = (0.5 - 0.1415) / (float)(TRACKS_PER_DISK - 1); 154 | double arc_segment = (2.0 * M_PI) / (double)BITS_TRACK_SIZE; 155 | 156 | for (int i = 1; i < TRACKS_PER_DISK; i++) { 157 | float r = 0.5 - ((i - 1) * radius_per_track); 158 | tracks[i] = create_track_data(BITS_TRACK_SIZE); 159 | for (int track_byte_index = 0; track_byte_index < BITS_TRACK_SIZE; track_byte_index++) { 160 | // Get a set of (u, v) texture map points from the angle. 161 | float u = r * cosf(M_PI_2 + arc_segment * (BITS_TRACK_SIZE - track_byte_index)); 162 | float v = r * sinf(M_PI_2 + arc_segment * (BITS_TRACK_SIZE - track_byte_index)); 163 | // Translate (u,v) from the center, to the origin. 164 | u += 0.5; 165 | v = 0.5 - v; 166 | double gray = sample_bitmap_greyscale(image, u, v); 167 | tracks[i]->data[track_byte_index] = (gray > 0.5) ? 0xFF : 0x96; 168 | } 169 | } 170 | 171 | // 172 | // Build the WOZ file from the track data. 173 | // 174 | 175 | woz_file * woz = create_empty_woz_file(); 176 | if (!woz) { 177 | printf("Out of memory.\n"); 178 | return -3; 179 | } 180 | 181 | // Build INFO chunk 182 | chunk_write_uint8(woz->info, 2); // INFO v2 183 | chunk_write_uint8(woz->info, 1); // 5.25" image 184 | chunk_write_uint8(woz->info, 1); // Write protected 185 | chunk_write_uint8(woz->info, 1); // Synchronized 186 | chunk_write_uint8(woz->info, 1); // Cleaned 187 | chunk_write_utf8(woz->info, CREATOR_NAME, 32); // Creator 188 | chunk_write_uint8(woz->info, 1); // 1 disk side 189 | chunk_write_uint8(woz->info, 1); // 16-sector format 190 | chunk_write_uint8(woz->info, 32); // 4uS standard bit timing 191 | chunk_write_uint16(woz->info, 0x7F); // Should work on the whole ][ series (?) 192 | chunk_write_uint16(woz->info, 64); // I think this requires 64k (?) 193 | chunk_write_uint16(woz->info, BITS_BLOCKS_PER_TRACK); // Largest track size (all are same) 194 | 195 | // Build TMAP chunk 196 | // 197 | // Track 0 appears at its normal location with its normal bleed-over into 0.25, with the 198 | // normal gap at 0.5. The rest of the tracks are all side-by-each starting at position 1.0, 199 | // with no gap between the sets of three "detected" bits. The rest of the chunk gets the 0xFF 200 | // nothing-marker (not zeros which would indicate something else). 201 | chunk_write_uint8(woz->tmap, 0); 202 | chunk_write_uint8(woz->tmap, 0); 203 | chunk_write_uint8(woz->tmap, 0xFF); 204 | int tmap_nominal_track = 0; // start at 0 so the first loop increments to 1 205 | for (int i = 3; i < 160; i++) { 206 | if (i % 3 == 0) tmap_nominal_track++; 207 | if (tmap_nominal_track < TRACKS_PER_DISK) { 208 | chunk_write_uint8(woz->tmap, tmap_nominal_track); 209 | } else { 210 | chunk_write_uint8(woz->tmap, 0xFF); 211 | } 212 | } 213 | 214 | // Build TRKS chunk 215 | // !!! starting_block is relative to the start of the file !!! This means we rely on 216 | // writing the chunks in a fixed order up to this point (INFO, TMAP, TRKS, ...). 217 | uint16_t starting_block = 3; 218 | for (int i = 0 ; i < TRACKS_PER_DISK; i++) { 219 | chunk_write_uint16(woz->trks, starting_block); 220 | chunk_write_uint16(woz->trks, tracks[i]->block_count); 221 | chunk_write_uint32(woz->trks, (uint32_t)tracks[i]->data_length * 8); 222 | starting_block += tracks[i]->block_count; 223 | } 224 | chunk_set_mark(woz->trks, 1280); 225 | for (int i = 0 ; i < TRACKS_PER_DISK; i++) { 226 | chunk_write_bytes(woz->trks, tracks[i]->data, tracks[i]->data_length); 227 | int empty_padding_length = (int)((tracks[i]->block_count * BITS_BLOCK_SIZE) - tracks[i]->data_length); 228 | chunk_advance_mark(woz->trks, empty_padding_length); 229 | } 230 | 231 | // Build WRIT chunk 232 | int subtrack_index = 0; 233 | for (int i = 0; i < TRACKS_PER_DISK; i++) { 234 | // Track 0 is written at subtrack 0.0. skip to track 1.0 for track 1, but then every 3 235 | // after that. 236 | chunk_write_uint8(woz->writ, subtrack_index); 237 | subtrack_index += ((i == 0) ? 4 : 3); 238 | chunk_write_uint8(woz->writ, 1); // 1 command in this set 239 | chunk_write_uint8(woz->writ, 0x01); // Clear first 240 | chunk_write_uint8(woz->writ, 0); // Reserved (0) 241 | size_t length_for_crc = (tracks[i]->data_length + 7) / 8; 242 | uint32_t crc = woz_crc32(tracks[i]->data, length_for_crc); 243 | chunk_write_uint32(woz->writ, crc); // BITS checksum 244 | chunk_write_uint32(woz->writ, 0); // Don't write leader 245 | chunk_write_uint32(woz->writ, (uint32_t)tracks[i]->data_length * 8); 246 | chunk_write_uint8(woz->writ, 0x00); // Leader nibble 247 | chunk_write_uint8(woz->writ, 0); // Leader nibble count 248 | chunk_write_uint8(woz->writ, 0); // Leader count 249 | chunk_write_uint8(woz->writ, 0); // Reserved (0) 250 | } 251 | 252 | // 253 | // We have a complete WOZ built up in parts. Write the whole thing out to a single 254 | // file buffer for writing to disk. 255 | // 256 | 257 | write_woz_to_file(woz, argv[2]); 258 | 259 | // Cleanup like a good boy scout 260 | free_woz_file(woz); 261 | free_bitmap(image); 262 | for (int i = 0; i < TRACKS_PER_DISK; i++) { 263 | free_track_data(tracks[i]); 264 | } 265 | 266 | return 0; 267 | } 268 | 269 | // 270 | // 271 | // 272 | 273 | static 274 | track_data * create_track_data(size_t length) 275 | { 276 | track_data * data = calloc(1, sizeof(track_data) + length); 277 | if (data) { 278 | data->data_length = length; 279 | int block_count = (int)(length / BITS_BLOCK_SIZE); 280 | if (length % BITS_BLOCK_SIZE > 0) { 281 | block_count++; 282 | } 283 | data->block_count = block_count; 284 | } 285 | return data; 286 | } 287 | 288 | static 289 | void free_track_data(track_data * data) 290 | { 291 | free(data); 292 | } 293 | 294 | static 295 | uint8_t boot_1_sector_0[BYTES_PER_SECTOR] = { 296 | 0x01, 0xA5, 0x27, 0xC9, 0x09, 0xD0, 0x18, 0xA5, 0x2B, 0x4A, 0x4A, 0x4A, 0x4A, 0x09, 0xC0, 0x85, 297 | 0x3F, 0xA9, 0x5C, 0x85, 0x3E, 0x18, 0xAD, 0x5C, 0x08, 0x6D, 0x5D, 0x08, 0x8D, 0x5C, 0x08, 0xAE, 298 | 0x5D, 0x08, 0x30, 0x15, 0xBD, 0x4B, 0x08, 0x85, 0x5D, 0xCE, 0x5D, 0x08, 0xAD, 0x5C, 0x08, 0x85, 299 | 0x27, 0xCE, 0x5C, 0x08, 0xA6, 0x2B, 0x6C, 0x3E, 0x00, 0xEE, 0x5C, 0x08, 0xEE, 0x5C, 0x08, 0x20, 300 | 0x89, 0xFE, 0x20, 0x93, 0xFE, 0x20, 0x2F, 0xFB, 0x4C, 0x00, 0xB0, 0x00, 0x0D, 0x0B, 0x09, 0x07, 301 | 0x05, 0x03, 0x01, 0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F, 0x00, 0xB0, 0x0E, 0xB0, 0x0E, 302 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 303 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 304 | 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x50, 0x69, 0x63, 0x74, 0x75, 0x72, 305 | 0x65, 0x44, 0x53, 0x4B, 0x20, 0x28, 0x74, 0x6D, 0x29, 0x20, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 306 | 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x42, 0x65, 307 | 0x6E, 0x20, 0x5A, 0x6F, 0x74, 0x74, 0x6F, 0x20, 0x32, 0x30, 0x32, 0x31, 0x00, 0x00, 0x00, 0x00, 308 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 309 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 310 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 311 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 312 | }; 313 | 314 | static 315 | uint8_t boot_2_sector_F[BYTES_PER_SECTOR] = { 316 | 0xA2, 0x60, 0xBD, 0x88, 0xC0, 0xA2, 0x50, 0xBD, 0x88, 0xC0, 0xA9, 0x17, 0x85, 0x25, 0x20, 0xE2, 317 | 0xF3, 0xA2, 0x07, 0x20, 0xF0, 0xF6, 0x20, 0x57, 0xF4, 0x20, 0xF6, 0xF3, 0xA9, 0xB1, 0x85, 0x09, 318 | 0xA9, 0x00, 0x85, 0x08, 0x85, 0xFB, 0xAE, 0x52, 0xB0, 0x20, 0x53, 0xB0, 0xA0, 0x09, 0x84, 0xFA, 319 | 0xA4, 0xFB, 0xB1, 0x08, 0xC8, 0xD0, 0x03, 0xEE, 0x09, 0x00, 0x84, 0xFB, 0xA4, 0xFA, 0x91, 0x06, 320 | 0xC8, 0xC0, 0x1E, 0xD0, 0xE9, 0xEE, 0x52, 0xB0, 0xA0, 0x99, 0xCC, 0x52, 0xB0, 0xF0, 0x4E, 0x4C, 321 | 0x26, 0xB0, 0x06, 0x8A, 0x4A, 0x4A, 0x4A, 0x18, 0x0A, 0xA8, 0xB9, 0x75, 0xB0, 0x48, 0xC8, 0xB9, 322 | 0x75, 0xB0, 0x48, 0x8A, 0x29, 0x07, 0x18, 0x0A, 0x0A, 0x85, 0x07, 0x68, 0x18, 0x65, 0x07, 0x85, 323 | 0x07, 0x68, 0x85, 0x06, 0x60, 0x00, 0x20, 0x80, 0x20, 0x00, 0x21, 0x80, 0x21, 0x00, 0x22, 0x80, 324 | 0x22, 0x00, 0x23, 0x80, 0x23, 0x28, 0x20, 0xA8, 0x20, 0x28, 0x21, 0xA8, 0x21, 0x28, 0x22, 0xA8, 325 | 0x22, 0x28, 0x23, 0xA8, 0x23, 0x50, 0x20, 0xD0, 0x20, 0x50, 0x21, 0xD0, 0x21, 0xA2, 0x00, 0xBD, 326 | 0xB0, 0xB0, 0xF0, 0x09, 0x09, 0x80, 0x20, 0xF0, 0xFD, 0xE8, 0x4C, 0x9F, 0xB0, 0x4C, 0xAD, 0xB0, 327 | 0x0A, 0x46, 0x4C, 0x55, 0x58, 0x2D, 0x49, 0x4D, 0x41, 0x47, 0x45, 0x20, 0x54, 0x48, 0x49, 0x53, 328 | 0x20, 0x44, 0x49, 0x53, 0x4B, 0x20, 0x46, 0x4F, 0x52, 0x20, 0x41, 0x20, 0x53, 0x55, 0x52, 0x50, 329 | 0x52, 0x49, 0x53, 0x45, 0x0D, 0x3D, 0x29, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 330 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 331 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x5A 332 | }; 333 | --------------------------------------------------------------------------------