From 09b867c8797c15fd689bfca19ec0f86570b5da08 Mon Sep 17 00:00:00 2001 From: Zachary Hall Date: Fri, 17 Nov 2023 12:33:33 -0800 Subject: [PATCH] Initial commit. --- .gitignore | 4 + .vscode/c_cpp_properties.json | 24 +++ .vscode/settings.json | 16 ++ LICENSE | 16 ++ Makefile | 345 ++++++++++++++++++++++++++++++++++ README.md | 17 ++ label.mk | 1 + src/debug.h | 6 + src/main.c | 284 ++++++++++++++++++++++++++++ src/vera.c | 51 +++++ src/vera.h | 10 + 11 files changed, 774 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 label.mk create mode 100644 src/debug.h create mode 100644 src/main.c create mode 100644 src/vera.c create mode 100644 src/vera.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71dfc23 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.cx16 +*.lbl +*.B16 +obj/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c41faff --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,24 @@ +{ + "configurations": [ + { + "name": "CC65", + "includePath": [ + "${workspaceFolder}/**", + "/usr/share/cc65/include" + ], + "defines": [ + "__CX16__", + "__fastcall__=", + "fastcall=", + "__asm__(...)=", + "asm(...)=", + "cdecl=", + "__cdecl__=" + ], + "cStandard": "c99", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4849637 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.associations": { + "cx16.h": "c", + "stdbool.h": "c", + "stdint.h": "c", + "conio.h": "c", + "stdio.h": "c", + "string.h": "c", + "mouse.h": "c", + "stdlib.h": "c", + "cbm.h": "c", + "vera.h": "c" + }, + "C_Cpp.default.compilerPath": "", + "C_Cpp.intelliSenseEngine": "default" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b0dde06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2023 Zachary Hall + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f54b6f --- /dev/null +++ b/Makefile @@ -0,0 +1,345 @@ +############################################################################### +### Generic Makefile for cc65 projects - full version with abstract options ### +### V1.3.0(w) 2010 - 2013 Oliver Schmidt & Patryk "Silver Dream !" Ɓogiewa ### +############################################################################### + +############################################################################### +### In order to override defaults - values can be assigned to the variables ### +############################################################################### + +# Space or comma separated list of cc65 supported target platforms to build for. +# Default: c64 (lowercase!) +TARGETS := cx16 + +# Name of the final, single-file executable. +# Default: name of the current dir with target name appended +PROGRAM := b16view + +# Path(s) to additional libraries required for linking the program +# Use only if you don't want to place copies of the libraries in SRCDIR +# Default: none +LIBS := + +# Custom linker configuration file +# Use only if you don't want to place it in SRCDIR +# Default: none +CONFIG := + +# Additional C compiler flags and options. +# Default: none +CFLAGS = + +# Additional assembler flags and options. +# Default: none +ASFLAGS = + +# Additional linker flags and options. +# Default: none +LDFLAGS = + +# Path to the directory containing C and ASM sources. +# Default: src +SRCDIR := + +# Path to the directory where object files are to be stored (inside respective target subdirectories). +# Default: obj +OBJDIR := + +# Command used to run the emulator. +# Default: depending on target platform. For default (c64) target: x64 -kernal kernal -VICIIdsize -autoload +EMUCMD := + +# Optional commands used before starting the emulation process, and after finishing it. +# Default: none +# Examples +#PREEMUCMD := osascript -e "tell application \"System Events\" to set isRunning to (name of processes) contains \"X11.bin\"" -e "if isRunning is true then tell application \"X11\" to activate" +#PREEMUCMD := osascript -e "tell application \"X11\" to activate" +#POSTEMUCMD := osascript -e "tell application \"System Events\" to tell process \"X11\" to set visible to false" +#POSTEMUCMD := osascript -e "tell application \"Terminal\" to activate" +PREEMUCMD := +POSTEMUCMD := + +# On Windows machines VICE emulators may not be available in the PATH by default. +# In such case, please set the variable below to point to directory containing +# VICE emulators. +#VICE_HOME := "C:\Program Files\WinVICE-2.2-x86\" +VICE_HOME := + +# Options state file name. You should not need to change this, but for those +# rare cases when you feel you really need to name it differently - here you are +STATEFILE := Makefile.options +################################################################################### +#### DO NOT EDIT BELOW THIS LINE, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING! #### +################################################################################### + +################################################################################### +### Mapping abstract options to the actual compiler, assembler and linker flags ### +### Predefined compiler, assembler and linker flags, used with abstract options ### +### valid for 2.14.x. Consult the documentation of your cc65 version before use ### +################################################################################### + +# Compiler flags used to tell the compiler to optimise for SPEED +define _optspeed_ + CFLAGS += -Oris +endef + +# Compiler flags used to tell the compiler to optimise for SIZE +define _optsize_ + CFLAGS += -Or +endef + +# Compiler and assembler flags for generating listings +define _listing_ + CFLAGS += --listing $$(@:.o=.lst) + ASFLAGS += --listing $$(@:.o=.lst) + REMOVES += $(addsuffix .lst,$(basename $(OBJECTS))) +endef + +# Linker flags for generating map file +define _mapfile_ + LDFLAGS += --mapfile $$@.map + REMOVES += $(PROGRAM).map +endef + +# Linker flags for generating VICE label file +define _labelfile_ + LDFLAGS += -Ln $$@.lbl + REMOVES += $(PROGRAM).lbl +endef + +# Linker flags for generating a debug file +define _debugfile_ + LDFLAGS += -Wl --dbgfile,$$@.dbg + REMOVES += $(PROGRAM).dbg +endef + +############################################################################### +### Defaults to be used if nothing defined in the editable sections above ### +############################################################################### + +# Presume the C64 target like the cl65 compile & link utility does. +# Set TARGETS to override. +ifeq ($(TARGETS),) + TARGETS := c64 +endif + +# Presume we're in a project directory so name the program like the current +# directory. Set PROGRAM to override. +ifeq ($(PROGRAM),) + PROGRAM := $(notdir $(CURDIR)) +endif + +# Presume the C and asm source files to be located in the subdirectory 'src'. +# Set SRCDIR to override. +ifeq ($(SRCDIR),) + SRCDIR := src +endif + +# Presume the object and dependency files to be located in the subdirectory +# 'obj' (which will be created). Set OBJDIR to override. +ifeq ($(OBJDIR),) + OBJDIR := obj +endif +TARGETOBJDIR := $(OBJDIR)/$(TARGETS) + +# Default emulator commands and options for particular targets. +# Set EMUCMD to override. +c64_EMUCMD := $(VICE_HOME)x64 -kernal kernal -VICIIdsize -autoload +c128_EMUCMD := $(VICE_HOME)x128 -kernal kernal -VICIIdsize -autoload +vic20_EMUCMD := $(VICE_HOME)xvic -kernal kernal -VICdsize -autoload +pet_EMUCMD := $(VICE_HOME)xpet -Crtcdsize -autoload +plus4_EMUCMD := $(VICE_HOME)xplus4 -TEDdsize -autoload +# So far there is no x16 emulator in VICE (why??) so we have to use xplus4 with -memsize option +c16_EMUCMD := $(VICE_HOME)xplus4 -ramsize 16 -TEDdsize -autoload +cbm510_EMUCMD := $(VICE_HOME)xcbm2 -model 510 -VICIIdsize -autoload +cbm610_EMUCMD := $(VICE_HOME)xcbm2 -model 610 -Crtcdsize -autoload +atari_EMUCMD := atari800 -windowed -xl -pal -nopatchall -run +cx16_EMUCMD := x16emu -fsroot . -debug -run -prg +box16_EMUCMD := box16 -hypercall_path . -sym $(PROGRAM).cx16.lbl -run -prg +emu_prog := +ifeq ($(emu_prog),box16) + cx16_EMUCMD = $(box16_EMUCMD) +endif +ifeq ($(EMUCMD),) + EMUCMD = $($(CC65TARGET)_EMUCMD) +endif + +############################################################################### +### The magic begins ### +############################################################################### + +# The "Native Win32" GNU Make contains quite some workarounds to get along with +# cmd.exe as shell. However it does not provide means to determine that it does +# actually activate those workarounds. Especially $(SHELL) does NOT contain the +# value 'cmd.exe'. So the usual way to determine if cmd.exe is being used is to +# execute the command 'echo' without any parameters. Only cmd.exe will return a +# non-empty string - saying 'ECHO is on/off'. +# +# Many "Native Win32" programs accept '/' as directory delimiter just fine. How- +# ever the internal commands of cmd.exe generally require '\' to be used. +# +# cmd.exe has an internal command 'mkdir' that doesn't understand nor require a +# '-p' to create parent directories as needed. +# +# cmd.exe has an internal command 'del' that reports a syntax error if executed +# without any file so make sure to call it only if there's an actual argument. +ifeq ($(shell echo),) + MKDIR = mkdir -p $1 + RMDIR = rmdir $1 + RMFILES = $(RM) $1 +else + MKDIR = mkdir $(subst /,\,$1) + RMDIR = rmdir $(subst /,\,$1) + RMFILES = $(if $1,del /f $(subst /,\,$1)) +endif +COMMA := , +SPACE := $(N/A) $(N/A) +define NEWLINE + + +endef +# Note: Do not remove any of the two empty lines above ! + +TARGETLIST := $(subst $(COMMA),$(SPACE),$(TARGETS)) + +ifeq ($(words $(TARGETLIST)),1) + +# Set PROGRAM to something like 'myprog.c64'. +override PROGRAM := $(PROGRAM).$(TARGETLIST) + +# Set SOURCES to something like 'src/foo.c src/bar.s'. +# Use of assembler files with names ending differently than .s is deprecated! +SOURCES := $(wildcard $(SRCDIR)/*.c) +SOURCES += $(wildcard $(SRCDIR)/*.s) +SOURCES += $(wildcard $(SRCDIR)/*.asm) +SOURCES += $(wildcard $(SRCDIR)/*.a65) + +# Add to SOURCES something like 'src/c64/me.c src/c64/too.s'. +# Use of assembler files with names ending differently than .s is deprecated! +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.c) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.s) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.asm) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.a65) + +# Set OBJECTS to something like 'obj/c64/foo.o obj/c64/bar.o'. +OBJECTS := $(addsuffix .o,$(basename $(addprefix $(TARGETOBJDIR)/,$(notdir $(SOURCES))))) + +# Set DEPENDS to something like 'obj/c64/foo.d obj/c64/bar.d'. +DEPENDS := $(OBJECTS:.o=.d) + +# Add to LIBS something like 'src/foo.lib src/c64/bar.lib'. +LIBS += $(wildcard $(SRCDIR)/*.lib) +LIBS += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.lib) + +# Add to CONFIG something like 'src/c64/bar.cfg src/foo.cfg'. +CONFIG += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.cfg) +CONFIG += $(wildcard $(SRCDIR)/*.cfg) + +# Select CONFIG file to use. Target specific configs have higher priority. +ifneq ($(word 2,$(CONFIG)),) + CONFIG := $(firstword $(CONFIG)) + $(info Using config file $(CONFIG) for linking) +endif + +.SUFFIXES: +.PHONY: all test clean zap love + +all: $(PROGRAM) + +-include $(DEPENDS) +-include $(STATEFILE) + +# If OPTIONS are given on the command line then save them to STATEFILE +# if (and only if) they have actually changed. But if OPTIONS are not +# given on the command line then load them from STATEFILE. Have object +# files depend on STATEFILE only if it actually exists. +ifeq ($(origin OPTIONS),command line) + ifneq ($(OPTIONS),$(_OPTIONS_)) + ifeq ($(OPTIONS),) + $(info Removing OPTIONS) + $(shell $(RM) $(STATEFILE)) + $(eval $(STATEFILE):) + else + $(info Saving OPTIONS=$(OPTIONS)) + $(shell echo _OPTIONS_=$(OPTIONS) > $(STATEFILE)) + endif + $(eval $(OBJECTS): $(STATEFILE)) + endif +else + ifeq ($(origin _OPTIONS_),file) + $(info Using saved OPTIONS=$(_OPTIONS_)) + OPTIONS = $(_OPTIONS_) + $(eval $(OBJECTS): $(STATEFILE)) + endif +endif + +# Transform the abstract OPTIONS to the actual cc65 options. +$(foreach o,$(subst $(COMMA),$(SPACE),$(OPTIONS)),$(eval $(_$o_))) + +# Strip potential variant suffix from the actual cc65 target. +CC65TARGET := $(firstword $(subst .,$(SPACE),$(TARGETLIST))) + +# The remaining targets. +$(TARGETOBJDIR): + $(call MKDIR,$@) +$(CONFIG): + +vpath %.c $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.c | $(TARGETOBJDIR) + cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(CFLAGS) -o $@ $< + +vpath %.s $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.s | $(TARGETOBJDIR) + cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +vpath %.asm $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.asm | $(TARGETOBJDIR) + cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +vpath %.a65 $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.a65 | $(TARGETOBJDIR) + cl65 -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +vpath %.png $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(PROGRAM): $(CONFIG) $(OBJECTS) $(LIBS) + cl65 -t $(CC65TARGET) $(LDFLAGS) -o $@ $(patsubst %.cfg,-C %.cfg,$^) + +test: $(PROGRAM) + $(PREEMUCMD) + $(EMUCMD) $< + $(POSTEMUCMD) + +clean: + $(call RMFILES,$(OBJECTS)) + $(call RMFILES,$(DEPENDS)) + $(call RMFILES,$(REMOVES)) + $(call RMFILES,$(PROGRAM)) + +else # $(words $(TARGETLIST)),1 + +all test clean: + $(foreach t,$(TARGETLIST),$(MAKE) TARGETS=$t $@$(NEWLINE)) + +endif # $(words $(TARGETLIST)),1 + +OBJDIRLIST := $(wildcard $(OBJDIR)/*) + +zap: + $(foreach o,$(OBJDIRLIST),-$(call RMFILES,$o/*.o $o/*.d $o/*.lst)$(NEWLINE)) + $(foreach o,$(OBJDIRLIST),-$(call RMDIR,$o)$(NEWLINE)) + -$(call RMDIR,$(OBJDIR)) + -$(call RMFILES,$(basename $(PROGRAM)).* $(STATEFILE)) + +love: + @echo "Not war, eh?" + +################################################################### +### Place your additional targets in the additional Makefiles ### +### in the same directory - their names have to end with ".mk"! ### +################################################################### +-include *.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000..74c0def --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# B16Viewer +A viewer for the Commander X16 using the B16/BMX bitmap format being developed for it + +## Building +This program requires CC65 in your PATH to build + +Steps: +1. Clone the repo. For example: ``git clone https://github.com/catmeow72/b16view.git b16view`` +2. cd into the repo. For example: ``cd b16view`` +3. Run ``make`` + +## Usage +To run the program in the official Commander X16 emulator run ``make test`` + +To run the program in Box16, run ``make test emu_prog=box16`` + +The program will ask you what file to use, and any compatible B16 file in the directory of the Makefile should work. Just wait for it to load the file and upload it to the emulated VERA, and it should display the bitmap image. \ No newline at end of file diff --git a/label.mk b/label.mk new file mode 100644 index 0000000..89e99e5 --- /dev/null +++ b/label.mk @@ -0,0 +1 @@ +$(eval $(call _labelfile_,$(PROGRAM))) \ No newline at end of file diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..211d2bf --- /dev/null +++ b/src/debug.h @@ -0,0 +1,6 @@ +#ifndef DEBUG_H +#define DEBUG_H +#define DEBUG_PORT1 (*(volatile char*)0x9FB9) +#define DEBUG_PORT2 (*(volatile char*)0x9FBA) +#define DEBUG_CHAR (*(volatile char*)0x9FBB) +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..41f3992 --- /dev/null +++ b/src/main.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include "vera.h" +#include "debug.h" +uint8_t palette[512]; +uint8_t *image = BANK_RAM; +uint8_t *image_end = BANK_RAM; +uint8_t ram_bank_begin = 1; +uint8_t ram_bank_end = 1; +uint8_t vera_bit_depth; +uint8_t bitdepth; +uint16_t width, height; +uint32_t imgdatabytes; +uint8_t pixels_per_byte; +uint8_t filleridx = 255; +bool over320; +uint8_t oldhscale, oldvscale; +uint8_t significant_palette_entries; +uint8_t significant_palette_start; +uint16_t read16() { + cbm_k_chkin(2); + return ((cbm_k_chrin())) | ((uint16_t)cbm_k_chrin() << 8); +} +char readchar() { + cbm_k_chkin(2); + return cbm_k_chrin(); +} +uint8_t read8() { + cbm_k_chkin(2); + return cbm_k_getin(); +} +bool checkheader() { + char header[4] = {0x42, 0x4D, 0x58, 0}; // X16BM + char tested[4] = {0, 0, 0, 0}; + uint8_t i = 0; + bool valid = true; + char chr = 0; + printf("Reading magic bytes...\n"); + for (; i < 3; i++) { + chr = readchar(); + tested[i] = chr; + printf("%2x%s", chr, i >= 4 ? "\n" : ", "); + if (chr != header[i]) { + valid = false; + } + } + for (i = 0; i < 3; i++) { + if (tested[i] < 0x20 || tested[i] >= 0x7F) { + tested[i] = '.'; + } + } + printf("%s%c=%s\n", tested, valid ? '=' : '!', header); + return valid; +} +void change_bank(uint8_t bank) { + if (RAM_BANK != bank) { + printf("Switching RAM bank to %u\n", bank); + RAM_BANK = bank; + } +} +int readfile(const char *filename) { + uint16_t i; + uint16_t i_max; + uint8_t banki; + uint8_t j; + uint8_t *ptr; + uint8_t version; + bool all_significant; + cbm_k_setnam(filename); + cbm_k_setlfs(2, 8, 2); + cbm_k_open(); + if (cbm_k_readst()) { + printf("Error opening file.\n"); + return 1; + } + if (!checkheader()) { + printf("Invalid file!\n"); + return 1; + } + version = read8(); + bitdepth = read8(); + if (bitdepth == 0) { + printf("Error: bitdepth was 0.\n"); + return 1; + } + vera_bit_depth = read8(); + if (vera_bit_depth > 3) { + printf("Error: VERA bit depth was invalid!\n"); + } + pixels_per_byte = 8 / bitdepth; + printf("Bit depth: %u => %u pixels per byte\n", bitdepth, pixels_per_byte); + width = read16(); + height = read16(); + printf("Width: %u, height: %u\n", width, height); + over320 = width > 320 || height > 240; + filleridx = read8(); + printf("Border color: %02x\n", filleridx); + imgdatabytes = ((uint32_t)width * (uint32_t)height) / (uint32_t)pixels_per_byte; + printf("Bytes: %lu\n", imgdatabytes); + significant_palette_entries = read8(); + significant_palette_start = read8(); + if (significant_palette_entries == 0) { + printf("All palette entries significant.\n"); + all_significant = true; + significant_palette_start = 0; + } else { + printf("%u palette entries starting at %u significant.\n", significant_palette_entries, significant_palette_start); + } + printf("Skipping reserved bytes...\n"); + for (i = 0; i < 19; i++) { + read8(); + } + printf("Reading palette entries...\n"); + for (i = 0; i < 512; i++) { + j = i >> 1; + if (all_significant || j >= significant_palette_start && j <= significant_palette_entries) { + palette[i] = read8(); + } else { + read8(); + palette[i] = get_from_backed_up_palette(i); + } + } + image_end = (imgdatabytes % 8192) + image; + ram_bank_end = (imgdatabytes / 8192) + 1; + printf("Image: (bank %u, addr %u) => (bank %u, addr %u)\n", ram_bank_begin, image - BANK_RAM, ram_bank_end, image_end - BANK_RAM); + for (banki = ram_bank_begin; banki <= ram_bank_end; banki++) { + change_bank(banki); + i_max = (banki == ram_bank_end) ? (uint16_t)(image_end - image) : 8192; + printf("Writing %u bytes to bank %u\n", i_max, banki); + for (i = 0; i < i_max; i++) { + ptr = (i % 8192) + image; + *ptr = read8(); + } + } + cbm_k_clrch(); + cbm_k_close(2); + return 0; +} +void updatescale() { + uint8_t hscale; + uint8_t vscale; + VERA.control &= 0b10000001; + hscale = VERA.display.hscale; + vscale = VERA.display.vscale; + oldhscale = hscale; + oldvscale = vscale; + if (over320) return; + if (hscale > 64) VERA.display.hscale = 64; + if (vscale > 64) VERA.display.vscale = 64; +} +void restorescale() { + VERA.control &= 0b10000001; + VERA.display.hscale = oldhscale; + VERA.display.vscale = oldvscale; +} +void uploadimage() { + size_t i = 0, j = 0, x = 0, y = 0, value = 0, bitmask = 0; + size_t vera_w = 320, vera_h = 240; + uint16_t hiram_i = 0; + uint8_t hiram_bank = ram_bank_begin; + uint16_t vera_max = 0; + uint32_t tmp = 0; + bool vera_max_bank = 1; + uint8_t config = 0b00000100 | vera_bit_depth; + if (over320) { + vera_w *= 2; + vera_h *= 2; + } + tmp = ((uint32_t)vera_w * (uint32_t)vera_h)/(uint32_t)pixels_per_byte; + vera_max = tmp; + vera_max_bank = tmp >> 16; + VERA.layer0.config = config; + VERA.layer0.tilebase = over320 ? 1 : 0; + printf("Loading image bytes: $%lx\n", imgdatabytes); + printf("Image width: %u, image height: %u\npixels per byte: %u\n", width, height, pixels_per_byte); + VERA.control &= ~0b1; + VERA.address = 0; + VERA.address_hi = 0b00010000; + RAM_BANK = hiram_bank; + for (x = 0; VERA.address < vera_max || (vera_max_bank & 0b1) != (VERA.address_hi & 0b1); x+=pixels_per_byte) { + if (x >= vera_w) { + x -= vera_w; + y++; + } + if (x < width && y < height) { + VERA.data0 = image[hiram_i]; + hiram_i++; + if (hiram_i >= 8192) { + hiram_i -= 8192; + hiram_bank++; + RAM_BANK = hiram_bank; + } + } else { + value = 0; + bitmask = (1 << bitdepth) - 1; + for (i = 0; i < pixels_per_byte; i++) { + value |= (filleridx & bitmask) << (i * bitdepth); + } + VERA.data0 = value; + } + } + upload_palette(palette); + vera_layer_enable(0b11); + return; +} +void set_text_color(uint8_t color) { + uint16_t mw, mh; + uint16_t base; + size_t i; + base = VERA.layer1.mapbase; + VERA.control &= 0b10000001; + VERA.address = (base << 9) + 1; + VERA.address_hi = 0b00100000 | (base >> 7) & 0b1; + mw = (VERA.layer1.config >> 4) & 0b11; + mh = (VERA.layer1.config >> 6) & 0b11; + mw = 32 << mw; + mh = 32 << mh; + for (i = 0; i < mw * mh; i++) { + VERA.data0 = color; + } +} +int main(int argc, char **argv) { + char *buf; + int ret; + bool done, text_visible; + char ch; + uint8_t coloridx = 1; + size_t i; + backup_palette(); + buf = (char*)malloc(81); + printf("Enter viewable file name: "); + fgets(buf, 81, stdin); + for (i = 0; i < 81; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + clrscr(); + gotoxy(0, 0); + printf("Loading file...\n"); + ret = readfile(buf); + if (ret != 0) return ret; + clrscr(); + gotoxy(0, 0); + printf("File loaded. Uploading to VERA.\n"); + updatescale(); + uploadimage(); + set_text_256color(true); + printf("Press arrow keys to change text colors."); + gotoxy(0, 0); + printf("[ESC] to exit, [SPACE] to toggle text."); + coloridx = significant_palette_start - 1; + set_text_color(coloridx); + done = false; + text_visible = true; + while (!done) { + while (kbhit()) { + ch = cgetc(); + if (ch == CH_ESC) { + done = true; + break; + } else if (ch == ' ') { + text_visible = !text_visible; + vera_layer_enable(0b1 | (text_visible ? 0b10 : 0)); + } else if (ch == CH_CURS_LEFT) { + set_text_color(--coloridx); + } else if (ch == CH_CURS_RIGHT) { + set_text_color(++coloridx); + } + } + } + vera_layer_enable(0b10); + set_text_256color(false); + restorescale(); + clrscr(); + restore_palette(); + return 0; +} \ No newline at end of file diff --git a/src/vera.c b/src/vera.c new file mode 100644 index 0000000..8db6d23 --- /dev/null +++ b/src/vera.c @@ -0,0 +1,51 @@ +#include "vera.h" +#include +#include +uint8_t palette_backup[512]; +bool palette_backed_up = false; +void backup_palette() { + uint16_t i; + VERA.control &= ~0b1; + VERA.address_hi = 0b00010001; + VERA.address = 0xFA00; + for (i = 0; i < 512; i++) { + palette_backup[i] = VERA.data0; + } + palette_backed_up = true; +} +void upload_palette(uint8_t *ptr) { + uint16_t i; + VERA.control &= ~0b1; + VERA.address_hi = 0b00010001; + VERA.address = 0xFA00; + for (i = 0; i < 512; i++) { + VERA.data0 = ptr[i]; + } +} +void restore_palette() { + uint16_t i; + VERA.control &= ~0b1; + VERA.address_hi = 0b00010001; + VERA.address = 0xFA00; + for (i = 0; i < 512; i++) { + VERA.data0 = palette_backup[i]; + } +} +void set_text_256color(bool enabled) { + uint8_t config = VERA.layer1.config; + if (enabled) { + config |= (1 << 3); + } else { + config &= ~(1 << 3); + } + VERA.layer1.config = config; +} +uint8_t get_from_backed_up_palette(uint16_t idx) { + if (!palette_backed_up) { + backup_palette(); + } + if (idx >= 512) { + return 0; + } + return palette_backup[idx]; +} \ No newline at end of file diff --git a/src/vera.h b/src/vera.h new file mode 100644 index 0000000..93e57d6 --- /dev/null +++ b/src/vera.h @@ -0,0 +1,10 @@ +#ifndef VERA_H +#define VERA_H +#include +#include +void upload_palette(uint8_t *ptr); +void backup_palette(); +void restore_palette(); +uint8_t get_from_backed_up_palette(uint16_t idx); +void set_text_256color(bool enabled); +#endif \ No newline at end of file