mirror of
https://github.com/buserror/mii_emu.git
synced 2024-11-23 22:31:52 +00:00
Initial Commit
Cleaned up for release at last! Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
parent
936f37728f
commit
f7a56ebc01
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.bash_history
|
||||
build-*
|
||||
.vscode
|
||||
compile_commands.json
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Michel Pollet
|
||||
Copyright (c) 2023 Michel Pollet <buserror+git@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
105
Makefile
Normal file
105
Makefile
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
# GCC is default -- simply because it's faster!
|
||||
CC = gcc
|
||||
SHELL = /bin/bash
|
||||
# This is where (g)make looks for the source files for implicit rules
|
||||
VPATH := src src/format src/drivers nuklear contrib
|
||||
|
||||
CPPFLAGS += -Isrc -Isrc/format -Isrc/roms -Isrc/drivers
|
||||
CPPFLAGS += -Icontrib -Inuklear
|
||||
CPPFLAGS += -Ilibmish/src
|
||||
CFLAGS += --std=gnu99 -Wall -Wextra -O2 -g
|
||||
CFLAGS += -Wno-unused-parameter -Wno-unused-function
|
||||
LDLIBS += -lX11 -lm -lGL -lGLU
|
||||
|
||||
HAS_ALSA := $(shell pkg-config --exists alsa && echo 1)
|
||||
ifeq ($(HAS_ALSA),1)
|
||||
LDLIBS += $(shell pkg-config --libs alsa)
|
||||
CPPFLAGS += $(shell pkg-config --cflags alsa) -DHAS_ALSA
|
||||
else
|
||||
${warning ALSA not found, no sound support}
|
||||
endif
|
||||
|
||||
O := build-$(shell $(CC) -dumpmachine)
|
||||
BIN := $(O)/bin
|
||||
LIB := $(O)/lib
|
||||
OBJ := $(O)/obj
|
||||
|
||||
all : $(BIN)/mii_emu
|
||||
|
||||
MII_SRC := $(wildcard src/*.c src/format/*.c \
|
||||
src/drivers/*.c contrib/*.c)
|
||||
UI_SRC := $(wildcard nuklear/*.c)
|
||||
SRC := $(MII_SRC) $(UI_SRC)
|
||||
ALL_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${SRC:.c=.o}}}
|
||||
|
||||
$(BIN)/mii_emu : $(ALL_OBJ)
|
||||
$(BIN)/mii_emu : $(LIB)/libmish.a
|
||||
|
||||
libmish : $(LIB)/libmish.a
|
||||
LDLIBS += $(LIB)/libmish.a
|
||||
$(LIB)/libmish.a : | $(LIB) $(OBJ) $(BIN)
|
||||
make -j -C libmish O=$(PWD)
|
||||
|
||||
# Smartport firmware needs the assembler first
|
||||
test/asm/%.bin : test/asm/%.asm | $(BIN)/mii_asm
|
||||
$(BIN)/mii_asm -v -o $@ $<
|
||||
# And it also INCBIN the firmware driver
|
||||
$(OBJ)/mii_smarport.o : test/asm/mii_smartport_driver.bin
|
||||
|
||||
$(OBJ)/libsofd.o : CPPFLAGS += -DHAVE_X11
|
||||
|
||||
clean :
|
||||
rm -rf $(O)
|
||||
|
||||
# This is for development purpose. This will recompile the project
|
||||
# everytime a file is modified.
|
||||
watch :
|
||||
while true; do \
|
||||
clear; $(MAKE) -j all tests; \
|
||||
inotifywait -qre close_write src src/format nuklear test; \
|
||||
done
|
||||
|
||||
tests : $(BIN)/mii_test $(BIN)/mii_cpu_test $(BIN)/mii_asm
|
||||
|
||||
# Just the library for mii, not any of the UI stuff
|
||||
TEST_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${MII_SRC:.c=.o}}}
|
||||
VPATH += test
|
||||
# Base test without the UI, for performance testing
|
||||
$(BIN)/mii_test : $(TEST_OBJ)
|
||||
$(BIN)/mii_test : $(OBJ)/mii_test.o $(OBJ)/mii_mish.o
|
||||
$(OBJ)/mii_test.o : CFLAGS += -O0 -Og
|
||||
|
||||
$(OBJ)/mii_cpu_test.o : CFLAGS += -O0 -Og
|
||||
$(BIN)/mii_cpu_test : $(OBJ)/mii_cpu_test.o $(TEST_OBJ)
|
||||
|
||||
$(BIN)/mii_asm : $(OBJ)/mii_asm.o $(TEST_OBJ)
|
||||
|
||||
ifeq ($(V),1)
|
||||
Q :=
|
||||
else
|
||||
Q := @
|
||||
endif
|
||||
|
||||
$(OBJ)/%.o : %.c | $(OBJ)
|
||||
@echo " CC $<"
|
||||
$(Q)$(CC) -MMD $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(BIN)/% : | $(BIN)
|
||||
@echo " LD $@"
|
||||
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
||||
|
||||
$(OBJ) $(BIN) $(LIB) :
|
||||
@mkdir -p $@
|
||||
|
||||
# Generates the necessary file to help clangd index the files properly.
|
||||
# This currently has to be done manually, but helps a lot if you use 'kate'
|
||||
# editor or anthing else that is compatible with the LSP protocol
|
||||
compile_commands.json: lsp
|
||||
lsp:
|
||||
{ $$(which gmake) CC=gcc V=1 --always-make --dry-run all tests; \
|
||||
$$(which gmake) CC=gcc V=1 --always-make --dry-run -C libmish ; } | \
|
||||
sh utils/clangd_gen.sh >compile_commands.json
|
||||
|
||||
-include $(O)/*.d
|
||||
-include $(O)/obj/*.d
|
152
README.md
Normal file
152
README.md
Normal file
@ -0,0 +1,152 @@
|
||||
# MII Apple //e Emulator
|
||||
|
||||
I know there are many out there, but none of them were ticking my fancy, so I decide to write my own. To start with it was "How hard can it be really?" then it snowballed as more and more things were fixed & added. It's been shelved for a while because well, it lacked documentation, headers, licence and stuff, so I spent some time cleaning it up for release.
|
||||
|
||||
One primary reason for this project was that linapple (or -pie) codebase is really horrible. It dates back from 2000's or before, with loads of Windows crud leftover, some SDL crud added, the audio just doesn't really work, and overall if you want to hack around the codebase, it's pretty dreadful.
|
||||
|
||||
|
||||
![Monochrome Double-Hi res](docs/screen_main.png)
|
||||
*Double hires in monochrome*
|
||||
|
||||
I wanted something:
|
||||
|
||||
* Modern, Clean code, modular -- in the spirit of simavr, usable as a library.
|
||||
* Made for linux. In C. With a Makefile. None of the trendy bloatware.
|
||||
* Small, Fast.
|
||||
* Minimal dependencies.
|
||||
* No gigantic config file.
|
||||
* I didn't need II+ or unenhanced IIe, just 65c02 //e.
|
||||
|
||||
|
||||
![Glorious NTSC colors](docs/screen_color.png)
|
||||
*Double hires in color*
|
||||
|
||||
## What can it do?
|
||||
* 65c02 //e with 128K of ram.
|
||||
* Support all known graphic modes:
|
||||
* Double-hires in mono and color, with automatic switch
|
||||
* All the other modes in color/mono
|
||||
* Color, Green, Amber rendering
|
||||
* Simulated 'scanlines' to make it look vintage
|
||||
* Speaker audio. ALSA For playback.
|
||||
* Adds a small 'attack' filter when playing back to soften the often annoying 'click' of typical audio effects from the apple II.
|
||||
* Mouse Card -- mouse isn't captured like in some other emulators.
|
||||
* No Slot Clock
|
||||
* Smartport DMA 'hard drive' card
|
||||
* "Titan Accelerator //e" simulation, to turn on/off fast mode.
|
||||
* Terence's J Boldt [1MB ROM card](https://github.com/tjboldt/ProDOS-ROM-Drive), also because I own a couple!
|
||||
* Floppy Drive [more on that later]
|
||||
* No dependencies (X11) OpenGL rendering, using Nuklear backend for UI
|
||||
|
||||
![Phosphorescent Green](docs/screen_green.png)
|
||||
*Good old green monitor style. Theres Amber too.*
|
||||
|
||||
## How to I compile it and run it?
|
||||
* You need a C compiler, make, and a few libraries:
|
||||
* libasound2-dev [ optional, for audio ]
|
||||
* libgl-dev
|
||||
* libglu-dev
|
||||
* libx11-dev
|
||||
* Many of them will probably be installed already.
|
||||
* For more details on development, see [Compiling](docs/Compiling.md)
|
||||
* Then just type `make` and it should compile.
|
||||
* To run it, just type `build-x86_64-linux-gnu/bin/bin/mii_emu` and it should start.
|
||||
* `mii_emu --help` will display:
|
||||
|
||||
Usage: ./build-x86_64-linux-gnu/bin/mii_emu [options]
|
||||
Options:
|
||||
-h, --help This help
|
||||
-v, --verbose Verbose output
|
||||
-m, --mute Mute the speaker
|
||||
-vol, --volume <volume> Set speaker volume (0.0 to 10.0)
|
||||
-speed, --speed <speed> Set the CPU speed in MHz
|
||||
-s, --slot <slot>:<driver> Specify a slot and driver
|
||||
Slot id is 1..7
|
||||
-d, --drive <slot>:<drive>:<filename> Specify a drive
|
||||
Slot id is 1..7, drive is 1..2
|
||||
Alternate syntax: <slot>:<drive> <filename>
|
||||
-L, --list-drivers List available drivers
|
||||
-def, --default Set default drives:
|
||||
Slot 4: mouse
|
||||
Slot 6: disk2
|
||||
Slot 7: smartport
|
||||
-nsc[=0|1] Enable/Disable No Slot Clock:
|
||||
0: disable
|
||||
1: enable [Enabled by default]
|
||||
-titan[=0|1] Enable/Disable Titan Accelerator IIe:
|
||||
0: disable [default]
|
||||
1: enable [Start at 3.58MHz]
|
||||
And the available drivers:
|
||||
|
||||
$ ./build-x86_64-linux-gnu/bin/mii_emu -L
|
||||
mii: available drivers:
|
||||
titan - Titan Accelerator IIe
|
||||
smartport - SmartPort card
|
||||
nsc - No Slot Clock
|
||||
mouse - Mouse card
|
||||
eecard - EEPROM 1MB card
|
||||
disk2 - Apple Disk ][
|
||||
|
||||
## Key Bindings
|
||||
There are just a few keys that are mapped for anything useful.
|
||||
* **Control-F12** is Control-Reset on the IIe. (**Shift-Control-F12** is **Open Apple-Reset**)
|
||||
* **'Super'** left and **'Super'** right are **Open** and **Close Apple** keys.
|
||||
These keys are mapped to the left and right 'Windows' keys on a PC keyboard, and they might want to open the start menu (I know it's the case with Cinnamon), so you might want to disable that.
|
||||
* **F5** sets the CPU speed to 1MHz
|
||||
* **F6** to 4MHz.
|
||||
* These keys control the built-in debugger:
|
||||
* **Control-F11** Stops the emulator; see the command prompt/telnet for how to proceed, dump state, disassembly etc.
|
||||
* **Control-F10** 'steps' the emulator, ie one instruction at a time.
|
||||
* **Control-F9** is 'continue' -- resumes the emulator.
|
||||
|
||||
|
||||
![Telnet into mii_emu](docs/screen_mish.png)
|
||||
*The built-in shell, telnet into the emulator!*
|
||||
|
||||
## Anything else?
|
||||
* Well it has it's own command line shell, using my own [libmish](https://github.com/buserror/libmish) so there's loads you can do by... *telnet into* the emulator!
|
||||
* Yes, you can telnet into the running emulator!
|
||||
+ The telnet port is displayed in the window title bar.
|
||||
+ The port is 'semi random' -- it tries to find one that is derivative of the program name, but it will try several until it finds one that is free.
|
||||
* It has it's own debugger shell. You can:
|
||||
+ Add break/watch points.
|
||||
+ Step, Next (jump over JSR's), Continue, Halt
|
||||
+ Has a 16 instruction trace history when hitting a breakpoint.
|
||||
+ Dump memory map
|
||||
+ Trigger soft switches
|
||||
+ Disassemble
|
||||
* Also MII comes with it's own mini-assembler, used to compile some driver and the CPU unit tests (inspired by apple2ts, see bellow)
|
||||
* It embeds all the files it needs, ROMs, fonts etc so you don't need to install it, just run it, it'll work.
|
||||
* MII "Hard Disk" uses 'overlay' files:
|
||||
+ This writes blocks on a /separate/ (sparse) file from the main file.
|
||||
+ This "Overlay" is created automatically and allows you to keep your image files clean, unless you want to 'commit' your overlay back in.
|
||||
+ This allows you to make sure your disk images aren't corrupted when 'hard rebooting' the emulator, if you are in the process of testing/developing a program for example.
|
||||
|
||||
## What it cannot do
|
||||
* MouseCalc crashes (VBL IRQ, or a mouse mode I don't support yet)
|
||||
* A2Desktop PT3 player doesn't see keypresses.
|
||||
* Sometimes the emulator goes in 'slow mode', ie 0.2MHz. Likely the frame scheduler playing up.
|
||||
* Thats' about it really, all the other things I tried work
|
||||
|
||||
## What it could do with
|
||||
* Not sure about keeping Nuklear, it does a lot bit it's hard work customizing anything
|
||||
* Add a memory extension 'card' -- not sure why, but hey, why not.
|
||||
* Joystick support. As soon as I find a USB joystick that vaguely looks retro, I'll get one.
|
||||
* The main window is 1280x720 on purpose, so it could do Full Screen.
|
||||
* Port it to Raspbery Pi. I don't expect compiling issues, just video issues with GLes
|
||||
* Make a tool to 'flatten' overlay files back into the primary image.
|
||||
* Some sort of UI to select/eject disks.
|
||||
|
||||
|
||||
![Total Replay](docs/screen_total.png)
|
||||
*Obligatory View of [Total Replay](https://github.com/a2-4am/4cade), from legend [4am](https://github.com/a2-4am)*
|
||||
## Inspiration, Licence etc
|
||||
* MIT Licence, I think this is the most permissive, and this work is a derivative and has a lot of inpsiration from too many projects to claim anything more restrictive.
|
||||
* The CPU Emulation was inspired by a few other implementations:
|
||||
* [Chips](https://github.com/floooh/chips) -- which I used for a little while, but it was a pain to add the 65c02 instructions, and it had stuff I didn't want/need like 6510 emulation.
|
||||
* [Apple2ts](https://github.com/ct6502/apple2ts/) -- In fact I converted a few bits from there; and I got the idea for the built-in assember from there, and some of the unit tests, AND the Smartport driver emulation idea!
|
||||
* Other bits were inspired by:
|
||||
* [bobbin](https://github.com/micahcowan/bobbin) which is newish as well, and is great for text mode, but I didn't like the fact its' all globals etc. I still borrowed the Floppy emulation from there, until I get around to do one.
|
||||
* [isapple2](https://github.com/ivanizag/izapple2/) for other bits and pieces.
|
||||
* And of course, countless books, articles and posts read over the last 40 years!
|
||||
|
379
contrib/incbin.h
Normal file
379
contrib/incbin.h
Normal file
@ -0,0 +1,379 @@
|
||||
/**
|
||||
* @file incbin.h
|
||||
* @author Dale Weiler
|
||||
* @brief Utility for including binary files
|
||||
*
|
||||
* Facilities for including binary files into the current translation unit and
|
||||
* making use from them externally in other translation units.
|
||||
*/
|
||||
#ifndef INCBIN_HDR
|
||||
#define INCBIN_HDR
|
||||
#include <limits.h>
|
||||
|
||||
// Michel addition:
|
||||
// Allow the included file to have an extra zero, to include text files
|
||||
// as plain zero terminated strings
|
||||
#ifdef INCBIN_TRAILING_ZERO
|
||||
#define INCBIN_TRAIL INCBIN_BYTE "0\n"
|
||||
#else
|
||||
#define INCBIN_TRAIL
|
||||
#endif
|
||||
|
||||
#if defined(__AVX512BW__) || \
|
||||
defined(__AVX512CD__) || \
|
||||
defined(__AVX512DQ__) || \
|
||||
defined(__AVX512ER__) || \
|
||||
defined(__AVX512PF__) || \
|
||||
defined(__AVX512VL__) || \
|
||||
defined(__AVX512F__)
|
||||
# define INCBIN_ALIGNMENT_INDEX 6
|
||||
#elif defined(__AVX__) || \
|
||||
defined(__AVX2__)
|
||||
# define INCBIN_ALIGNMENT_INDEX 5
|
||||
#elif defined(__SSE__) || \
|
||||
defined(__SSE2__) || \
|
||||
defined(__SSE3__) || \
|
||||
defined(__SSSE3__) || \
|
||||
defined(__SSE4_1__) || \
|
||||
defined(__SSE4_2__) || \
|
||||
defined(__neon__)
|
||||
# define INCBIN_ALIGNMENT_INDEX 4
|
||||
#elif ULONG_MAX != 0xffffffffu
|
||||
# define INCBIN_ALIGNMENT_INDEX 3
|
||||
# else
|
||||
# define INCBIN_ALIGNMENT_INDEX 2
|
||||
#endif
|
||||
|
||||
/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
|
||||
#define INCBIN_ALIGN_SHIFT_0 1
|
||||
#define INCBIN_ALIGN_SHIFT_1 2
|
||||
#define INCBIN_ALIGN_SHIFT_2 4
|
||||
#define INCBIN_ALIGN_SHIFT_3 8
|
||||
#define INCBIN_ALIGN_SHIFT_4 16
|
||||
#define INCBIN_ALIGN_SHIFT_5 32
|
||||
#define INCBIN_ALIGN_SHIFT_6 64
|
||||
|
||||
/* Actual alignment value */
|
||||
#define INCBIN_ALIGNMENT \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
|
||||
INCBIN_ALIGNMENT_INDEX)
|
||||
|
||||
/* Stringize */
|
||||
#define INCBIN_STR(X) \
|
||||
#X
|
||||
#define INCBIN_STRINGIZE(X) \
|
||||
INCBIN_STR(X)
|
||||
/* Concatenate */
|
||||
#define INCBIN_CAT(X, Y) \
|
||||
X ## Y
|
||||
#define INCBIN_CONCATENATE(X, Y) \
|
||||
INCBIN_CAT(X, Y)
|
||||
/* Deferred macro expansion */
|
||||
#define INCBIN_EVAL(X) \
|
||||
X
|
||||
#define INCBIN_INVOKE(N, ...) \
|
||||
INCBIN_EVAL(N(__VA_ARGS__))
|
||||
|
||||
/* Green Hills uses a different directive for including binary data */
|
||||
#if defined(__ghs__)
|
||||
# if (__ghs_asm == 2)
|
||||
# define INCBIN_MACRO ".file"
|
||||
/* Or consider the ".myrawdata" entry in the ld file */
|
||||
# else
|
||||
# define INCBIN_MACRO "\tINCBIN"
|
||||
# endif
|
||||
#else
|
||||
# define INCBIN_MACRO ".incbin"
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define INCBIN_ALIGN \
|
||||
__attribute__((aligned(INCBIN_ALIGNMENT)))
|
||||
#else
|
||||
# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
|
||||
#endif
|
||||
|
||||
#if defined(__arm__) || /* GNU C and RealView */ \
|
||||
defined(__arm) || /* Diab */ \
|
||||
defined(_ARM) /* ImageCraft */
|
||||
# define INCBIN_ARM
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
/* Utilize .balign where supported */
|
||||
# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
||||
# define INCBIN_ALIGN_BYTE ".balign 1\n"
|
||||
#elif defined(INCBIN_ARM)
|
||||
/*
|
||||
* On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
|
||||
* the shift count. This is the value passed to `.align'
|
||||
*/
|
||||
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
|
||||
# define INCBIN_ALIGN_BYTE ".align 0\n"
|
||||
#else
|
||||
/* We assume other inline assembler's treat `.align' as `.balign' */
|
||||
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
||||
# define INCBIN_ALIGN_BYTE ".align 1\n"
|
||||
#endif
|
||||
|
||||
/* INCBIN_CONST is used by incbin.c generated files */
|
||||
#if defined(__cplusplus)
|
||||
# define INCBIN_EXTERNAL extern "C"
|
||||
# define INCBIN_CONST extern const
|
||||
#else
|
||||
# define INCBIN_EXTERNAL extern
|
||||
# define INCBIN_CONST const
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Optionally override the linker section into which data is emitted.
|
||||
*
|
||||
* @warning If you use this facility, you'll have to deal with platform-specific linker output
|
||||
* section naming on your own
|
||||
*
|
||||
* Overriding the default linker output section, e.g for esp8266/Arduino:
|
||||
* @code
|
||||
* #define INCBIN_OUTPUT_SECTION ".irom.text"
|
||||
* #include "incbin.h"
|
||||
* INCBIN(Foo, "foo.txt");
|
||||
* // Data is emitted into program memory that never gets copied to RAM
|
||||
* @endcode
|
||||
*/
|
||||
#if !defined(INCBIN_OUTPUT_SECTION)
|
||||
# if defined(__APPLE__)
|
||||
# define INCBIN_OUTPUT_SECTION ".const_data"
|
||||
# else
|
||||
# define INCBIN_OUTPUT_SECTION ".rodata"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
/* The directives are different for Apple branded compilers */
|
||||
# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n"
|
||||
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
||||
# define INCBIN_INT ".long "
|
||||
# define INCBIN_MANGLE "_"
|
||||
# define INCBIN_BYTE ".byte "
|
||||
# define INCBIN_TYPE(...)
|
||||
#else
|
||||
# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n"
|
||||
# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
||||
# if defined(__ghs__)
|
||||
# define INCBIN_INT ".word "
|
||||
# else
|
||||
# define INCBIN_INT ".int "
|
||||
# endif
|
||||
# if defined(__USER_LABEL_PREFIX__)
|
||||
# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
|
||||
# else
|
||||
# define INCBIN_MANGLE ""
|
||||
# endif
|
||||
# if defined(INCBIN_ARM)
|
||||
/* On arm assemblers, `@' is used as a line comment token */
|
||||
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
|
||||
# elif defined(__MINGW32__) || defined(__MINGW64__)
|
||||
/* Mingw doesn't support this directive either */
|
||||
# define INCBIN_TYPE(NAME)
|
||||
# else
|
||||
/* It's safe to use `@' on other architectures */
|
||||
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
|
||||
# endif
|
||||
# define INCBIN_BYTE ".byte "
|
||||
#endif
|
||||
|
||||
/* List of style types used for symbol names */
|
||||
#define INCBIN_STYLE_CAMEL 0
|
||||
#define INCBIN_STYLE_SNAKE 1
|
||||
|
||||
/**
|
||||
* @brief Specify the prefix to use for symbol names.
|
||||
*
|
||||
* By default this is `g', producing symbols of the form:
|
||||
* @code
|
||||
* #include "incbin.h"
|
||||
* INCBIN(Foo, "foo.txt");
|
||||
*
|
||||
* // Now you have the following symbols:
|
||||
* // const unsigned char gFooData[];
|
||||
* // const unsigned char *const gFooEnd;
|
||||
* // const unsigned int gFooSize;
|
||||
* @endcode
|
||||
*
|
||||
* If however you specify a prefix before including: e.g:
|
||||
* @code
|
||||
* #define INCBIN_PREFIX incbin
|
||||
* #include "incbin.h"
|
||||
* INCBIN(Foo, "foo.txt");
|
||||
*
|
||||
* // Now you have the following symbols instead:
|
||||
* // const unsigned char incbinFooData[];
|
||||
* // const unsigned char *const incbinFooEnd;
|
||||
* // const unsigned int incbinFooSize;
|
||||
* @endcode
|
||||
*/
|
||||
#if !defined(INCBIN_PREFIX)
|
||||
# define INCBIN_PREFIX g
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Specify the style used for symbol names.
|
||||
*
|
||||
* Possible options are
|
||||
* - INCBIN_STYLE_CAMEL "CamelCase"
|
||||
* - INCBIN_STYLE_SNAKE "snake_case"
|
||||
*
|
||||
* Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
|
||||
* @code
|
||||
* #include "incbin.h"
|
||||
* INCBIN(Foo, "foo.txt");
|
||||
*
|
||||
* // Now you have the following symbols:
|
||||
* // const unsigned char <prefix>FooData[];
|
||||
* // const unsigned char *const <prefix>FooEnd;
|
||||
* // const unsigned int <prefix>FooSize;
|
||||
* @endcode
|
||||
*
|
||||
* If however you specify a style before including: e.g:
|
||||
* @code
|
||||
* #define INCBIN_STYLE INCBIN_STYLE_SNAKE
|
||||
* #include "incbin.h"
|
||||
* INCBIN(foo, "foo.txt");
|
||||
*
|
||||
* // Now you have the following symbols:
|
||||
* // const unsigned char <prefix>foo_data[];
|
||||
* // const unsigned char *const <prefix>foo_end;
|
||||
* // const unsigned int <prefix>foo_size;
|
||||
* @endcode
|
||||
*/
|
||||
#if !defined(INCBIN_STYLE)
|
||||
# define INCBIN_STYLE INCBIN_STYLE_CAMEL
|
||||
#endif
|
||||
|
||||
/* Style lookup tables */
|
||||
#define INCBIN_STYLE_0_DATA Data
|
||||
#define INCBIN_STYLE_0_END End
|
||||
#define INCBIN_STYLE_0_SIZE Size
|
||||
#define INCBIN_STYLE_1_DATA _data
|
||||
#define INCBIN_STYLE_1_END _end
|
||||
#define INCBIN_STYLE_1_SIZE _size
|
||||
|
||||
/* Style lookup: returning identifier */
|
||||
#define INCBIN_STYLE_IDENT(TYPE) \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_STYLE_, \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_EVAL(INCBIN_STYLE), \
|
||||
INCBIN_CONCATENATE(_, TYPE)))
|
||||
|
||||
/* Style lookup: returning string literal */
|
||||
#define INCBIN_STYLE_STRING(TYPE) \
|
||||
INCBIN_STRINGIZE( \
|
||||
INCBIN_STYLE_IDENT(TYPE)) \
|
||||
|
||||
/* Generate the global labels by indirectly invoking the macro with our style
|
||||
* type and concatenating the name against them. */
|
||||
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
|
||||
INCBIN_INVOKE( \
|
||||
INCBIN_GLOBAL, \
|
||||
INCBIN_CONCATENATE( \
|
||||
NAME, \
|
||||
INCBIN_INVOKE( \
|
||||
INCBIN_STYLE_IDENT, \
|
||||
TYPE))) \
|
||||
INCBIN_INVOKE( \
|
||||
INCBIN_TYPE, \
|
||||
INCBIN_CONCATENATE( \
|
||||
NAME, \
|
||||
INCBIN_INVOKE( \
|
||||
INCBIN_STYLE_IDENT, \
|
||||
TYPE)))
|
||||
|
||||
/**
|
||||
* @brief Externally reference binary data included in another translation unit.
|
||||
*
|
||||
* Produces three external symbols that reference the binary data included in
|
||||
* another translation unit.
|
||||
*
|
||||
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
||||
* "Data", as well as "End" and "Size" after. An example is provided below.
|
||||
*
|
||||
* @param NAME The name given for the binary data
|
||||
*
|
||||
* @code
|
||||
* INCBIN_EXTERN(Foo);
|
||||
*
|
||||
* // Now you have the following symbols:
|
||||
* // extern const unsigned char <prefix>FooData[];
|
||||
* // extern const unsigned char *const <prefix>FooEnd;
|
||||
* // extern const unsigned int <prefix>FooSize;
|
||||
* @endcode
|
||||
*/
|
||||
#define INCBIN_EXTERN(NAME) \
|
||||
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||
INCBIN_STYLE_IDENT(DATA))[]; \
|
||||
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||
INCBIN_STYLE_IDENT(END)); \
|
||||
INCBIN_EXTERNAL const unsigned int \
|
||||
INCBIN_CONCATENATE( \
|
||||
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||
INCBIN_STYLE_IDENT(SIZE))
|
||||
|
||||
/**
|
||||
* @brief Include a binary file into the current translation unit.
|
||||
*
|
||||
* Includes a binary file into the current translation unit, producing three symbols
|
||||
* for objects that encode the data and size respectively.
|
||||
*
|
||||
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
||||
* "Data", as well as "End" and "Size" after. An example is provided below.
|
||||
*
|
||||
* @param NAME The name to associate with this binary data (as an identifier.)
|
||||
* @param FILENAME The file to include (as a string literal.)
|
||||
*
|
||||
* @code
|
||||
* INCBIN(Icon, "icon.png");
|
||||
*
|
||||
* // Now you have the following symbols:
|
||||
* // const unsigned char <prefix>IconData[];
|
||||
* // const unsigned char *const <prefix>IconEnd;
|
||||
* // const unsigned int <prefix>IconSize;
|
||||
* @endcode
|
||||
*
|
||||
* @warning This must be used in global scope
|
||||
* @warning The identifiers may be different if INCBIN_STYLE is not default
|
||||
*
|
||||
* To externally reference the data included by this in another translation unit
|
||||
* please @see INCBIN_EXTERN.
|
||||
*/
|
||||
#ifdef _MSC_VER
|
||||
#define INCBIN(NAME, FILENAME) \
|
||||
INCBIN_EXTERN(NAME)
|
||||
#else
|
||||
#define INCBIN(NAME, FILENAME) \
|
||||
__asm__(INCBIN_SECTION \
|
||||
INCBIN_GLOBAL_LABELS(NAME, DATA) \
|
||||
INCBIN_ALIGN_HOST \
|
||||
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
|
||||
INCBIN_MACRO " \"" FILENAME "\"\n" \
|
||||
INCBIN_TRAIL \
|
||||
INCBIN_GLOBAL_LABELS(NAME, END) \
|
||||
INCBIN_ALIGN_BYTE \
|
||||
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
|
||||
INCBIN_BYTE "1\n" \
|
||||
INCBIN_GLOBAL_LABELS(NAME, SIZE) \
|
||||
INCBIN_ALIGN_HOST \
|
||||
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
|
||||
INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
|
||||
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
|
||||
INCBIN_ALIGN_HOST \
|
||||
".text\n" \
|
||||
); \
|
||||
INCBIN_EXTERN(NAME)
|
||||
|
||||
#endif
|
||||
#endif
|
2413
contrib/libsofd.c
Normal file
2413
contrib/libsofd.c
Normal file
File diff suppressed because it is too large
Load Diff
194
contrib/libsofd.h
Normal file
194
contrib/libsofd.h
Normal file
@ -0,0 +1,194 @@
|
||||
/* libSOFD - Simple Open File Dialog [for X11 without toolkit]
|
||||
*
|
||||
* Copyright (C) 2014 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef LIBSOFD_H
|
||||
#define LIBSOFD_H 1
|
||||
|
||||
#ifdef HAVE_X11
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/* public API */
|
||||
|
||||
/** open a file select dialog
|
||||
* @param dpy X Display connection
|
||||
* @param parent (optional) if not NULL, become transient for given window
|
||||
* @param x if >0 set explict initial width of the window
|
||||
* @param y if >0 set explict initial height of the window
|
||||
* @return 0 on success
|
||||
*/
|
||||
int x_fib_show (Display *dpy, Window parent, int x, int y);
|
||||
|
||||
/** force close the dialog.
|
||||
* This is normally not needed, the dialog closes itself
|
||||
* when a file is selected or the user cancels selection.
|
||||
* @param dpy X Display connection
|
||||
*/
|
||||
void x_fib_close (Display *dpy);
|
||||
|
||||
/** non-blocking X11 event handler.
|
||||
* It is safe to run this function even if the dialog is
|
||||
* closed or was not initialized.
|
||||
*
|
||||
* @param dpy X Display connection
|
||||
* @param event the XEvent to process
|
||||
* @return status
|
||||
* 0: the event was not for this window, or file-dialog still
|
||||
* active, or the dialog window is not displayed.
|
||||
* >0: file was selected, dialog closed
|
||||
* <0: file selection was cancelled.
|
||||
*/
|
||||
int x_fib_handle_events (Display *dpy, XEvent *event);
|
||||
|
||||
/** last status of the dialog
|
||||
* @return >0: file was selected, <0: canceled or inactive. 0: active
|
||||
*/
|
||||
int x_fib_status ();
|
||||
|
||||
/** query the selected filename
|
||||
* @return NULL if none set, or allocated string to be free()ed by the called
|
||||
*/
|
||||
char *x_fib_filename ();
|
||||
|
||||
/** customize/configure the dialog before calling \ref x_fib_show
|
||||
* changes only have any effect if the dialog is not visible.
|
||||
* @param k key to change
|
||||
* 0: set current dir to display (must end with slash)
|
||||
* 1: set title of dialog window
|
||||
* 2: specify a custom X11 font to use
|
||||
* 3: specify a custom 'places' file to include
|
||||
* (following gtk-bookmark convention)
|
||||
* @param v value
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int x_fib_configure (int k, const char *v);
|
||||
|
||||
/** customize/configure the dialog before calling \ref x_fib_show
|
||||
* changes only have any effect if the dialog is not visible.
|
||||
*
|
||||
* @param k button to change:
|
||||
* 1: show hidden files
|
||||
* 2: show places
|
||||
* 3: show filter/list all (automatically hidden if there is no
|
||||
* filter function)
|
||||
* @param v <0 to hide the button >=0 show button,
|
||||
* 0: set button-state to not-checked
|
||||
* 1: set button-state to checked
|
||||
* >1: retain current state
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int x_fib_cfg_buttons (int k, int v);
|
||||
|
||||
/** set custom callback to filter file-names.
|
||||
* NULL will disable the filter and hide the 'show all' button.
|
||||
* changes only have any effect if the dialog is not visible.
|
||||
*
|
||||
* @param cb callback function to check file
|
||||
* the callback function is called with the file name (basename only)
|
||||
* and is expected to return 1 if the file passes the filter
|
||||
* and 0 if the file should not be listed by default.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int x_fib_cfg_filter_callback (int (*cb)(const char*));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* END X11 specific functions */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 'recently used' API. x-platform
|
||||
* NOTE: all functions use a static cache and are not reentrant.
|
||||
* It is expected that none of these functions are called in
|
||||
* parallel from different threads.
|
||||
*/
|
||||
|
||||
/** release static resources of 'recently used files'
|
||||
*/
|
||||
void x_fib_free_recent ();
|
||||
|
||||
/** add an entry to the recently used list
|
||||
*
|
||||
* The dialog does not add files automatically on open,
|
||||
* if the application succeeds to open a selected file,
|
||||
* this function should be called.
|
||||
*
|
||||
* @param path complete path to file
|
||||
* @param atime time of last use, 0: NOW
|
||||
* @return -1 on error, number of current entries otherwise
|
||||
*/
|
||||
int x_fib_add_recent (const char *path, time_t atime);
|
||||
|
||||
/** get a platform specific path to a good location for
|
||||
* saving the recently used file list.
|
||||
* (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec)
|
||||
*
|
||||
* @param application-name to use to include in file
|
||||
* @return pointer to static path or NULL
|
||||
*/
|
||||
const char *x_fib_recent_file(const char *appname);
|
||||
|
||||
/** save the current list of recently used files to the given filename
|
||||
* (the format is one file per line, filename URL encoded and space separated
|
||||
* with last-used timestamp)
|
||||
*
|
||||
* This function tries to creates the containing directory if it does
|
||||
* not exist.
|
||||
*
|
||||
* @param fn file to save the list to
|
||||
* @return 0: on success
|
||||
*/
|
||||
int x_fib_save_recent (const char *fn);
|
||||
|
||||
/** load a recently used file list.
|
||||
*
|
||||
* @param fn file to load the list from
|
||||
* @return 0: on success
|
||||
*/
|
||||
int x_fib_load_recent (const char *fn);
|
||||
|
||||
/** get number of entries in the current list
|
||||
* @return number of entries in the recently used list
|
||||
*/
|
||||
unsigned int x_fib_recent_count ();
|
||||
|
||||
/** get recently used entry at given position
|
||||
*
|
||||
* @param i entry to query
|
||||
* @return pointer to static string
|
||||
*/
|
||||
const char *x_fib_recent_at (unsigned int i);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // header guard
|
289
contrib/md5.c
Normal file
289
contrib/md5.c
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
|
||||
* MD5 Message-Digest Algorithm (RFC 1321).
|
||||
*
|
||||
* Homepage:
|
||||
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
|
||||
*
|
||||
* Author:
|
||||
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
|
||||
*
|
||||
* This software was written by Alexander Peslyak in 2001. No copyright is
|
||||
* claimed, and the software is hereby placed in the public domain.
|
||||
* In case this attempt to disclaim copyright and place the software in the
|
||||
* public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* (This is a heavily cut-down "BSD license".)
|
||||
*
|
||||
* This differs from Colin Plumb's older public domain implementation in that
|
||||
* no exactly 32-bit integer data type is required (any 32-bit or wider
|
||||
* unsigned integer data type will do), there's no compile-time endianness
|
||||
* configuration, and the function prototypes match OpenSSL's. No code from
|
||||
* Colin Plumb's implementation has been reused; this comment merely compares
|
||||
* the properties of the two independent implementations.
|
||||
*
|
||||
* The primary goals of this implementation are portability and ease of use.
|
||||
* It is meant to be fast, but not as fast as possible. Some known
|
||||
* optimizations are not included to reduce source code size and avoid
|
||||
* compile-time configuration.
|
||||
*/
|
||||
|
||||
#ifndef HAVE_OPENSSL
|
||||
#include <string.h>
|
||||
#include "md5.h"
|
||||
|
||||
/*
|
||||
* The basic MD5 functions.
|
||||
*
|
||||
* F and G are optimized compared to their RFC 1321 definitions for
|
||||
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
|
||||
* implementation.
|
||||
*/
|
||||
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
||||
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
|
||||
#define H(x, y, z) (((x) ^ (y)) ^ (z))
|
||||
#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
|
||||
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
|
||||
|
||||
/*
|
||||
* The MD5 transformation for all four rounds.
|
||||
*/
|
||||
#define STEP(f, a, b, c, d, x, t, s) \
|
||||
(a) += f((b), (c), (d)) + (x) + (t); \
|
||||
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
|
||||
(a) += (b);
|
||||
|
||||
/*
|
||||
* SET reads 4 input bytes in little-endian byte order and stores them in a
|
||||
* properly aligned word in host byte order.
|
||||
*
|
||||
* The check for little-endian architectures that tolerate unaligned memory
|
||||
* accesses is just an optimization. Nothing will break if it fails to detect
|
||||
* a suitable architecture.
|
||||
*
|
||||
* Unfortunately, this optimization may be a C strict aliasing rules violation
|
||||
* if the caller's data buffer has effective type that cannot be aliased by
|
||||
* MD5_u32plus. In practice, this problem may occur if these MD5 routines are
|
||||
* inlined into a calling function, or with future and dangerously advanced
|
||||
* link-time optimizations. For the time being, keeping these MD5 routines in
|
||||
* their own translation unit avoids the problem.
|
||||
*/
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
|
||||
#define SET(n) \
|
||||
(*(MD5_u32plus *)&ptr[(n) * 4])
|
||||
#define GET(n) \
|
||||
SET(n)
|
||||
#else
|
||||
#define SET(n) \
|
||||
(ctx->block[(n)] = \
|
||||
(MD5_u32plus)ptr[(n) * 4] | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
|
||||
#define GET(n) \
|
||||
(ctx->block[(n)])
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This processes one or more 64-byte data blocks, but does NOT update the bit
|
||||
* counters. There are no alignment requirements.
|
||||
*/
|
||||
static const void *body(MD5_CTX *ctx, const void *data, unsigned long size)
|
||||
{
|
||||
const unsigned char *ptr;
|
||||
MD5_u32plus a, b, c, d;
|
||||
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
|
||||
|
||||
ptr = (const unsigned char *)data;
|
||||
|
||||
a = ctx->a;
|
||||
b = ctx->b;
|
||||
c = ctx->c;
|
||||
d = ctx->d;
|
||||
|
||||
do {
|
||||
saved_a = a;
|
||||
saved_b = b;
|
||||
saved_c = c;
|
||||
saved_d = d;
|
||||
|
||||
/* Round 1 */
|
||||
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
|
||||
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
|
||||
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
|
||||
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
|
||||
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
|
||||
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
|
||||
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
|
||||
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
|
||||
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
|
||||
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
|
||||
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
|
||||
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
|
||||
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
|
||||
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
|
||||
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
|
||||
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
|
||||
|
||||
/* Round 2 */
|
||||
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
|
||||
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
|
||||
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
|
||||
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
|
||||
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
|
||||
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
|
||||
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
|
||||
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
|
||||
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
|
||||
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
|
||||
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
|
||||
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
|
||||
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
|
||||
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
|
||||
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
|
||||
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
|
||||
|
||||
/* Round 3 */
|
||||
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
|
||||
STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
|
||||
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
|
||||
STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
|
||||
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
|
||||
STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
|
||||
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
|
||||
STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
|
||||
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
|
||||
STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
|
||||
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
|
||||
STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
|
||||
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
|
||||
STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
|
||||
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
|
||||
STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
|
||||
|
||||
/* Round 4 */
|
||||
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
|
||||
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
|
||||
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
|
||||
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
|
||||
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
|
||||
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
|
||||
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
|
||||
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
|
||||
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
|
||||
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
|
||||
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
|
||||
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
|
||||
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
|
||||
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
|
||||
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
|
||||
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
|
||||
|
||||
a += saved_a;
|
||||
b += saved_b;
|
||||
c += saved_c;
|
||||
d += saved_d;
|
||||
|
||||
ptr += 64;
|
||||
} while (size -= 64);
|
||||
|
||||
ctx->a = a;
|
||||
ctx->b = b;
|
||||
ctx->c = c;
|
||||
ctx->d = d;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MD5_Init(MD5_CTX *ctx)
|
||||
{
|
||||
ctx->a = 0x67452301;
|
||||
ctx->b = 0xefcdab89;
|
||||
ctx->c = 0x98badcfe;
|
||||
ctx->d = 0x10325476;
|
||||
|
||||
ctx->lo = 0;
|
||||
ctx->hi = 0;
|
||||
}
|
||||
|
||||
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size)
|
||||
{
|
||||
MD5_u32plus saved_lo;
|
||||
unsigned long used, available;
|
||||
|
||||
saved_lo = ctx->lo;
|
||||
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
|
||||
ctx->hi++;
|
||||
ctx->hi += size >> 29;
|
||||
|
||||
used = saved_lo & 0x3f;
|
||||
|
||||
if (used) {
|
||||
available = 64 - used;
|
||||
|
||||
if (size < available) {
|
||||
memcpy(&ctx->buffer[used], data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&ctx->buffer[used], data, available);
|
||||
data = (const unsigned char *)data + available;
|
||||
size -= available;
|
||||
body(ctx, ctx->buffer, 64);
|
||||
}
|
||||
|
||||
if (size >= 64) {
|
||||
data = body(ctx, data, size & ~(unsigned long)0x3f);
|
||||
size &= 0x3f;
|
||||
}
|
||||
|
||||
memcpy(ctx->buffer, data, size);
|
||||
}
|
||||
|
||||
#define OUT(dst, src) \
|
||||
(dst)[0] = (unsigned char)(src); \
|
||||
(dst)[1] = (unsigned char)((src) >> 8); \
|
||||
(dst)[2] = (unsigned char)((src) >> 16); \
|
||||
(dst)[3] = (unsigned char)((src) >> 24);
|
||||
|
||||
void MD5_Final(unsigned char *result, MD5_CTX *ctx)
|
||||
{
|
||||
unsigned long used, available;
|
||||
|
||||
used = ctx->lo & 0x3f;
|
||||
|
||||
ctx->buffer[used++] = 0x80;
|
||||
|
||||
available = 64 - used;
|
||||
|
||||
if (available < 8) {
|
||||
memset(&ctx->buffer[used], 0, available);
|
||||
body(ctx, ctx->buffer, 64);
|
||||
used = 0;
|
||||
available = 64;
|
||||
}
|
||||
|
||||
memset(&ctx->buffer[used], 0, available - 8);
|
||||
|
||||
ctx->lo <<= 3;
|
||||
OUT(&ctx->buffer[56], ctx->lo)
|
||||
OUT(&ctx->buffer[60], ctx->hi)
|
||||
|
||||
body(ctx, ctx->buffer, 64);
|
||||
|
||||
OUT(&result[0], ctx->a)
|
||||
OUT(&result[4], ctx->b)
|
||||
OUT(&result[8], ctx->c)
|
||||
OUT(&result[12], ctx->d)
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
#endif
|
43
contrib/md5.h
Normal file
43
contrib/md5.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
|
||||
* MD5 Message-Digest Algorithm (RFC 1321).
|
||||
*
|
||||
* Homepage:
|
||||
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
|
||||
*
|
||||
* Author:
|
||||
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
|
||||
*
|
||||
* This software was written by Alexander Peslyak in 2001. No copyright is
|
||||
* claimed, and the software is hereby placed in the public domain.
|
||||
* In case this attempt to disclaim copyright and place the software in the
|
||||
* public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* See md5.c for more information.
|
||||
*/
|
||||
|
||||
#ifndef _MD5_H
|
||||
#define _MD5_H
|
||||
|
||||
/* Any 32-bit or wider unsigned integer data type will do */
|
||||
typedef unsigned int MD5_u32plus;
|
||||
|
||||
typedef struct {
|
||||
MD5_u32plus lo, hi;
|
||||
MD5_u32plus a, b, c, d;
|
||||
unsigned char buffer[64];
|
||||
MD5_u32plus block[16];
|
||||
} MD5_CTX;
|
||||
|
||||
void MD5_Init(MD5_CTX *ctx);
|
||||
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
|
||||
void MD5_Final(unsigned char *result, MD5_CTX *ctx);
|
||||
|
||||
#endif /* _MD5_H */
|
1724
contrib/stb_image_write.h
Normal file
1724
contrib/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
disks/Games1MB.po
Normal file
BIN
disks/Games1MB.po
Normal file
Binary file not shown.
1
disks/dos33master.nib
generated
Normal file
1
disks/dos33master.nib
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
disks/prodos242.dsk
Normal file
BIN
disks/prodos242.dsk
Normal file
Binary file not shown.
33
docs/Compiling.md
Normal file
33
docs/Compiling.md
Normal file
@ -0,0 +1,33 @@
|
||||
## How to I compile it and run it?
|
||||
* You need a C compiler, make, and a few libraries:
|
||||
* libasound2-dev [ optional, for audio ]
|
||||
* libgl-dev
|
||||
* libglu-dev
|
||||
* libx11-dev
|
||||
* Many of them will probably be installed already if you regulargly compile stuff.
|
||||
* Then just type `make` and it should compile.
|
||||
* To run it, just type `build-x86_64-linux-gnu/bin/bin/mii_emu` and it should start.
|
||||
|
||||
## Development
|
||||
* This is the first project I made using vscode as my main IDE; I used Eclipse for many years as the indexer was pretty much unbeatable (still is).
|
||||
* To support vscode I needed some tooling to create the '*compile_commands.json*' file, which is used by the C/C++ extension to provide intellisense. So to create that file, you use `make lsp` and it will create the file in the project directory.
|
||||
* Another thing vscode sucks at is Makefiles (despite the extension, building is painful and next to useless). So there is a target called `make watch` that you can run in a separate terminal to auto-build as soon as you same any files in vscode. That is when you realize for example that vscode re-save the file anytime you press control-S, regardless of wether the file has changed or not. So you end up with a lot of unnecessary builds. I'm sure there is a way to fix that, but I haven't found it yet.
|
||||
|
||||
## Code Style
|
||||
I have pretty consistent code style across my projects.
|
||||
* tabs=4. Just because.
|
||||
* 80 columns lines or so. I don't like to scroll horizontally and I like splitting my screen vertically. (also see, tabs=4!).
|
||||
* K&R style exclusively. None of that Allman/GNU horribleness.
|
||||
* No Yoda, we are no longer in 1975. `if (0 == foo)` is just stupid.
|
||||
* I use !! to convert to 1 or zero from an expression. I call it the 'normalisation operator'. it is not commonly seen, apart from places like the Linux kernel.
|
||||
* I use *minipt.h* for protothreads. I like protothreads, and when you are aware of their limitations, they are a great tool. They are used for the video rendering, the no-slot-clock etc.
|
||||
|
||||
## What needs doing?
|
||||
* I'm sure there are bugs. I haven't tested it on a lot of hardware, and apart from quite a few games and a few known productivity app, it had had very little extensive testing. So testing!
|
||||
* In the code there are a few bits which needs fixing
|
||||
* The mouse card is not working properly. It works for some (most importantly A2 Desktop), but it's not working properly. I suspect the VBL Interrupt handling.
|
||||
* In mii.c, the *mii_page_table_update()* does work, but it sub-optimal, it was handy to debug the countless combination of soft-switches, but it is not the ideal way to handle that problem; it needs a proper switch-case statement.
|
||||
* The floppy drive emulation was borrowed from bobbin, and it works, and it
|
||||
got it all working, but it's definitely not matching the style of the rest of the codebase. It needs to be replaced.
|
||||
* Plenty of the most complicated piece of code (video, memory mapping) load a dozen of soft-switches, it probably should use a separately maintained bit field that is maintained by the on/off switches, and would make it a lot easier to test for bit combinations.
|
||||
* The static array of memory 'banks' works, but it prevents me easily making a memory extension card. It should be refactored at some point.
|
BIN
docs/screen_color.png
Normal file
BIN
docs/screen_color.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 KiB |
BIN
docs/screen_green.png
Normal file
BIN
docs/screen_green.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 279 KiB |
BIN
docs/screen_main.png
Normal file
BIN
docs/screen_main.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 510 KiB |
BIN
docs/screen_mish.png
Normal file
BIN
docs/screen_mish.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
docs/screen_total.png
Normal file
BIN
docs/screen_total.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 210 KiB |
BIN
fonts/DroidSans.ttf
Normal file
BIN
fonts/DroidSans.ttf
Normal file
Binary file not shown.
20
fonts/FreeLicense.txt
Normal file
20
fonts/FreeLicense.txt
Normal file
@ -0,0 +1,20 @@
|
||||
KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE
|
||||
version 1.2f
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions:
|
||||
|
||||
1. The User may not sell copies of the Software for a fee.
|
||||
|
||||
1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software.
|
||||
|
||||
2. The User may not modify, reverse-engineer, or create any derivative works of the Software.
|
||||
|
||||
3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font!
|
||||
|
||||
3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license.
|
||||
|
||||
4. This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
5. Kreative Software reserves the right to change this license at any time without notice.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE.
|
BIN
fonts/PRNumber3.ttf
Normal file
BIN
fonts/PRNumber3.ttf
Normal file
Binary file not shown.
BIN
fonts/PrintChar21.ttf
Normal file
BIN
fonts/PrintChar21.ttf
Normal file
Binary file not shown.
BIN
fonts/ProggyClean.ttf
Normal file
BIN
fonts/ProggyClean.ttf
Normal file
Binary file not shown.
2
libmish/.gitignore
vendored
Normal file
2
libmish/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build-*
|
||||
doc/.tags*
|
202
libmish/LICENSE-2.0.txt
Normal file
202
libmish/LICENSE-2.0.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
98
libmish/Makefile
Normal file
98
libmish/Makefile
Normal file
@ -0,0 +1,98 @@
|
||||
# Makefile
|
||||
#
|
||||
# Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
SOV = 1
|
||||
VERSION = 0.10
|
||||
PKG = 1
|
||||
|
||||
TARGET = libmish
|
||||
DESC = Program Shell Access
|
||||
|
||||
ifneq ($(CC), emcc)
|
||||
BASE_LDFLAGS += -lutil
|
||||
endif
|
||||
BASE_LDFLAGS += -lpthread
|
||||
EXTRA_LDFLAGS = $(BASE_LDFLAGS)
|
||||
|
||||
# Auto load all the .c files dependencies, and object files
|
||||
LIBSRC := ${notdir ${wildcard src/*.c}}
|
||||
|
||||
# Tell make/gcc to find the files in VPATH
|
||||
VPATH = src
|
||||
VPATH += tests
|
||||
IPATH = src
|
||||
|
||||
include ./Makefile.common
|
||||
|
||||
TOOLS =
|
||||
TESTS = ${BIN}/mish_test \
|
||||
${BIN}/mish_vt_test ${BIN}/mish_cmd_test \
|
||||
${BIN}/mish_input_test \
|
||||
${BIN}/mish_argv_make_test
|
||||
|
||||
all : tools tests
|
||||
|
||||
debug: all ${BIN}/mish_debug_test
|
||||
@echo "*** mish_debug_test is dangerous, do not install anywhere"
|
||||
|
||||
.PHONY: static shared tools tests
|
||||
static: $(LIB)/$(TARGET).a
|
||||
shared: ${LIB}/$(TARGET).so.$(SOV)
|
||||
tools: $(TOOLS)
|
||||
tests: $(TESTS)
|
||||
|
||||
LIBOBJ := ${patsubst %, ${OBJ}/%, ${notdir ${LIBSRC:.c=.o}}}
|
||||
|
||||
$(LIB)/$(TARGET).a : $(LIBOBJ) | $(LIB)
|
||||
$(LIB)/$(TARGET).so.$(SOV) : $(LIBOBJ) | $(LIB)/$(TARGET).a
|
||||
|
||||
# This rule OUGHT to be enough, but somehow it fails to take into account
|
||||
# the 'shared' dependency, if its written explicitely as it is now, it
|
||||
# works. I see no reason why it should not work, as the one a line down
|
||||
# has no problem!!
|
||||
#$(BIN)/%: | shared
|
||||
$(TOOLS) $(TESTS): | shared
|
||||
ifeq ($(CC), emcc)
|
||||
$(BIN)/%: LDFLAGS_TARGET+=-L${LIB}
|
||||
endif
|
||||
$(BIN)/%: LDFLAGS_TARGET += -lmish -lrt
|
||||
|
||||
$(BIN)/mish_input_test: LDFLAGS_TARGET =
|
||||
|
||||
clean::
|
||||
rm -f $(LIB)/$(TARGET).* $(TOOLS) $(TESTS)
|
||||
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)/bin/ $(DESTDIR)/lib/ $(DESTDIR)/include/
|
||||
for bin in $(TOOLS); do $(INSTALL) -m 0755 $$bin $(DESTDIR)/bin/$$(basename $$bin) ; done
|
||||
$(INSTALL) -m 644 $(LIB)/$(TARGET).a $(DESTDIR)/lib/
|
||||
$(INSTALL) -m 644 src/mish.h $(DESTDIR)/include/
|
||||
rm -rf $(DESTDIR)/lib/$(TARGET).so*
|
||||
$(INSTALL) -m 644 $(LIB)/$(TARGET).so.$(VERSION).$(SOV) $(DESTDIR)/lib/
|
||||
cp -f -d $(LIB)/{$(TARGET).so.$(SOV),$(TARGET).so} $(DESTDIR)/lib/
|
||||
mkdir -p $(DESTDIR)/lib/pkgconfig
|
||||
sed -e "s|PREFIX|$(PREFIX)|" -e "s|VERSION|$(VERSION)|" \
|
||||
mish.pc >$(DESTDIR)/lib/pkgconfig/mish.pc
|
||||
|
||||
deb :
|
||||
rm -rf /tmp/$(TARGET) *.deb
|
||||
make clean && make all && make install DESTDIR=/tmp/$(TARGET)/usr
|
||||
mkdir -p $(BUILD)/debian; (cd $(BUILD)/debian; \
|
||||
fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET) -v $(VERSION) \
|
||||
--iteration $(PKG) \
|
||||
--description "$(DESC)" \
|
||||
usr/lib/{$(TARGET).so.$(SOV),$(TARGET).so.$(VERSION).$(SOV),$(TARGET).so} && \
|
||||
fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET)-dev -v $(VERSION) \
|
||||
--iteration $(PKG) \
|
||||
--description "$(DESC) - development files" \
|
||||
-d "$(TARGET) (= $(VERSION))" \
|
||||
usr/lib/$(TARGET).a \
|
||||
usr/lib/pkgconfig \
|
||||
usr/include ; \
|
||||
)
|
97
libmish/Makefile.common
Normal file
97
libmish/Makefile.common
Normal file
@ -0,0 +1,97 @@
|
||||
# Makefile.common
|
||||
#
|
||||
# Copyright (C) 2012 Michel Pollet <buserror@gmail.com>
|
||||
#
|
||||
|
||||
SHELL = /bin/bash
|
||||
INSTALL ?= install
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O2 -Wall
|
||||
|
||||
O ?= .
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
ARCH := $(shell $(CC) -dumpmachine)
|
||||
BUILDNAME := build-$(ARCH)
|
||||
BUILD := $(O)/$(BUILDNAME)
|
||||
OBJ = $(BUILD)/obj/$(TARGET)
|
||||
BIN = $(BUILD)/bin
|
||||
LIB = $(BUILD)/lib
|
||||
|
||||
DESTDIR = $(O)/build-$(ARCH)
|
||||
|
||||
EXTRA_CFLAGS += -g -fPIC --std=gnu99
|
||||
EXTRA_CFLAGS += -ffunction-sections -fdata-sections
|
||||
EXTRA_CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}}
|
||||
|
||||
ifeq (${shell uname},Linux)
|
||||
EXTRA_LDFLAGS += -Wl,--relax,--gc-sections
|
||||
READLINK = readlink -f
|
||||
endif
|
||||
ifeq (${shell uname},Darwin)
|
||||
READLINK = echo
|
||||
endif
|
||||
|
||||
ifeq ($(V),1)
|
||||
Q :=
|
||||
else
|
||||
Q := @
|
||||
endif
|
||||
|
||||
EXTRA_LDFLAGS += -L$(LIB) -Wl,-rpath,${shell $(READLINK) ${LIB}}
|
||||
|
||||
all: ${OBJ}
|
||||
|
||||
#
|
||||
# Generic rules
|
||||
#
|
||||
$(OBJ) $(BIN) $(LIB):
|
||||
+$(Q)echo " MKDIR" $@ && mkdir -p $@
|
||||
|
||||
$(LIB)/%.a : | $(LIB)
|
||||
$(Q)echo " LIB ${@}" && \
|
||||
$(AR) cr $@ $^ \
|
||||
|| echo Error: $(AR) cru $@ $^
|
||||
|
||||
.SECONDARY:
|
||||
|
||||
$(OBJ)/%.o: %.c | $(OBJ)
|
||||
$(Q)echo " CC" ${filter -O%, ${EXTRA_CFLAGS} $(CFLAGS) ${CFLAGS_TARGET}} "${<}" && \
|
||||
${CC} -MMD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} \
|
||||
|| echo Error: ${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<}
|
||||
|
||||
$(BIN)/%: $(OBJ)/%.o | $(BIN)
|
||||
$(Q)echo " LD ${*}" && \
|
||||
${CC} -o ${@} $(filter %.o %.a,$^) \
|
||||
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}} \
|
||||
|| echo Error: ${CC} -o $(filter %.o %.a,$^) \
|
||||
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}}
|
||||
|
||||
ifeq (${shell uname},Linux)
|
||||
define soname
|
||||
"-Wl,-soname,$(1)"
|
||||
endef
|
||||
else
|
||||
define soname
|
||||
endef
|
||||
endif
|
||||
|
||||
$(LIB)/%.so.$(SOV) $(LIB)/%.so.$(VERSION).$(SOV) $(LIB)/%.so: | $(LIB)
|
||||
$(Q)echo " LDSO $@" && \
|
||||
{ \
|
||||
naked=${shell basename ${basename $@}}; \
|
||||
${CC} -shared \
|
||||
${call soname,$$naked} \
|
||||
-o $(LIB)/$$naked.$(VERSION).$(SOV) \
|
||||
$(filter %.o %.a,$^) \
|
||||
${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS} && \
|
||||
ln -sf $$naked.$(VERSION).$(SOV) ${LIB}/$$naked.$(SOV) && \
|
||||
ln -sf $$naked.$(SOV) ${LIB}/$$naked ; \
|
||||
} || echo ERROR: $@ -- relaunch with V=1
|
||||
|
||||
clean::
|
||||
# rm -rf $(OBJ) $(BIN)/$(TARGET)
|
||||
rm -rf $(BUILD)
|
||||
|
||||
# Include autogenerated dependencies
|
||||
-include ${wildcard $(OBJ)/*.d}
|
111
libmish/README.md
Normal file
111
libmish/README.md
Normal file
@ -0,0 +1,111 @@
|
||||
|
||||
## libmish TL;DR :
|
||||
*libmish* is a library that allows you to add a command prompt to any of your own program. It captures the standard output and standard error file descriptor, create a log with this, and displays it back to you, with a command prompt.
|
||||
|
||||
You can then add your own commands to interract with your (running) program. Oh, *libmish* can also serve this to incoming telnet sessions (on a custom port), so you can connect to detached processes too.
|
||||
|
||||
## Demo
|
||||
Consider this program. The only "new" things are the <u>#include</u>, and the <u>mish_prepare()</u> call. That gives you telnet access and will log all output, but not much else.
|
||||
|
||||
```C
|
||||
#include "mish.h"
|
||||
|
||||
int cnt = 0;
|
||||
|
||||
int main()
|
||||
{
|
||||
mish_prepare(0);
|
||||
|
||||
while (1) {
|
||||
sleep(1);
|
||||
printf("Count %d\n", cnt++);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now add this at the bottom of the file:
|
||||
|
||||
```C
|
||||
/* And here is a command line action that can reset the 'cnt' variable */
|
||||
static void _test_set_cnt(void * param, int argc, const char * argv[])
|
||||
{
|
||||
if (argc > 1) {
|
||||
cnt = atoi(argv[1]);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%s: syntax 'set XXX' to set the variable\n", argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(set, "set");
|
||||
MISH_CMD_HELP(set,
|
||||
"set 'cnt' variable",
|
||||
"test command for libmish!");
|
||||
MISH_CMD_REGISTER(set, _test_set_cnt);
|
||||
```
|
||||
|
||||
And here you go:
|
||||
![Demo of libmish](doc/demo.gif)
|
||||
|
||||
As you can see, the stderr output is colorized. The program also told you the telnet port to call into (but you can also see it via the 'env' command later on).
|
||||
|
||||
Calling the 'set' command will change the main variable. Of course it doesn't been to be thread safe in this instance, if you want your command to run in a thread safe way, you have to use <u>mish_cmd_poll()</u> from your thread, this will run pending commands in your own context.
|
||||
|
||||
## Ok what's going on here, why do I need this?
|
||||
Let's say, you have that program that runs for days. Or months, or years, and it has it's log in a log file and all is very well, but sometime, you'd like to just *interract* with it, say, check statistics, internal state, or just change a parameter or so. Or just pet it for the good job it's doing.
|
||||
|
||||
Traditionally, you'd just restart the program with new parameters, or use a *host* of other complicated methods to send commands to the running program, like signals, UNIX sockets and countless stuff like that.
|
||||
|
||||
*libmish* allows you to that poking around, without having to bother that much about it. Just add *libmish*, register some of your own commands, and you'll be able to 'connect' to your running program, check it's output history (without having to deal with log files) and use your own commands on your own running program.
|
||||
|
||||
## What does work?
|
||||
Well, its' already quite useful:
|
||||
|
||||
* You can browse the history of the log of your program with Beg/Page Up/Down/End
|
||||
* You can telnet in, and check the log too.
|
||||
* You can display command history and use control-P/N to navigate it
|
||||
* You can easily "register" your commands, with their own 'help'.
|
||||
* It only requires one single "mish_prepare(...)" call at the start of yout main() to work.
|
||||
|
||||
## Security? What security?
|
||||
Obviously, using mish in your program is an obvious security risk, you're
|
||||
allowing anyone on that machine to temper with your program via telnet, there's no
|
||||
safegards in place. *libmish* is really made for developement purpose on
|
||||
machine that you trust.
|
||||
|
||||
Also, as much as I don't think I code like a dork, I didn't go mega full paranoia on security in the internals, so there's bound checking, but I can almost guarantee there must be ways of fooling the various parsers into crashing. Again this was all written in the space of 3 days, so I went for the goalpost as a hare, not as a tank!
|
||||
|
||||
If you want to make sure *libmish* is disabled on machine that you *don't* trust (ie, production), you can set an environment variable MISH_OFF=1 before launching the programs and it will prevent the library starting. But again, buyers beware.
|
||||
|
||||
As to why I use a TCP port (bound to 127.0.0.1), well it's because:
|
||||
|
||||
* I want to be able to use 'telnet' to connect...
|
||||
* 'telnet' on linux doesn't handle UNIX sockets
|
||||
* I need 'telnet' because it gives me the terminal window size.
|
||||
|
||||
... otherwise, I wouldn't have!
|
||||
|
||||
## Issues & todos & caveats
|
||||
This is a brand new library, a whole ton of things works, but quite a few things don't, just yet.
|
||||
|
||||
Some bits I know I want but didn't *need* and some I've tried to fix, but haven't spent enough time on it to nail them down.
|
||||
|
||||
**TODO:**
|
||||
|
||||
* Currently the main thread uses select(), because it's portable. Need to add an epool() alternative one for linux.
|
||||
* Display a timestamp for lines in the backlog.
|
||||
* Display some sort of progressy-bar thing at the bottom when navigating the log.
|
||||
* Add a UNIX stream socket access, but we'll have to use socat/netcat, stty raw and some sort of helper command line.
|
||||
* Make the backlog expire after X hours/days. A stamp is already collected, but not used.
|
||||
|
||||
**BUGS:**
|
||||
|
||||
* Find a way to cleany quit without screwing up the terminal settings (telnet).
|
||||
- If you have that problem, use 'resize' or 'reset' to make your terminal work again.
|
||||
- Or better, use 'screen telnet xxx yyy' as screen is awesome at cleaning up.
|
||||
- or __betterer__, use the "dis" disconnect command, that works.
|
||||
- Backlog navigation doesn't take into account line wrapping.
|
||||
|
||||
## Why is it called *libmish* anyway, sounds silly.
|
||||
Well since you think you're clever, "lib" is for library, "sh" is for *shell* and "mi" is for **me** aka, **michel** pronunced *me* *shell*. Geddit?
|
||||
|
21
libmish/doc/Makefile
Normal file
21
libmish/doc/Makefile
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
|
||||
#
|
||||
# This file is part of libmish.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# For the callgraph, you need Graphviz, ruby and exuberant ctags.
|
||||
# This is not generated in the normal code build
|
||||
|
||||
|
||||
all: libmish_callgraph.pdf
|
||||
|
||||
libmish_callgraph.pdf:
|
||||
ctags -f .tags ../src/*.[ch] 2>/dev/null && \
|
||||
ruby ./tags_to_dot.rb .tags \
|
||||
../src/*.c ../src/mish.h >.tags.dot && \
|
||||
dot -Tpdf .tags.dot -o $@
|
||||
|
||||
clean:
|
||||
rm -f .tags*
|
BIN
libmish/doc/demo.gif
Normal file
BIN
libmish/doc/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
BIN
libmish/doc/libmish_callgraph.pdf
Normal file
BIN
libmish/doc/libmish_callgraph.pdf
Normal file
Binary file not shown.
99
libmish/doc/tags_to_dot.rb
Executable file
99
libmish/doc/tags_to_dot.rb
Executable file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/ruby
|
||||
#
|
||||
# Copyright 2008, 2009 Michel Pollet <buserror@gmail.com>
|
||||
#
|
||||
# This file is part of libmish.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
$files = Hash.new
|
||||
$syms = Hash.new
|
||||
|
||||
tags = File.new(ARGV[0])
|
||||
|
||||
key = Array.new
|
||||
|
||||
while !tags.eof?
|
||||
next unless tags.readline.chomp.match(/([^\t]+)\t([^\t]+)\t(.*)\$\/;"\t([^\t]+)/);
|
||||
key[0] = $1;
|
||||
key[1] = $2;
|
||||
key[3] = $3;
|
||||
key[4] = $4;
|
||||
|
||||
next if key[4] == 'm' or key[4] == 't' or key[4] == 's' or key[4] == 'e' or key[4] == 'v';
|
||||
next if key[0].match(/[us]?int[0-9]+_t/);
|
||||
next if key[0] == "ROM_BASED";
|
||||
|
||||
key[1].gsub!(/.*\/|\.[ch]$/,"");
|
||||
|
||||
unless $files.key? key[1]
|
||||
$files[key[1]] = Hash.new
|
||||
end
|
||||
unless $files[key[1]].key? key[0]
|
||||
$files[key[1]][key[0]] = Hash.new
|
||||
$syms[key[0]] = key[1]
|
||||
end
|
||||
#puts key[0] + " = '#{key[4]}'"
|
||||
end
|
||||
|
||||
puts "digraph dump { node [shape=rect]; compound=true; nodesep=.1; ranksep=2; rankdir=LR; concentrate=true; "
|
||||
|
||||
modules = Hash.new;
|
||||
links = Array.new;
|
||||
|
||||
1.upto(ARGV.length-1) { |i|
|
||||
|
||||
use = File.new(ARGV[i])
|
||||
# puts "<<<<<<<<FILE " + ARGV[i]
|
||||
|
||||
fil = ARGV[i].gsub(/.*\/|\.[ch]$/,"");
|
||||
|
||||
while !use.eof?
|
||||
|
||||
line = use.readline;
|
||||
next if line.match(/[ \t]*\/\//);
|
||||
line.gsub!(/[^a-zA-Z0-9_]/, " ");
|
||||
line.gsub!(/[ \t]+/, " ");
|
||||
# puts ">>>" + line
|
||||
words = line.split(/[ \t]+/);
|
||||
words.each { |w|
|
||||
if $syms.key? w and $syms[w] != fil
|
||||
unless $files[$syms[w]][w].key? fil
|
||||
# puts w + " is in " + $syms[w]
|
||||
$files[$syms[w]][w][fil] = 1
|
||||
|
||||
sym=w
|
||||
unless modules.key? fil
|
||||
modules[fil] = Array.new
|
||||
end
|
||||
modules[fil] += [ "\"cc_#{fil}_#{sym}\" [label=\"#{sym}\",color=\"gray\",height=\".08\",style=dotted];" ]
|
||||
links += ["\"cc_#{fil}_#{sym}\" -> \"dd_#{$syms[w]}_#{sym}\";"]
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
$files.keys.each { |fil|
|
||||
# puts "File #{fil} ?"
|
||||
$files[fil].keys.each { |sym|
|
||||
# puts "\tSym #{sym} : #{$files[fil][sym].length} ?"
|
||||
if $files[fil][sym].length > 0
|
||||
unless modules.key? fil
|
||||
modules[fil] = Array.new
|
||||
end
|
||||
modules[fil] += [ "\"dd_#{fil}_#{sym}\" [label=\"#{sym}\"];" ]
|
||||
end
|
||||
}
|
||||
}
|
||||
modules.keys.each {|fil|
|
||||
puts "\tsubgraph cluster_#{fil} {\n\t\tlabel=\"#{fil}\"; fontsize=\"18\"; "
|
||||
modules[fil].each { |lin|
|
||||
puts "\t\t#{lin}"
|
||||
}
|
||||
puts "\t}"
|
||||
}
|
||||
links.each { |lin|
|
||||
puts "\t#{lin}"
|
||||
}
|
||||
puts "}"
|
13
libmish/mish.pc
Normal file
13
libmish/mish.pc
Normal file
@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mish.pc:
|
||||
prefix=PREFIX
|
||||
exec_prefix=${prefix}
|
||||
includedir=${prefix}/include
|
||||
libdir=${exec_prefix}/lib
|
||||
|
||||
Name: libmish
|
||||
Description: Adds command prompt for your program(s)
|
||||
Version: VERSION
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lmish -lrt
|
860
libmish/src/bsd_queue.h
Normal file
860
libmish/src/bsd_queue.h
Normal file
@ -0,0 +1,860 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Copyright (c) 1991, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* @(#)queue.h 8.5 (Berkeley) 8/20/94
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef _SYS_QUEUE_H_
|
||||
#define _SYS_QUEUE_H_
|
||||
|
||||
//#include <sys/cdefs.h>
|
||||
|
||||
/*
|
||||
* This file defines four types of data structures: singly-linked lists,
|
||||
* singly-linked tail queues, lists and tail queues.
|
||||
*
|
||||
* A singly-linked list is headed by a single forward pointer. The elements
|
||||
* are singly linked for minimum space and pointer manipulation overhead at
|
||||
* the expense of O(n) removal for arbitrary elements. New elements can be
|
||||
* added to the list after an existing element or at the head of the list.
|
||||
* Elements being removed from the head of the list should use the explicit
|
||||
* macro for this purpose for optimum efficiency. A singly-linked list may
|
||||
* only be traversed in the forward direction. Singly-linked lists are ideal
|
||||
* for applications with large datasets and few or no removals or for
|
||||
* implementing a LIFO queue.
|
||||
*
|
||||
* A singly-linked tail queue is headed by a pair of pointers, one to the
|
||||
* head of the list and the other to the tail of the list. The elements are
|
||||
* singly linked for minimum space and pointer manipulation overhead at the
|
||||
* expense of O(n) removal for arbitrary elements. New elements can be added
|
||||
* to the list after an existing element, at the head of the list, or at the
|
||||
* end of the list. Elements being removed from the head of the tail queue
|
||||
* should use the explicit macro for this purpose for optimum efficiency.
|
||||
* A singly-linked tail queue may only be traversed in the forward direction.
|
||||
* Singly-linked tail queues are ideal for applications with large datasets
|
||||
* and few or no removals or for implementing a FIFO queue.
|
||||
*
|
||||
* A list is headed by a single forward pointer (or an array of forward
|
||||
* pointers for a hash table header). The elements are doubly linked
|
||||
* so that an arbitrary element can be removed without a need to
|
||||
* traverse the list. New elements can be added to the list before
|
||||
* or after an existing element or at the head of the list. A list
|
||||
* may be traversed in either direction.
|
||||
*
|
||||
* A tail queue is headed by a pair of pointers, one to the head of the
|
||||
* list and the other to the tail of the list. The elements are doubly
|
||||
* linked so that an arbitrary element can be removed without a need to
|
||||
* traverse the list. New elements can be added to the list before or
|
||||
* after an existing element, at the head of the list, or at the end of
|
||||
* the list. A tail queue may be traversed in either direction.
|
||||
*
|
||||
* For details on the use of these macros, see the queue(3) manual page.
|
||||
*
|
||||
* Below is a summary of implemented functions where:
|
||||
* + means the macro is available
|
||||
* - means the macro is not available
|
||||
* s means the macro is available but is slow (runs in O(n) time)
|
||||
*
|
||||
* SLIST LIST STAILQ TAILQ
|
||||
* _HEAD + + + +
|
||||
* _CLASS_HEAD + + + +
|
||||
* _HEAD_INITIALIZER + + + +
|
||||
* _ENTRY + + + +
|
||||
* _CLASS_ENTRY + + + +
|
||||
* _INIT + + + +
|
||||
* _EMPTY + + + +
|
||||
* _FIRST + + + +
|
||||
* _NEXT + + + +
|
||||
* _PREV - + - +
|
||||
* _LAST - - + +
|
||||
* _FOREACH + + + +
|
||||
* _FOREACH_FROM + + + +
|
||||
* _FOREACH_SAFE + + + +
|
||||
* _FOREACH_FROM_SAFE + + + +
|
||||
* _FOREACH_REVERSE - - - +
|
||||
* _FOREACH_REVERSE_FROM - - - +
|
||||
* _FOREACH_REVERSE_SAFE - - - +
|
||||
* _FOREACH_REVERSE_FROM_SAFE - - - +
|
||||
* _INSERT_HEAD + + + +
|
||||
* _INSERT_BEFORE - + - +
|
||||
* _INSERT_AFTER + + + +
|
||||
* _INSERT_TAIL - - + +
|
||||
* _CONCAT s s + +
|
||||
* _REMOVE_AFTER + - + -
|
||||
* _REMOVE_HEAD + - + -
|
||||
* _REMOVE s + s +
|
||||
* _SWAP + + + +
|
||||
*
|
||||
*/
|
||||
#ifdef QUEUE_MACRO_DEBUG
|
||||
#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
|
||||
#define QUEUE_MACRO_DEBUG_TRACE
|
||||
#define QUEUE_MACRO_DEBUG_TRASH
|
||||
#endif
|
||||
|
||||
#ifdef QUEUE_MACRO_DEBUG_TRACE
|
||||
/* Store the last 2 places the queue element or head was altered */
|
||||
struct qm_trace {
|
||||
unsigned long lastline;
|
||||
unsigned long prevline;
|
||||
const char *lastfile;
|
||||
const char *prevfile;
|
||||
};
|
||||
|
||||
#define TRACEBUF struct qm_trace trace;
|
||||
#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } ,
|
||||
|
||||
#define QMD_TRACE_HEAD(head) do { \
|
||||
(head)->trace.prevline = (head)->trace.lastline; \
|
||||
(head)->trace.prevfile = (head)->trace.lastfile; \
|
||||
(head)->trace.lastline = __LINE__; \
|
||||
(head)->trace.lastfile = __FILE__; \
|
||||
} while (0)
|
||||
|
||||
#define QMD_TRACE_ELEM(elem) do { \
|
||||
(elem)->trace.prevline = (elem)->trace.lastline; \
|
||||
(elem)->trace.prevfile = (elem)->trace.lastfile; \
|
||||
(elem)->trace.lastline = __LINE__; \
|
||||
(elem)->trace.lastfile = __FILE__; \
|
||||
} while (0)
|
||||
|
||||
#else /* !QUEUE_MACRO_DEBUG_TRACE */
|
||||
#define QMD_TRACE_ELEM(elem)
|
||||
#define QMD_TRACE_HEAD(head)
|
||||
#define TRACEBUF
|
||||
#define TRACEBUF_INITIALIZER
|
||||
#endif /* QUEUE_MACRO_DEBUG_TRACE */
|
||||
|
||||
#ifdef QUEUE_MACRO_DEBUG_TRASH
|
||||
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
|
||||
#define QMD_IS_TRASHED(x) ((x) == (void *)(intptr_t)-1)
|
||||
#else /* !QUEUE_MACRO_DEBUG_TRASH */
|
||||
#define TRASHIT(x)
|
||||
#define QMD_IS_TRASHED(x) 0
|
||||
#endif /* QUEUE_MACRO_DEBUG_TRASH */
|
||||
|
||||
#if defined(QUEUE_MACRO_DEBUG_TRACE) || defined(QUEUE_MACRO_DEBUG_TRASH)
|
||||
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
|
||||
#else /* !QUEUE_MACRO_DEBUG_TRACE && !QUEUE_MACRO_DEBUG_TRASH */
|
||||
#define QMD_SAVELINK(name, link)
|
||||
#endif /* QUEUE_MACRO_DEBUG_TRACE || QUEUE_MACRO_DEBUG_TRASH */
|
||||
|
||||
#ifdef __cplusplus
|
||||
/*
|
||||
* In C++ there can be structure lists and class lists:
|
||||
*/
|
||||
#define QUEUE_TYPEOF(type) type
|
||||
#else
|
||||
#define QUEUE_TYPEOF(type) struct type
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Singly-linked List declarations.
|
||||
*/
|
||||
#define SLIST_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *slh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define SLIST_CLASS_HEAD(name, type) \
|
||||
struct name { \
|
||||
class type *slh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define SLIST_HEAD_INITIALIZER(head) \
|
||||
{ NULL }
|
||||
|
||||
#define SLIST_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *sle_next; /* next element */ \
|
||||
}
|
||||
|
||||
#define SLIST_CLASS_ENTRY(type) \
|
||||
struct { \
|
||||
class type *sle_next; /* next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* Singly-linked List functions.
|
||||
*/
|
||||
#if (defined(_KERNEL) && defined(INVARIANTS))
|
||||
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \
|
||||
if (*(prevp) != (elm)) \
|
||||
panic("Bad prevptr *(%p) == %p != %p", \
|
||||
(prevp), *(prevp), (elm)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define QMD_SLIST_CHECK_PREVPTR(prevp, elm)
|
||||
#endif
|
||||
|
||||
#define SLIST_CONCAT(head1, head2, type, field) do { \
|
||||
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \
|
||||
if (curelm == NULL) { \
|
||||
if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \
|
||||
SLIST_INIT(head2); \
|
||||
} else if (SLIST_FIRST(head2) != NULL) { \
|
||||
while (SLIST_NEXT(curelm, field) != NULL) \
|
||||
curelm = SLIST_NEXT(curelm, field); \
|
||||
SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \
|
||||
SLIST_INIT(head2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
|
||||
|
||||
#define SLIST_FIRST(head) ((head)->slh_first)
|
||||
|
||||
#define SLIST_FOREACH(var, head, field) \
|
||||
for ((var) = SLIST_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = SLIST_NEXT((var), field))
|
||||
|
||||
#define SLIST_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = SLIST_NEXT((var), field))
|
||||
|
||||
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = SLIST_FIRST((head)); \
|
||||
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
|
||||
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
|
||||
for ((varp) = &SLIST_FIRST((head)); \
|
||||
((var) = *(varp)) != NULL; \
|
||||
(varp) = &SLIST_NEXT((var), field))
|
||||
|
||||
#define SLIST_INIT(head) do { \
|
||||
SLIST_FIRST((head)) = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
|
||||
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
|
||||
SLIST_NEXT((slistelm), field) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_INSERT_HEAD(head, elm, field) do { \
|
||||
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
|
||||
SLIST_FIRST((head)) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
|
||||
|
||||
#define SLIST_REMOVE(head, elm, type, field) do { \
|
||||
QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
|
||||
if (SLIST_FIRST((head)) == (elm)) { \
|
||||
SLIST_REMOVE_HEAD((head), field); \
|
||||
} \
|
||||
else { \
|
||||
QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \
|
||||
while (SLIST_NEXT(curelm, field) != (elm)) \
|
||||
curelm = SLIST_NEXT(curelm, field); \
|
||||
SLIST_REMOVE_AFTER(curelm, field); \
|
||||
} \
|
||||
TRASHIT(*oldnext); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE_AFTER(elm, field) do { \
|
||||
SLIST_NEXT(elm, field) = \
|
||||
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE_HEAD(head, field) do { \
|
||||
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_REMOVE_PREVPTR(prevp, elm, field) do { \
|
||||
QMD_SLIST_CHECK_PREVPTR(prevp, elm); \
|
||||
*(prevp) = SLIST_NEXT(elm, field); \
|
||||
TRASHIT((elm)->field.sle_next); \
|
||||
} while (0)
|
||||
|
||||
#define SLIST_SWAP(head1, head2, type) do { \
|
||||
QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \
|
||||
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
|
||||
SLIST_FIRST(head2) = swap_first; \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Singly-linked Tail queue declarations.
|
||||
*/
|
||||
#define STAILQ_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *stqh_first;/* first element */ \
|
||||
struct type **stqh_last;/* addr of last next element */ \
|
||||
}
|
||||
|
||||
#define STAILQ_CLASS_HEAD(name, type) \
|
||||
struct name { \
|
||||
class type *stqh_first; /* first element */ \
|
||||
class type **stqh_last; /* addr of last next element */ \
|
||||
}
|
||||
|
||||
#define STAILQ_HEAD_INITIALIZER(head) \
|
||||
{ NULL, &(head).stqh_first }
|
||||
|
||||
#define STAILQ_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *stqe_next; /* next element */ \
|
||||
}
|
||||
|
||||
#define STAILQ_CLASS_ENTRY(type) \
|
||||
struct { \
|
||||
class type *stqe_next; /* next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* Singly-linked Tail queue functions.
|
||||
*/
|
||||
#define STAILQ_CONCAT(head1, head2) do { \
|
||||
if (!STAILQ_EMPTY((head2))) { \
|
||||
*(head1)->stqh_last = (head2)->stqh_first; \
|
||||
(head1)->stqh_last = (head2)->stqh_last; \
|
||||
STAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
|
||||
|
||||
#define STAILQ_FIRST(head) ((head)->stqh_first)
|
||||
|
||||
#define STAILQ_FOREACH(var, head, field) \
|
||||
for((var) = STAILQ_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = STAILQ_NEXT((var), field))
|
||||
|
||||
#define STAILQ_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = STAILQ_NEXT((var), field))
|
||||
|
||||
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = STAILQ_FIRST((head)); \
|
||||
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
|
||||
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define STAILQ_INIT(head) do { \
|
||||
STAILQ_FIRST((head)) = NULL; \
|
||||
(head)->stqh_last = &STAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
|
||||
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
STAILQ_NEXT((tqelm), field) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
|
||||
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
STAILQ_FIRST((head)) = (elm); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
|
||||
STAILQ_NEXT((elm), field) = NULL; \
|
||||
*(head)->stqh_last = (elm); \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_LAST(head, type, field) \
|
||||
(STAILQ_EMPTY((head)) ? NULL : \
|
||||
__containerof((head)->stqh_last, \
|
||||
QUEUE_TYPEOF(type), field.stqe_next))
|
||||
|
||||
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
|
||||
|
||||
#define STAILQ_REMOVE(head, elm, type, field) do { \
|
||||
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
|
||||
if (STAILQ_FIRST((head)) == (elm)) { \
|
||||
STAILQ_REMOVE_HEAD((head), field); \
|
||||
} \
|
||||
else { \
|
||||
QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \
|
||||
while (STAILQ_NEXT(curelm, field) != (elm)) \
|
||||
curelm = STAILQ_NEXT(curelm, field); \
|
||||
STAILQ_REMOVE_AFTER(head, curelm, field); \
|
||||
} \
|
||||
TRASHIT(*oldnext); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
|
||||
if ((STAILQ_NEXT(elm, field) = \
|
||||
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_REMOVE_HEAD(head, field) do { \
|
||||
if ((STAILQ_FIRST((head)) = \
|
||||
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
|
||||
(head)->stqh_last = &STAILQ_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define STAILQ_SWAP(head1, head2, type) do { \
|
||||
QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \
|
||||
QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \
|
||||
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
|
||||
(head1)->stqh_last = (head2)->stqh_last; \
|
||||
STAILQ_FIRST(head2) = swap_first; \
|
||||
(head2)->stqh_last = swap_last; \
|
||||
if (STAILQ_EMPTY(head1)) \
|
||||
(head1)->stqh_last = &STAILQ_FIRST(head1); \
|
||||
if (STAILQ_EMPTY(head2)) \
|
||||
(head2)->stqh_last = &STAILQ_FIRST(head2); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/*
|
||||
* List declarations.
|
||||
*/
|
||||
#define LIST_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *lh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define LIST_CLASS_HEAD(name, type) \
|
||||
struct name { \
|
||||
class type *lh_first; /* first element */ \
|
||||
}
|
||||
|
||||
#define LIST_HEAD_INITIALIZER(head) \
|
||||
{ NULL }
|
||||
|
||||
#define LIST_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *le_next; /* next element */ \
|
||||
struct type **le_prev; /* address of previous next element */ \
|
||||
}
|
||||
|
||||
#define LIST_CLASS_ENTRY(type) \
|
||||
struct { \
|
||||
class type *le_next; /* next element */ \
|
||||
class type **le_prev; /* address of previous next element */ \
|
||||
}
|
||||
|
||||
/*
|
||||
* List functions.
|
||||
*/
|
||||
|
||||
#if (defined(_KERNEL) && defined(INVARIANTS))
|
||||
/*
|
||||
* QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME)
|
||||
*
|
||||
* If the list is non-empty, validates that the first element of the list
|
||||
* points back at 'head.'
|
||||
*/
|
||||
#define QMD_LIST_CHECK_HEAD(head, field) do { \
|
||||
if (LIST_FIRST((head)) != NULL && \
|
||||
LIST_FIRST((head))->field.le_prev != \
|
||||
&LIST_FIRST((head))) \
|
||||
panic("Bad list head %p first->prev != head", (head)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME)
|
||||
*
|
||||
* If an element follows 'elm' in the list, validates that the next element
|
||||
* points back at 'elm.'
|
||||
*/
|
||||
#define QMD_LIST_CHECK_NEXT(elm, field) do { \
|
||||
if (LIST_NEXT((elm), field) != NULL && \
|
||||
LIST_NEXT((elm), field)->field.le_prev != \
|
||||
&((elm)->field.le_next)) \
|
||||
panic("Bad link elm %p next->prev != elm", (elm)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME)
|
||||
*
|
||||
* Validates that the previous element (or head of the list) points to 'elm.'
|
||||
*/
|
||||
#define QMD_LIST_CHECK_PREV(elm, field) do { \
|
||||
if (*(elm)->field.le_prev != (elm)) \
|
||||
panic("Bad link elm %p prev->next != elm", (elm)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define QMD_LIST_CHECK_HEAD(head, field)
|
||||
#define QMD_LIST_CHECK_NEXT(elm, field)
|
||||
#define QMD_LIST_CHECK_PREV(elm, field)
|
||||
#endif /* (_KERNEL && INVARIANTS) */
|
||||
|
||||
#define LIST_CONCAT(head1, head2, type, field) do { \
|
||||
QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \
|
||||
if (curelm == NULL) { \
|
||||
if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \
|
||||
LIST_FIRST(head2)->field.le_prev = \
|
||||
&LIST_FIRST((head1)); \
|
||||
LIST_INIT(head2); \
|
||||
} \
|
||||
} else if (LIST_FIRST(head2) != NULL) { \
|
||||
while (LIST_NEXT(curelm, field) != NULL) \
|
||||
curelm = LIST_NEXT(curelm, field); \
|
||||
LIST_NEXT(curelm, field) = LIST_FIRST(head2); \
|
||||
LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \
|
||||
LIST_INIT(head2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
|
||||
|
||||
#define LIST_FIRST(head) ((head)->lh_first)
|
||||
|
||||
#define LIST_FOREACH(var, head, field) \
|
||||
for ((var) = LIST_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = LIST_NEXT((var), field))
|
||||
|
||||
#define LIST_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = LIST_NEXT((var), field))
|
||||
|
||||
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = LIST_FIRST((head)); \
|
||||
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
|
||||
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define LIST_INIT(head) do { \
|
||||
LIST_FIRST((head)) = NULL; \
|
||||
} while (0)
|
||||
|
||||
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
|
||||
QMD_LIST_CHECK_NEXT(listelm, field); \
|
||||
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
|
||||
LIST_NEXT((listelm), field)->field.le_prev = \
|
||||
&LIST_NEXT((elm), field); \
|
||||
LIST_NEXT((listelm), field) = (elm); \
|
||||
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
|
||||
QMD_LIST_CHECK_PREV(listelm, field); \
|
||||
(elm)->field.le_prev = (listelm)->field.le_prev; \
|
||||
LIST_NEXT((elm), field) = (listelm); \
|
||||
*(listelm)->field.le_prev = (elm); \
|
||||
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_INSERT_HEAD(head, elm, field) do { \
|
||||
QMD_LIST_CHECK_HEAD((head), field); \
|
||||
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
|
||||
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
|
||||
LIST_FIRST((head)) = (elm); \
|
||||
(elm)->field.le_prev = &LIST_FIRST((head)); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
|
||||
|
||||
#define LIST_PREV(elm, head, type, field) \
|
||||
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
|
||||
__containerof((elm)->field.le_prev, \
|
||||
QUEUE_TYPEOF(type), field.le_next))
|
||||
|
||||
#define LIST_REMOVE(elm, field) do { \
|
||||
QMD_SAVELINK(oldnext, (elm)->field.le_next); \
|
||||
QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
|
||||
QMD_LIST_CHECK_NEXT(elm, field); \
|
||||
QMD_LIST_CHECK_PREV(elm, field); \
|
||||
if (LIST_NEXT((elm), field) != NULL) \
|
||||
LIST_NEXT((elm), field)->field.le_prev = \
|
||||
(elm)->field.le_prev; \
|
||||
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
|
||||
TRASHIT(*oldnext); \
|
||||
TRASHIT(*oldprev); \
|
||||
} while (0)
|
||||
|
||||
#define LIST_SWAP(head1, head2, type, field) do { \
|
||||
QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \
|
||||
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
|
||||
LIST_FIRST((head2)) = swap_tmp; \
|
||||
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
|
||||
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
|
||||
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
|
||||
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Tail queue declarations.
|
||||
*/
|
||||
#define TAILQ_HEAD(name, type) \
|
||||
struct name { \
|
||||
struct type *tqh_first; /* first element */ \
|
||||
struct type **tqh_last; /* addr of last next element */ \
|
||||
TRACEBUF \
|
||||
}
|
||||
|
||||
#define TAILQ_CLASS_HEAD(name, type) \
|
||||
struct name { \
|
||||
class type *tqh_first; /* first element */ \
|
||||
class type **tqh_last; /* addr of last next element */ \
|
||||
TRACEBUF \
|
||||
}
|
||||
|
||||
#define TAILQ_HEAD_INITIALIZER(head) \
|
||||
{ NULL, &(head).tqh_first, TRACEBUF_INITIALIZER }
|
||||
|
||||
#define TAILQ_ENTRY(type) \
|
||||
struct { \
|
||||
struct type *tqe_next; /* next element */ \
|
||||
struct type **tqe_prev; /* address of previous next element */ \
|
||||
TRACEBUF \
|
||||
}
|
||||
|
||||
#define TAILQ_CLASS_ENTRY(type) \
|
||||
struct { \
|
||||
class type *tqe_next; /* next element */ \
|
||||
class type **tqe_prev; /* address of previous next element */ \
|
||||
TRACEBUF \
|
||||
}
|
||||
|
||||
/*
|
||||
* Tail queue functions.
|
||||
*/
|
||||
#if (defined(_KERNEL) && defined(INVARIANTS))
|
||||
/*
|
||||
* QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
|
||||
*
|
||||
* If the tailq is non-empty, validates that the first element of the tailq
|
||||
* points back at 'head.'
|
||||
*/
|
||||
#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
|
||||
if (!TAILQ_EMPTY(head) && \
|
||||
TAILQ_FIRST((head))->field.tqe_prev != \
|
||||
&TAILQ_FIRST((head))) \
|
||||
panic("Bad tailq head %p first->prev != head", (head)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME)
|
||||
*
|
||||
* Validates that the tail of the tailq is a pointer to pointer to NULL.
|
||||
*/
|
||||
#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
|
||||
if (*(head)->tqh_last != NULL) \
|
||||
panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME)
|
||||
*
|
||||
* If an element follows 'elm' in the tailq, validates that the next element
|
||||
* points back at 'elm.'
|
||||
*/
|
||||
#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
|
||||
if (TAILQ_NEXT((elm), field) != NULL && \
|
||||
TAILQ_NEXT((elm), field)->field.tqe_prev != \
|
||||
&((elm)->field.tqe_next)) \
|
||||
panic("Bad link elm %p next->prev != elm", (elm)); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME)
|
||||
*
|
||||
* Validates that the previous element (or head of the tailq) points to 'elm.'
|
||||
*/
|
||||
#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
|
||||
if (*(elm)->field.tqe_prev != (elm)) \
|
||||
panic("Bad link elm %p prev->next != elm", (elm)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define QMD_TAILQ_CHECK_HEAD(head, field)
|
||||
#define QMD_TAILQ_CHECK_TAIL(head, headname)
|
||||
#define QMD_TAILQ_CHECK_NEXT(elm, field)
|
||||
#define QMD_TAILQ_CHECK_PREV(elm, field)
|
||||
#endif /* (_KERNEL && INVARIANTS) */
|
||||
|
||||
#define TAILQ_CONCAT(head1, head2, field) do { \
|
||||
if (!TAILQ_EMPTY(head2)) { \
|
||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
TAILQ_INIT((head2)); \
|
||||
QMD_TRACE_HEAD(head1); \
|
||||
QMD_TRACE_HEAD(head2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
|
||||
|
||||
#define TAILQ_FIRST(head) ((head)->tqh_first)
|
||||
|
||||
#define TAILQ_FOREACH(var, head, field) \
|
||||
for ((var) = TAILQ_FIRST((head)); \
|
||||
(var); \
|
||||
(var) = TAILQ_NEXT((var), field))
|
||||
|
||||
#define TAILQ_FOREACH_FROM(var, head, field) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
|
||||
(var); \
|
||||
(var) = TAILQ_NEXT((var), field))
|
||||
|
||||
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = TAILQ_FIRST((head)); \
|
||||
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
|
||||
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
|
||||
for ((var) = TAILQ_LAST((head), headname); \
|
||||
(var); \
|
||||
(var) = TAILQ_PREV((var), headname, field))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
|
||||
(var); \
|
||||
(var) = TAILQ_PREV((var), headname, field))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
|
||||
for ((var) = TAILQ_LAST((head), headname); \
|
||||
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
|
||||
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
|
||||
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
|
||||
(var) = (tvar))
|
||||
|
||||
#define TAILQ_INIT(head) do { \
|
||||
TAILQ_FIRST((head)) = NULL; \
|
||||
(head)->tqh_last = &TAILQ_FIRST((head)); \
|
||||
QMD_TRACE_HEAD(head); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
|
||||
QMD_TAILQ_CHECK_NEXT(listelm, field); \
|
||||
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
|
||||
TAILQ_NEXT((elm), field)->field.tqe_prev = \
|
||||
&TAILQ_NEXT((elm), field); \
|
||||
else { \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
QMD_TRACE_HEAD(head); \
|
||||
} \
|
||||
TAILQ_NEXT((listelm), field) = (elm); \
|
||||
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
|
||||
QMD_TRACE_ELEM(&(elm)->field); \
|
||||
QMD_TRACE_ELEM(&(listelm)->field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
|
||||
QMD_TAILQ_CHECK_PREV(listelm, field); \
|
||||
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
|
||||
TAILQ_NEXT((elm), field) = (listelm); \
|
||||
*(listelm)->field.tqe_prev = (elm); \
|
||||
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
|
||||
QMD_TRACE_ELEM(&(elm)->field); \
|
||||
QMD_TRACE_ELEM(&(listelm)->field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
|
||||
QMD_TAILQ_CHECK_HEAD(head, field); \
|
||||
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
|
||||
TAILQ_FIRST((head))->field.tqe_prev = \
|
||||
&TAILQ_NEXT((elm), field); \
|
||||
else \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
TAILQ_FIRST((head)) = (elm); \
|
||||
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
|
||||
QMD_TRACE_HEAD(head); \
|
||||
QMD_TRACE_ELEM(&(elm)->field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
|
||||
QMD_TAILQ_CHECK_TAIL(head, field); \
|
||||
TAILQ_NEXT((elm), field) = NULL; \
|
||||
(elm)->field.tqe_prev = (head)->tqh_last; \
|
||||
*(head)->tqh_last = (elm); \
|
||||
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
|
||||
QMD_TRACE_HEAD(head); \
|
||||
QMD_TRACE_ELEM(&(elm)->field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_LAST(head, headname) \
|
||||
(*(((struct headname *)((head)->tqh_last))->tqh_last))
|
||||
|
||||
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
|
||||
|
||||
#define TAILQ_PREV(elm, headname, field) \
|
||||
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
|
||||
|
||||
#define TAILQ_REMOVE(head, elm, field) do { \
|
||||
QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
|
||||
QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
|
||||
QMD_TAILQ_CHECK_NEXT(elm, field); \
|
||||
QMD_TAILQ_CHECK_PREV(elm, field); \
|
||||
if ((TAILQ_NEXT((elm), field)) != NULL) \
|
||||
TAILQ_NEXT((elm), field)->field.tqe_prev = \
|
||||
(elm)->field.tqe_prev; \
|
||||
else { \
|
||||
(head)->tqh_last = (elm)->field.tqe_prev; \
|
||||
QMD_TRACE_HEAD(head); \
|
||||
} \
|
||||
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
|
||||
TRASHIT(*oldnext); \
|
||||
TRASHIT(*oldprev); \
|
||||
QMD_TRACE_ELEM(&(elm)->field); \
|
||||
} while (0)
|
||||
|
||||
#define TAILQ_SWAP(head1, head2, type, field) do { \
|
||||
QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \
|
||||
QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \
|
||||
(head1)->tqh_first = (head2)->tqh_first; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
(head2)->tqh_first = swap_first; \
|
||||
(head2)->tqh_last = swap_last; \
|
||||
if ((swap_first = (head1)->tqh_first) != NULL) \
|
||||
swap_first->field.tqe_prev = &(head1)->tqh_first; \
|
||||
else \
|
||||
(head1)->tqh_last = &(head1)->tqh_first; \
|
||||
if ((swap_first = (head2)->tqh_first) != NULL) \
|
||||
swap_first->field.tqe_prev = &(head2)->tqh_first; \
|
||||
else \
|
||||
(head2)->tqh_last = &(head2)->tqh_first; \
|
||||
} while (0)
|
||||
|
||||
#endif /* !_SYS_QUEUE_H_ */
|
211
libmish/src/fifo_declare.h
Normal file
211
libmish/src/fifo_declare.h
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
fido_declare.h
|
||||
Copyright (C) 2003-2012 Michel Pollet <buserror@gmail.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* FIFO helpers, aka circular buffers
|
||||
*
|
||||
* these macros define accessories for FIFOs of any name, type and
|
||||
* any (power of two) size
|
||||
*/
|
||||
|
||||
#ifndef __FIFO_DECLARE__
|
||||
#define __FIFO_DECLARE__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
doing a :
|
||||
DECLARE_FIFO(uint8_t, myfifo, 128);
|
||||
|
||||
will declare :
|
||||
enum : myfifo_overflow_f
|
||||
type : myfifo_t
|
||||
functions:
|
||||
// write a byte into the fifo, return 1 if there was room, 0 if there wasn't
|
||||
int myfifo_write(myfifo_t *c, uint8_t b);
|
||||
// reads a byte from the fifo, return 0 if empty. Use myfifo_isempty() to check beforehand
|
||||
uint8_t myfifo_read(myfifo_t *c);
|
||||
int myfifo_isfull(myfifo_t *c);
|
||||
int myfifo_isempty(myfifo_t *c);
|
||||
// returns number of items to read now
|
||||
uint16_t myfifo_get_read_size(myfifo_t *c);
|
||||
// read item at offset o from read cursor, no cursor advance
|
||||
uint8_t myfifo_read_at(myfifo_t *c, uint16_t o);
|
||||
// write b at offset o compared to current write cursor, no cursor advance
|
||||
void myfifo_write_at(myfifo_t *c, uint16_t o, uint8_t b);
|
||||
|
||||
In your .c you need to 'implement' the fifo:
|
||||
DEFINE_FIFO(uint8_t, myfifo)
|
||||
|
||||
To use the fifo, you must declare at least one :
|
||||
myfifo_t fifo = FIFO_NULL;
|
||||
|
||||
while (!myfifo_isfull(&fifo))
|
||||
myfifo_write(&fifo, 0xaa);
|
||||
....
|
||||
while (!myfifo_isempty(&fifo))
|
||||
b = myfifo_read(&fifo);
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __AVR__
|
||||
#define FIFO_CURSOR_TYPE uint8_t
|
||||
#define FIFO_BOOL_TYPE char
|
||||
#define FIFO_INLINE
|
||||
#define FIFO_SYNC
|
||||
#endif
|
||||
|
||||
#ifndef FIFO_CURSOR_TYPE
|
||||
#define FIFO_CURSOR_TYPE uint16_t
|
||||
#endif
|
||||
#ifndef FIFO_BOOL_TYPE
|
||||
#define FIFO_BOOL_TYPE int
|
||||
#endif
|
||||
#ifndef FIFO_INLINE
|
||||
#define FIFO_INLINE inline
|
||||
#endif
|
||||
|
||||
/* We should not need volatile */
|
||||
#ifndef FIFO_VOLATILE
|
||||
#define FIFO_VOLATILE
|
||||
#endif
|
||||
#ifndef FIFO_SYNC
|
||||
#define FIFO_SYNC __sync_synchronize()
|
||||
#endif
|
||||
|
||||
#ifndef FIFO_ZERO_INIT
|
||||
#define FIFO_ZERO_INIT {0}
|
||||
#endif
|
||||
#define FIFO_NULL { FIFO_ZERO_INIT, 0, 0, 0 }
|
||||
|
||||
/* New compilers don't like unused static functions. However,
|
||||
* we do like 'static inlines' for these small accessors,
|
||||
* so we mark them as 'unused'. It stops it complaining */
|
||||
#ifdef __GNUC__
|
||||
#define FIFO_DECL static __attribute__ ((unused))
|
||||
#else
|
||||
#define FIFO_DECL static
|
||||
#endif
|
||||
|
||||
#define DECLARE_FIFO(__type, __name, __size) \
|
||||
enum { __name##_overflow_f = (1 << 0) }; \
|
||||
enum { __name##_fifo_size = (__size) }; \
|
||||
typedef struct __name##_t { \
|
||||
__type buffer[__name##_fifo_size]; \
|
||||
FIFO_VOLATILE FIFO_CURSOR_TYPE read; \
|
||||
FIFO_VOLATILE FIFO_CURSOR_TYPE write; \
|
||||
FIFO_VOLATILE uint8_t flags; \
|
||||
} __name##_t
|
||||
|
||||
/*
|
||||
* This allow declaring a FIFO without the functions that use
|
||||
* pass-by-value arguments. The compiler isn't too happy using
|
||||
* some of the variable length array these days and outputs a warning.
|
||||
* Also, it is not efficient, so if your FIFO member is a big struct,
|
||||
* you should use the pass by pointer instead.
|
||||
*/
|
||||
#define DEFINE_PTR_FIFO(__type, __name) \
|
||||
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isfull(__name##_t *c)\
|
||||
{\
|
||||
FIFO_CURSOR_TYPE next = (c->write + 1) & (__name##_fifo_size-1);\
|
||||
return c->read == next;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isempty(__name##_t * c)\
|
||||
{\
|
||||
return c->read == c->write;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_read_size(__name##_t *c)\
|
||||
{\
|
||||
return ((c->write + __name##_fifo_size) - c->read) & (__name##_fifo_size-1);\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_write_size(__name##_t *c)\
|
||||
{\
|
||||
return (__name##_fifo_size-1) - __name##_get_read_size(c);\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE void __name##_read_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\
|
||||
{\
|
||||
FIFO_SYNC; \
|
||||
c->read = (c->read + o) & (__name##_fifo_size-1);\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE void __name##_write_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\
|
||||
{\
|
||||
FIFO_SYNC; \
|
||||
c->write = (c->write + o) & (__name##_fifo_size-1);\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE void __name##_reset(__name##_t *c)\
|
||||
{\
|
||||
FIFO_SYNC; \
|
||||
c->read = c->write = c->flags = 0;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE __type * __name##_write_ptr(__name##_t * c) \
|
||||
{\
|
||||
return c->buffer + c->write;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE __type * __name##_read_ptr(__name##_t * c) \
|
||||
{\
|
||||
return c->buffer + c->read;\
|
||||
}\
|
||||
struct __name##_t
|
||||
|
||||
/*
|
||||
* And this declares the whole FIFO, including the pass-by-value functions
|
||||
*/
|
||||
#define DEFINE_FIFO(__type, __name) \
|
||||
DEFINE_PTR_FIFO(__type, __name); \
|
||||
FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_write(__name##_t * c, __type b)\
|
||||
{\
|
||||
FIFO_CURSOR_TYPE now = c->write;\
|
||||
FIFO_CURSOR_TYPE next = (now + 1) & (__name##_fifo_size-1);\
|
||||
if (c->read != next) { \
|
||||
c->buffer[now] = b;\
|
||||
FIFO_SYNC; \
|
||||
c->write = next;\
|
||||
return 1;\
|
||||
}\
|
||||
return 0;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE __type __name##_read(__name##_t * c)\
|
||||
{\
|
||||
__type res = FIFO_ZERO_INIT; \
|
||||
FIFO_CURSOR_TYPE read = c->read;\
|
||||
if (read == c->write)\
|
||||
return res;\
|
||||
res = c->buffer[read];\
|
||||
FIFO_SYNC; \
|
||||
c->read = (read + 1) & (__name##_fifo_size-1);\
|
||||
return res;\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE __type __name##_read_at(__name##_t *c, FIFO_CURSOR_TYPE o)\
|
||||
{\
|
||||
return c->buffer[(c->read + o) & (__name##_fifo_size-1)];\
|
||||
}\
|
||||
FIFO_DECL FIFO_INLINE void __name##_write_at(__name##_t *c, FIFO_CURSOR_TYPE o, __type b)\
|
||||
{\
|
||||
c->buffer[(c->write + o) & (__name##_fifo_size-1)] = b;\
|
||||
}\
|
||||
struct __name##_t
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
138
libmish/src/minipt.h
Normal file
138
libmish/src/minipt.h
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* minipt.h
|
||||
*
|
||||
* Created on: 1 Apr 2018
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef MPTOOLS_INCLUDE_MINIPT_H_
|
||||
#define MPTOOLS_INCLUDE_MINIPT_H_
|
||||
|
||||
/*
|
||||
* Mini Protothread.
|
||||
*
|
||||
* A thread or a coroutine would use a stack; this won't,
|
||||
* Use an old gcc trick of being able to goto and indirect label.
|
||||
* There are a few caveats: no persistent local variables, as you can't
|
||||
* have a consistent stack frame. It's easy to work around tho.
|
||||
*/
|
||||
#define _CONCAT2(s1, s2) s1##s2
|
||||
#define _CONCAT(s1, s2) _CONCAT2(s1, s2)
|
||||
|
||||
/* this wierd thing with the union is for gcc 12, which doesn't like us
|
||||
* storing the address of a 'local variable' (which is the label!) */
|
||||
static inline void _set_gcc_ptr_workaround(void **d, void *s) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdangling-pointer"
|
||||
*d = s;
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
#define pt_start(_pt) do { \
|
||||
if (_pt) goto *_pt; \
|
||||
} while (0);
|
||||
#define pt_end(_pt) do { \
|
||||
(_pt) = NULL; \
|
||||
_pt_exit: ; \
|
||||
} while(0);
|
||||
#define pt_yield(_pt) do { \
|
||||
_set_gcc_ptr_workaround(&(_pt), &&_CONCAT(_label, __LINE__));\
|
||||
goto _pt_exit;\
|
||||
_CONCAT(_label, __LINE__): ; \
|
||||
} while (0);
|
||||
|
||||
#define pt_wait(_pt, _condition) do { \
|
||||
while (!(_condition)) \
|
||||
pt_yield(_pt); \
|
||||
} while (0);
|
||||
|
||||
|
||||
#ifdef NEVER
|
||||
/*
|
||||
* This version is superior as it allows calling functions and keeping
|
||||
* a context, but I never actually had a /need/ for this, yet
|
||||
*/
|
||||
|
||||
struct pt_t {
|
||||
unsigned int sp;
|
||||
void * st[32];
|
||||
void * ctx[32];
|
||||
} pt_t;
|
||||
|
||||
#define pt_start(_pt) do { \
|
||||
if ((_pt)->st[(_pt)->sp]) goto *((_pt)->st[(_pt)->sp]); \
|
||||
} while (0);
|
||||
#define pt_end(_pt) do { (_pt)->st[(_pt)->sp] = NULL; return; } while(0);
|
||||
#define pt_yield(_pt) do { \
|
||||
(_pt)->st[(_pt)->sp] = &&_CONCAT(_label, __LINE__);\
|
||||
return;\
|
||||
_CONCAT(_label, __LINE__): ; \
|
||||
} while (0);
|
||||
#define pt_wait(_pt, _condition) do { \
|
||||
while (!(_condition)) \
|
||||
pt_yield(_pt); \
|
||||
} while (0);
|
||||
#define pt_call(_pt, _func) do { \
|
||||
(_pt)->sp++; \
|
||||
(_pt)->st[(_pt)->sp] = NULL; \
|
||||
do { \
|
||||
_func(_pt); \
|
||||
} while ((_pt)->st[(_pt)->sp]); \
|
||||
(_pt)->sp--; \
|
||||
} while (0);
|
||||
#define pt_ctx(_pt) ((_pt)->ctx[(_pt)->sp])
|
||||
|
||||
void my_minit_func(struct pt_t * p) {
|
||||
pt_start(p);
|
||||
pt_ctx(p) = calloc(1, sizeof(int));
|
||||
printf("%s start %p\n", __func__, pt_ctx(p));
|
||||
pt_yield(p);
|
||||
int * ctx = pt_ctx(p);
|
||||
printf("%s loop %p\n", __func__, pt_ctx(p));
|
||||
for (; *ctx < 10; ctx[0]++) {
|
||||
printf(" loop %d\n", *ctx);
|
||||
pt_yield(p);
|
||||
ctx = pt_ctx(p);
|
||||
}
|
||||
printf("%s done %p\n", __func__, pt_ctx(p));
|
||||
free(pt_ctx(p));
|
||||
pt_ctx(p) = NULL;
|
||||
pt_end(p);
|
||||
}
|
||||
|
||||
void my_minit(struct pt_t * p) {
|
||||
pt_start(p);
|
||||
printf("%s start\n", __func__);
|
||||
pt_call(p, my_minit_func);
|
||||
printf("%s done\n", __func__);
|
||||
pt_end(p);
|
||||
}
|
||||
|
||||
int main() {
|
||||
struct pt_t pt = {};
|
||||
|
||||
pt_call(&pt, my_minit);
|
||||
}
|
||||
/*
|
||||
tcc -run pt_call_test.c
|
||||
my_minit start
|
||||
my_minit_func start 0x555a68d970b0
|
||||
my_minit_func loop 0x555a68d970b0
|
||||
loop 0
|
||||
loop 1
|
||||
loop 2
|
||||
loop 3
|
||||
loop 4
|
||||
loop 5
|
||||
loop 6
|
||||
loop 7
|
||||
loop 8
|
||||
loop 9
|
||||
my_minit_func done 0x555a68d970b0
|
||||
my_minit done
|
||||
*/
|
||||
|
||||
#endif // NEVER
|
||||
|
||||
#endif /* MPTOOLS_INCLUDE_MINIPT_H_ */
|
113
libmish/src/mish.h
Normal file
113
libmish/src/mish.h
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* mish.h
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef LIBMISH_SRC_MISH_H_
|
||||
#define LIBMISH_SRC_MISH_H_
|
||||
|
||||
#ifndef _LIBMISH_HAS_CMD_HANDLER_
|
||||
/*
|
||||
* If you register with a non NULL parameter, you get that, otherwise
|
||||
* you get an (opaque) mish_client_p
|
||||
*/
|
||||
typedef void (*mish_cmd_handler_p)(
|
||||
void * param,
|
||||
int argc,
|
||||
const char *argv[]);
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MISH_CAP_NO_STDERR = (1 << 0),
|
||||
MISH_CAP_NO_TELNET = (1 << 2),
|
||||
MISH_CAP_FORCE_PTY = (1 << 1),
|
||||
};
|
||||
|
||||
/*!
|
||||
* Starts the mish thread, recover the stdout/err discriptor, and starts
|
||||
* displaying a prompt for your process.
|
||||
*/
|
||||
struct mish_t *
|
||||
mish_prepare(
|
||||
unsigned long caps );
|
||||
//! Returns the flags you passed to mish_prepare()
|
||||
unsigned long
|
||||
mish_get_flags(
|
||||
struct mish_t * m);
|
||||
/*!
|
||||
* This is normally called automatically at exit() time
|
||||
*/
|
||||
void
|
||||
mish_terminate(
|
||||
struct mish_t * m);
|
||||
|
||||
/*
|
||||
* Register a command; please use the macros, don't call this directly.
|
||||
*
|
||||
* Symbol is weak, so you can have mish commands in a dynamic library,
|
||||
* without having to drag libmish into programs that use it; the commands
|
||||
* won't register, but if you then link with a program that uses libmish,
|
||||
* they will.
|
||||
*/
|
||||
void
|
||||
mish_register_cmd(
|
||||
const char ** cmd_names,
|
||||
const char ** cmd_help,
|
||||
mish_cmd_handler_p cmd_handler,
|
||||
void * handler_param,
|
||||
int safe) __attribute__((weak));
|
||||
/*!
|
||||
* Poll the mish threads for pending commands, and call their handlers.
|
||||
* This is only necessary for 'safe' commands that needs to be run on the
|
||||
* main thread
|
||||
*/
|
||||
int
|
||||
mish_cmd_poll();
|
||||
|
||||
/*
|
||||
* This is how to add a command to your program:
|
||||
*
|
||||
* #include <mish.h>
|
||||
*
|
||||
* static void _my_command(void * yours, int argc, const char * argv[] ) {
|
||||
* // do something here, like in main()
|
||||
* }
|
||||
*
|
||||
* MISH_CMD_NAMES(_my_cmd, "cmd", "cmd_alias");
|
||||
* MISH_CMD_HELP(_my_cmd, "Short help one liner", "Extra help for help <cmd>")
|
||||
* MISH_CMD_REGISTER(_my_cmd, _my_command);
|
||||
*
|
||||
* That's it!
|
||||
*/
|
||||
|
||||
#define MISH_CMD_NAMES(_n, args...) \
|
||||
static const char * _cmd_##_n[] = { args, 0 }
|
||||
#define MISH_CMD_HELP(_n, args...) \
|
||||
static const char * _help_##_n[] = { args, 0 }
|
||||
/* This gymnastic is required becase GCC has decided that it'll ignore
|
||||
* ((weak)) symbols can be NULL, and throw me an error instead */
|
||||
static inline int _gcc_warning_false_pos_workaround(void * func) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Waddress"
|
||||
return func != NULL;
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
//! These are called on libmish thread, immediately.
|
||||
#define MISH_CMD_REGISTER(_d, _handler) \
|
||||
__attribute__((constructor,used)) \
|
||||
static void _mish_register_##_d() { \
|
||||
if (_gcc_warning_false_pos_workaround(mish_register_cmd)) \
|
||||
mish_register_cmd(_cmd_##_d,_help_##_d,_handler,0,0);\
|
||||
}
|
||||
//! These are called when the main program calls mish_cmd_poll()
|
||||
#define MISH_CMD_REGISTER_SAFE(_d, _handler) \
|
||||
__attribute__((constructor,used)) \
|
||||
static void _mish_register_##_d() { \
|
||||
if (_gcc_warning_false_pos_workaround(mish_register_cmd)) \
|
||||
mish_register_cmd(_cmd_##_d,_help_##_d,_handler,0,1);\
|
||||
}
|
||||
|
||||
#endif /* LIBMISH_SRC_MISH_H_ */
|
86
libmish/src/mish_capture_select.c
Normal file
86
libmish/src/mish_capture_select.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* mish_capture_select.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include "mish_priv.h"
|
||||
#include "mish.h"
|
||||
|
||||
/*
|
||||
* This is a select() based capture thread, it's not /ideal/ in terms of
|
||||
* performances, but it's portable and should work on OSX/BSD Linux etc.
|
||||
*/
|
||||
void *
|
||||
_mish_capture_select(
|
||||
void *param)
|
||||
{
|
||||
mish_p m = param;
|
||||
|
||||
while (!(m->flags & MISH_QUIT)) {
|
||||
mish_client_p c;
|
||||
/*
|
||||
* Call each client state machine, handle new output,
|
||||
* new input, draw prompts etc etc.
|
||||
* Also allow clients to tweak their 'output request' flag here
|
||||
*/
|
||||
TAILQ_FOREACH(c, &m->clients, self)
|
||||
c->cr.process(m, c);
|
||||
|
||||
fd_set r = m->select.read;
|
||||
fd_set w = m->select.write;
|
||||
struct timeval tv = { .tv_sec = 1 };
|
||||
int max = select(m->select.max, &r, &w, NULL, &tv);
|
||||
if (max == 0)
|
||||
continue;
|
||||
if (max == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
// real error here?
|
||||
continue;
|
||||
}
|
||||
/* check the telnet listen socket */
|
||||
mish_telnet_in_check(m, &r);
|
||||
/*
|
||||
* Get any input from the original terminals, it has been split
|
||||
* into lines already and queue all into the main backlog.
|
||||
*/
|
||||
for (int i = 0; i < 1 + !(m->flags & MISH_CAP_NO_STDERR); i++) {
|
||||
_mish_input_read(m, &r, &m->origin[i]);
|
||||
|
||||
mish_line_p l;
|
||||
while ((l = TAILQ_FIRST(&m->origin[i].backlog)) != NULL) {
|
||||
l->err = i == 1; // mark stderr as such
|
||||
TAILQ_REMOVE(&m->origin[i].backlog, l, self);
|
||||
TAILQ_INSERT_TAIL(&m->backlog.log, l, self);
|
||||
m->backlog.size++;
|
||||
m->backlog.alloc += sizeof(*l) + l->size;
|
||||
}
|
||||
}
|
||||
mish_client_p safe;
|
||||
// check if any client has input, or was closed down, and remove them
|
||||
TAILQ_FOREACH_SAFE(c, &m->clients, self, safe) {
|
||||
_mish_input_read(m, &r, &c->input);
|
||||
if (c->input.fd == -1 || (c->flags & MISH_CLIENT_DELETE))
|
||||
mish_client_delete(m, c);
|
||||
}
|
||||
}
|
||||
if ((m->flags & MISH_CONSOLE_TTY) &&
|
||||
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
|
||||
perror("thread tcsetattr");
|
||||
/*
|
||||
* Try to be nice and tell all clients to cleanup
|
||||
*/
|
||||
mish_client_p c;
|
||||
while ((c = TAILQ_FIRST(&m->clients)) != NULL)
|
||||
mish_client_delete(m, c);
|
||||
m->flags &= ~MISH_QUIT;
|
||||
m->capture = 0; // mark the thread done
|
||||
exit(0); // this calls mish_terminate, on main thread
|
||||
// return NULL;
|
||||
}
|
384
libmish/src/mish_client.c
Normal file
384
libmish/src/mish_client.c
Normal file
@ -0,0 +1,384 @@
|
||||
/*
|
||||
* mish_client.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "mish_priv.h"
|
||||
#include "mish.h"
|
||||
#include "minipt.h"
|
||||
|
||||
|
||||
mish_client_p
|
||||
mish_client_new(
|
||||
mish_p m,
|
||||
int in,
|
||||
int out,
|
||||
int is_tty)
|
||||
{
|
||||
mish_client_p c = (mish_client_p)calloc(1, sizeof(*c));
|
||||
|
||||
_mish_input_init(m, &c->input, in);
|
||||
if (is_tty) {
|
||||
c->input.process_char = _mish_client_vt_parse_input;
|
||||
c->cr.process = _mish_client_interractive_cr;
|
||||
} else {
|
||||
c->cr.process = _mish_client_dumb_cr;
|
||||
}
|
||||
c->input.refcon = c;
|
||||
c->output.fd = out;
|
||||
c->mish = m; // pointer to parent
|
||||
c->footer_height = 2;
|
||||
/*
|
||||
* Mark the in & out descriptors (could be the same, if a socket)
|
||||
* as non blocking.
|
||||
*/
|
||||
int fds[2] = { in, out};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int fd = fds[i];
|
||||
if (fd >= m->select.max - 1)
|
||||
m->select.max = fd +1;
|
||||
int flags = fcntl(fd, F_GETFL, NULL);
|
||||
if (flags == 1) {
|
||||
perror("mish: input F_GETFL");
|
||||
flags = 0;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
|
||||
perror("mish: input F_SETFL");
|
||||
}
|
||||
|
||||
TAILQ_INSERT_TAIL(&m->clients, c, self);
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
mish_client_delete(
|
||||
mish_p m,
|
||||
mish_client_p c)
|
||||
{
|
||||
/*
|
||||
* Turn back synchronous IO here, I don't want to have to deal with EAGAIN
|
||||
* when I'm actually trying to be nice and close as quickly as I can
|
||||
*/
|
||||
int fds[2] = { c->input.fd, c->output.fd };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (fds[i] == -1)
|
||||
continue;
|
||||
FD_CLR(fds[i], &m->select.read);
|
||||
FD_CLR(fds[i], &m->select.write);
|
||||
int flags;
|
||||
if (fcntl(fds[i], F_GETFL, &flags) == 0) {
|
||||
flags &= ~O_NONBLOCK;
|
||||
if (fcntl(fds[i], F_SETFL, &flags))
|
||||
/* don't care */;
|
||||
}
|
||||
}
|
||||
const char * restore = "\033[4l\033[;r\033[999;1H";
|
||||
if (write(c->output.fd, restore, strlen(restore)))
|
||||
;
|
||||
close(c->output.fd);
|
||||
|
||||
TAILQ_REMOVE(&m->clients, c, self);
|
||||
if (c == m->console)
|
||||
m->console = NULL;
|
||||
_mish_input_clear(m, &c->input);
|
||||
free(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* this calculates the number of glyphs in the prompt, it's needed
|
||||
* so we know where to position the cursor when displaying the input line.
|
||||
* Since prompt can have escape sequences like colors, or UTF8 glyphs
|
||||
*/
|
||||
static void
|
||||
_mish_client_set_prompt(
|
||||
mish_client_p c,
|
||||
const char *p)
|
||||
{
|
||||
if (p != c->prompt)
|
||||
strncpy(c->prompt, p, sizeof(c->prompt)-1);
|
||||
|
||||
mish_vt_sequence_t sq = {};
|
||||
char *s = c->prompt;
|
||||
c->prompt_gc = 0;
|
||||
while (*s) {
|
||||
if (_mish_vt_sequence_char(&sq, *s))
|
||||
if (sq.glyph)
|
||||
c->prompt_gc++;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remember, NO LOCALS in here -- this is a coroutine with no stack!
|
||||
*
|
||||
* Interactive 'client' prompt handling
|
||||
*/
|
||||
void
|
||||
_mish_client_interractive_cr(
|
||||
mish_p m,
|
||||
mish_client_p c)
|
||||
{
|
||||
pt_start(c->cr.state);
|
||||
|
||||
if (c->input.is_telnet)
|
||||
mish_telnet_send_init(c);
|
||||
/*
|
||||
* move cursor to 999;999 then asks the cursor position. That tells
|
||||
* us the window size! (handy)
|
||||
*/
|
||||
_mish_send_queue(c,
|
||||
"\033[999;999H\033[6n");
|
||||
while (_mish_send_flush(m, c))
|
||||
pt_yield(c->cr.state);
|
||||
/* remember when we started */
|
||||
c->output.sqb->stamp = _mish_stamp_ms();
|
||||
/* Wait for the input handler to get the right sequence */
|
||||
/* can be either the terminal response OR the telnet sequence */
|
||||
while (!(c->flags &
|
||||
(MISH_CLIENT_HAS_CURSOR_POS | MISH_CLIENT_HAS_WINDOW_SIZE)) &&
|
||||
(_mish_stamp_ms() - c->output.sqb->stamp) < (2 * 1000))
|
||||
pt_yield(c->cr.state);
|
||||
|
||||
if (c->flags & MISH_CLIENT_HAS_CURSOR_POS) {
|
||||
c->window_size.w = c->cursor_pos.x;
|
||||
c->window_size.h = c->cursor_pos.y;
|
||||
c->flags |= MISH_CLIENT_HAS_WINDOW_SIZE;
|
||||
}
|
||||
if (!(c->flags & MISH_CLIENT_HAS_WINDOW_SIZE)) {
|
||||
printf("mish: no window size, falling back to dumb scrollback\n");
|
||||
c->cr.process = _mish_client_dumb_cr;
|
||||
goto finish;
|
||||
}
|
||||
/* We are live scrolling, and we are at the last line of scrollback */
|
||||
c->flags |= MISH_CLIENT_INIT_SENT | MISH_CLIENT_SCROLLING;
|
||||
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
|
||||
/*
|
||||
* This is where we arrive to draw the entire screen; to start up,
|
||||
* and each time you do a control-l (TODO: or if the window is resized)
|
||||
* Or use the scrollback (page Up/down etc)
|
||||
*/
|
||||
redraw:
|
||||
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
|
||||
c->sending = c->bottom;
|
||||
c->current_vpos = c->window_size.h - c->footer_height;
|
||||
/*
|
||||
* walk back from the 'bottom' line we want until we reach the top of the
|
||||
* screen, or ran out of lines.
|
||||
*/
|
||||
while (c->sending && c->current_vpos >= 1) {
|
||||
mish_line_p p = TAILQ_PREV(c->sending, mish_line_queue_t, self);
|
||||
if (p) {
|
||||
c->sending = p;
|
||||
c->current_vpos--;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Now, we've just connected here, and there might be backlog, and we want
|
||||
* just enough of that to fill our screen
|
||||
*/
|
||||
/* Set the scrolling region to all minus the 2 bottom lines */
|
||||
_mish_send_queue_fmt(c, "\033D\033[1;%dr",
|
||||
c->window_size.h - c->footer_height);
|
||||
_mish_send_queue_fmt(c,
|
||||
"\033[%d;1H"
|
||||
"\033[J", // clear to bottom of scrolling area
|
||||
c->current_vpos);
|
||||
do {
|
||||
if (c->flags & MISH_CLIENT_UPDATE_WINDOW) {
|
||||
c->flags &= ~MISH_CLIENT_UPDATE_WINDOW;
|
||||
goto redraw;
|
||||
}
|
||||
/*
|
||||
* If there isn't anything to send from the stdout/stderr, do
|
||||
* prompt editing input handling.
|
||||
*/
|
||||
if (c->flags & MISH_CLIENT_UPDATE_PROMPT) {
|
||||
c->flags &= ~MISH_CLIENT_UPDATE_PROMPT;
|
||||
sprintf(c->prompt, ">>: ");
|
||||
_mish_client_set_prompt(c, c->prompt);
|
||||
|
||||
_mish_send_queue_fmt(c,
|
||||
// "_" // draw a fake cursor and end of scrolling text
|
||||
"\033[%d;1H" /* reposition the cursor to prompt area */
|
||||
"%s" // print prompt
|
||||
"\033[J" // Clear to end of screen
|
||||
"\033[4h", // Set insert mode
|
||||
c->window_size.h - c->footer_height + 1, c->prompt);
|
||||
if (c->cmd && c->cmd->len) {
|
||||
_mish_send_queue_fmt(c, "%s", c->cmd->line);
|
||||
/* if cursor is inside the line, move it back */
|
||||
if (c->cmd->len > c->cmd->done)
|
||||
_mish_send_queue_fmt(c, "\033[%dD",
|
||||
c->cmd->len - c->cmd->done);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Now, flush everything we had from the last iteration (inc prompt)
|
||||
* from the scatter gather vector.
|
||||
*/
|
||||
if (c->output.count) {
|
||||
while (_mish_send_flush(m, c))
|
||||
pt_yield(c->cr.state);
|
||||
} else
|
||||
pt_yield(c->cr.state);
|
||||
|
||||
if (!c->sending) {
|
||||
/* we're starting up, pool the backlog for a line to display */
|
||||
if (!c->bottom) {
|
||||
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
|
||||
c->sending = c->bottom;
|
||||
} else {
|
||||
// we WERE at the bottom, so find a possible next line
|
||||
mish_line_p next = TAILQ_NEXT(c->bottom, self);
|
||||
if (c->flags & MISH_CLIENT_SCROLLING) {
|
||||
if (next) {
|
||||
c->bottom = c->sending = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!c->sending) // bah, no new output, loop on
|
||||
continue;
|
||||
/* Remember you can't yield() until we're out of the while() loop */
|
||||
size_t start = c->output.total;
|
||||
/*
|
||||
* Now there is something to send, reposition the cursor in the
|
||||
* scrolling area, and start sending backlog lines.
|
||||
*/
|
||||
_mish_send_queue_fmt(c,
|
||||
"\033[s" /* Save cursor position (from the prompt area) */
|
||||
"\x8\033[%d;1H" /* pos cursor at bottom of scroll area */
|
||||
"\033[8l" /* replace mode */,
|
||||
c->current_vpos);
|
||||
/*
|
||||
* Send until there's nothing else OR we sent a screen worth
|
||||
* this allow a chance to service the input prompt
|
||||
*/
|
||||
size_t screen_worth = (c->window_size.h * c->window_size.w) / 1;
|
||||
do {
|
||||
if (c->sending->err)
|
||||
_mish_send_queue(c, MISH_COLOR_RED);
|
||||
_mish_send_queue_line(c, c->sending);
|
||||
if (c->sending->err)
|
||||
_mish_send_queue(c, "\033[m");
|
||||
// if we reach the bottom mark, stop
|
||||
c->sending = c->sending == c->bottom ?
|
||||
NULL : TAILQ_NEXT(c->sending, self);
|
||||
} while (c->sending &&
|
||||
(c->output.total - start) <= screen_worth);
|
||||
// update cursor position here -- SHOULD update it with each lines,
|
||||
// to handle word wrapping, but for now it's OK
|
||||
// TODO: Update cursor V pos handling line wrap
|
||||
if (!c->sending)
|
||||
c->current_vpos = c->window_size.h - c->footer_height;
|
||||
/* Restore the cursor to the prompt area */
|
||||
_mish_send_queue(c, "\033[u");
|
||||
} while(1);
|
||||
finish:
|
||||
pt_end(c->cr.state);
|
||||
}
|
||||
|
||||
/*
|
||||
* this is the dump pooler; just sends the lines, as is, as they appear.
|
||||
* Still try to max up the buffer if possible, but that's about it.
|
||||
*/
|
||||
void
|
||||
_mish_client_dumb_cr(
|
||||
mish_p m,
|
||||
mish_client_p c)
|
||||
{
|
||||
pt_start(c->cr.state);
|
||||
printf(MISH_COLOR_RED "mish: Started dumb console\n" MISH_COLOR_RESET);
|
||||
do {
|
||||
pt_yield(c->cr.state);
|
||||
|
||||
if (!c->sending) {
|
||||
/* we're starting up, pool the backlog for a line to display */
|
||||
if (!c->bottom) {
|
||||
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
|
||||
c->sending = c->bottom;
|
||||
} else {
|
||||
// we WERE at the bottom, so find a possible next line
|
||||
mish_line_p next = TAILQ_NEXT(c->bottom, self);
|
||||
if (next) {
|
||||
c->sending = next;
|
||||
c->bottom = TAILQ_LAST(&m->backlog.log, mish_line_queue_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!c->sending) // bah, no new output, loop on
|
||||
continue;
|
||||
|
||||
do {
|
||||
_mish_send_queue_line(c, c->sending);
|
||||
// if we reach the bottom mark, stop
|
||||
c->sending = c->sending == c->bottom ?
|
||||
NULL : TAILQ_NEXT(c->sending, self);
|
||||
} while (c->sending);
|
||||
|
||||
while (_mish_send_flush(m, c))
|
||||
pt_yield(c->cr.state);
|
||||
} while(1);
|
||||
|
||||
pt_end(c->cr.state);
|
||||
}
|
||||
|
||||
static void
|
||||
_mish_cmd_history(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
mish_client_p c = param;
|
||||
mish_input_p in = &c->input;
|
||||
mish_line_p l;
|
||||
int i = 0;
|
||||
TAILQ_FOREACH(l, &in->backlog, self) {
|
||||
printf("%3d %s\n", i+1, l->line);
|
||||
i++;
|
||||
}
|
||||
printf(MISH_COLOR_GREEN
|
||||
"mish: %d history"
|
||||
MISH_COLOR_RESET "\n", i);
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(history, "history");
|
||||
MISH_CMD_HELP(history,
|
||||
"Display the history of commands.");
|
||||
MISH_CMD_REGISTER(history, _mish_cmd_history);
|
||||
|
||||
|
||||
static void
|
||||
_mish_cmd_disconnect(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
mish_client_p c = param;
|
||||
|
||||
if (c == c->mish->console) {
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: can't disconnect console"
|
||||
MISH_COLOR_RESET "\n");
|
||||
return;
|
||||
}
|
||||
printf(MISH_COLOR_GREEN
|
||||
"mish: telnet: logout"
|
||||
MISH_COLOR_RESET "\n");
|
||||
c->flags |= MISH_CLIENT_DELETE;
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(disconnect, "dis", "disconnect", "logout");
|
||||
MISH_CMD_HELP(disconnect,
|
||||
"Disconnect this telnet session. If appropriate");
|
||||
MISH_CMD_REGISTER(disconnect, _mish_cmd_disconnect);
|
||||
|
233
libmish/src/mish_client_input.c
Normal file
233
libmish/src/mish_client_input.c
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* mish_client_input.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mish_priv.h"
|
||||
#include "mish_priv_cmd.h"
|
||||
#include "mish.h"
|
||||
|
||||
/*
|
||||
* Don't assume this handles every key combination in the world, it doesn't,
|
||||
* it handles mostly the one I use most from bash!
|
||||
* You like VI syntax, saaaaad story :-)
|
||||
*/
|
||||
//! Parse current input buffer
|
||||
int
|
||||
_mish_client_vt_parse_input(
|
||||
struct mish_t *m,
|
||||
struct mish_input_t *in,
|
||||
uint8_t ich)
|
||||
{
|
||||
mish_client_p c = in->refcon;
|
||||
|
||||
/*
|
||||
* if the sequence buffer is currently being flushed, we need to store
|
||||
* the input until it's done.
|
||||
* So lets store it into the input buffer, and once that output is finished
|
||||
* we can process it as before.
|
||||
*/
|
||||
if (c->output.sqb->done)
|
||||
return MISH_IN_STORE;
|
||||
// now flush what was stored, plus the one we just received
|
||||
for (int i = 0; i <= in->line->done; i++) {
|
||||
uint8_t ch = i == in->line->done ? ich : in->line->line[i];
|
||||
|
||||
if (in->is_telnet) {
|
||||
if (_mish_telnet_parse(c, ch))
|
||||
continue;
|
||||
}
|
||||
if (!_mish_vt_sequence_char(&c->vts, ch))
|
||||
continue;
|
||||
// if no command is editing, create an empty one, add it to history
|
||||
if (!c->cmd) {
|
||||
_mish_line_reserve(&c->cmd, 4);
|
||||
TAILQ_INSERT_TAIL(&in->backlog, c->cmd, self);
|
||||
} else {
|
||||
/*
|
||||
* we need to detach the element if we're going to resize it,
|
||||
* otherwise the list is completely fubar as the pointer changes
|
||||
*/
|
||||
if (c->cmd->size - c->cmd->len <= 4) {
|
||||
mish_line_p next = TAILQ_NEXT(c->cmd, self);
|
||||
TAILQ_REMOVE(&in->backlog, c->cmd, self);
|
||||
_mish_line_reserve(&c->cmd, 4);
|
||||
if (next)
|
||||
TAILQ_INSERT_BEFORE(next, c->cmd, self);
|
||||
else
|
||||
TAILQ_INSERT_TAIL(&in->backlog, c->cmd, self);
|
||||
}
|
||||
}
|
||||
switch (c->vts.seq) {
|
||||
case MISH_VT_SEQ(CSI, '~'): {
|
||||
mish_line_p cursor = c->bottom;
|
||||
if (c->vts.p[0] == 1) // GNU screen HOME seq
|
||||
goto kb_home;
|
||||
else if (c->vts.p[0] == 4) // GNU screen END seq
|
||||
goto kb_end;
|
||||
if (c->vts.p[0] == 5) { // Page UP
|
||||
for (int i = 0; i < c->window_size.h - 3 && cursor; i++)
|
||||
cursor = TAILQ_PREV(cursor, mish_line_queue_t, self);
|
||||
if (cursor) {
|
||||
c->bottom = cursor;
|
||||
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
|
||||
c->flags &= ~MISH_CLIENT_SCROLLING;
|
||||
}
|
||||
} else if (c->vts.p[0] == 6) { // down
|
||||
for (int i = 0; i < c->window_size.h - 3 && cursor; i++)
|
||||
cursor = TAILQ_NEXT(cursor, self);
|
||||
c->bottom = cursor;
|
||||
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
|
||||
if (!c->bottom)
|
||||
c->flags |= MISH_CLIENT_SCROLLING;
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_SEQ(CSI, 'H'): { // Home
|
||||
kb_home:
|
||||
// don't bother if there's not enough backlog
|
||||
if (m->backlog.size < c->window_size.h - 2)
|
||||
break;
|
||||
c->bottom = TAILQ_FIRST(&m->backlog.log);
|
||||
for (i = 0; i < c->window_size.h - 2 - 1 && c->bottom; i++)
|
||||
c->bottom = TAILQ_NEXT(c->bottom, self);
|
||||
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
|
||||
if (c->bottom)
|
||||
c->flags &= ~MISH_CLIENT_SCROLLING;
|
||||
} break;
|
||||
case MISH_VT_SEQ(CSI, 'F'): { // END
|
||||
kb_end:
|
||||
c->flags |= MISH_CLIENT_UPDATE_WINDOW | MISH_CLIENT_SCROLLING;
|
||||
c->bottom = NULL;
|
||||
} break;
|
||||
case MISH_VT_SEQ(CSI, 'R'):
|
||||
c->flags |= MISH_CLIENT_HAS_CURSOR_POS;
|
||||
c->cursor_pos.y = c->vts.p[0];
|
||||
c->cursor_pos.x = c->vts.p[1];
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 16): { // CTRL-P Prev history
|
||||
if (TAILQ_FIRST(&in->backlog) != c->cmd) {
|
||||
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
|
||||
c->cmd = TAILQ_PREV(c->cmd, mish_line_queue_t, self);
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_SEQ(RAW, 14): // CTRL-N Next history
|
||||
if (TAILQ_LAST(&in->backlog, mish_line_queue_t) != c->cmd) {
|
||||
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
|
||||
c->cmd = TAILQ_NEXT(c->cmd, self);
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 1): // CTRL-A Start of line
|
||||
if (c->cmd->done) {
|
||||
_mish_send_queue_fmt(c, "\033[%dD", c->cmd->done);
|
||||
c->cmd->done = 0;
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 5): // CTRL-E End of Line
|
||||
if (c->cmd->done < c->cmd->len) {
|
||||
_mish_send_queue_fmt(c, "\033[%dC",
|
||||
c->cmd->len - c->cmd->done);
|
||||
c->cmd->done = c->cmd->len;
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 2): // CTRL-B Prev char
|
||||
if (c->cmd->done) {
|
||||
c->cmd->done--;
|
||||
_mish_send_queue_fmt(c, "\033[%dD", 1);
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 6): // CTRL-F Next Char
|
||||
if (c->cmd->done < c->cmd->len) {
|
||||
c->cmd->done++;
|
||||
_mish_send_queue_fmt(c, "\033[%dC", 1);
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 23): { // CTRL-W Delete prev word
|
||||
int old_pos = c->cmd->done;
|
||||
while (c->cmd->done && c->cmd->line[c->cmd->done-1] == ' ')
|
||||
c->cmd->done--;
|
||||
while (c->cmd->done && c->cmd->line[c->cmd->done-1] != ' ')
|
||||
c->cmd->done--;
|
||||
|
||||
if (old_pos - c->cmd->done) {
|
||||
int del = old_pos - c->cmd->done;
|
||||
memmove(c->cmd->line + c->cmd->done,
|
||||
c->cmd->line + old_pos,
|
||||
c->cmd->len - old_pos + 1);
|
||||
c->cmd->len -= del;
|
||||
// move back del characters, and delete them
|
||||
_mish_send_queue_fmt(c, "\033[%dD\033[%dP", del, del);
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_SEQ(RAW, 0x7f): // DEL
|
||||
case MISH_VT_SEQ(RAW, 8): // CTRL-H
|
||||
if (c->cmd->done) {
|
||||
c->cmd->done--;
|
||||
memmove(c->cmd->line + c->cmd->done,
|
||||
c->cmd->line + c->cmd->done + 1,
|
||||
c->cmd->len - c->cmd->done + 1);
|
||||
c->cmd->len--;
|
||||
// backspace plus Delete (1) Character
|
||||
_mish_send_queue(c, "\x8\033[P");
|
||||
}
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 11): // CTRL-K Kill rest of line
|
||||
c->cmd->len = c->cmd->done;
|
||||
c->cmd->line[c->cmd->len] = 0;
|
||||
_mish_send_queue(c, "\033[K");
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 12): // CTRL-L Redraw
|
||||
c->flags |= MISH_CLIENT_UPDATE_WINDOW;
|
||||
break;
|
||||
case MISH_VT_SEQ(RAW, 13): // CTRL-M aka return
|
||||
c->cmd->line[c->cmd->len] = 0;
|
||||
if (0) { // debug
|
||||
fprintf(stdout, "CMD: '%.*s", c->cmd->done, c->cmd->line);
|
||||
fprintf(stdout, "\033[7m%.*s\033[0m", 1,
|
||||
c->cmd->line + c->cmd->done);
|
||||
if (c->cmd->done < c->cmd->len)
|
||||
fprintf(stdout, "%s", c->cmd->line + c->cmd->done+1);
|
||||
fprintf(stdout, "'\n");
|
||||
}
|
||||
mish_cmd_call(c->cmd->line, c);
|
||||
c->cmd = NULL; // new one
|
||||
{ // reuse the last empty one
|
||||
mish_line_p last = TAILQ_LAST(&in->backlog, mish_line_queue_t);
|
||||
if (last && last->len == 0)
|
||||
c->cmd = last;
|
||||
}
|
||||
c->flags |= MISH_CLIENT_UPDATE_PROMPT;
|
||||
break;
|
||||
default:
|
||||
if (c->vts.seq & ~0xff) {
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: Unknown sequence: %08x ", c->vts.seq);
|
||||
for (int i = 0; i < c->vts.pc; i++)
|
||||
printf(":%d", c->vts.p[i]);
|
||||
printf("'%c%c'", c->vts.seq >> 8, c->vts.seq & 0xff);
|
||||
printf(MISH_COLOR_RESET "\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (c->vts.glyph && c->vts.glyph >= ' ' && c->vts.glyph < 0x7f) {
|
||||
memmove(c->cmd->line + c->cmd->done + 1,
|
||||
c->cmd->line + c->cmd->done,
|
||||
c->cmd->len - c->cmd->done);
|
||||
c->cmd->line[c->cmd->done] = c->vts.glyph;
|
||||
c->cmd->done++;
|
||||
c->cmd->len++;
|
||||
c->cmd->line[c->cmd->len] = 0;
|
||||
// no need to explicitly insert, terminal should already be setup
|
||||
_mish_send_queue_fmt(c, "%lc", c->vts.glyph);
|
||||
}
|
||||
}
|
||||
// remove any buffered input.
|
||||
in->line->len = 0;
|
||||
return MISH_IN_SKIP;
|
||||
}
|
307
libmish/src/mish_cmd.c
Normal file
307
libmish/src/mish_cmd.c
Normal file
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* mish_cmd.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "mish_priv_cmd.h"
|
||||
#include "mish_priv.h"
|
||||
#include "mish.h"
|
||||
|
||||
#include "fifo_declare.h"
|
||||
|
||||
|
||||
#ifndef offsetof
|
||||
#define offsetof(type, member) __builtin_offsetof (type, member)
|
||||
#endif
|
||||
#ifndef container_of
|
||||
#define container_of(ptr, type, member) ({ \
|
||||
const char *__mptr = (const char *)(ptr); \
|
||||
(type *)(__mptr - offsetof(type, member) );})
|
||||
#endif
|
||||
|
||||
typedef struct mish_cmd_t {
|
||||
TAILQ_ENTRY(mish_cmd_t) self;
|
||||
mish_cmd_handler_p cmd_cb;
|
||||
void * param_cb;
|
||||
uint32_t safe : 1;
|
||||
|
||||
const char ** names; // list of aliases for the command
|
||||
const char ** help;
|
||||
} mish_cmd_t, *mish_cmd_p;
|
||||
|
||||
typedef struct mish_cmd_call_t {
|
||||
mish_cmd_p cmd;
|
||||
char ** argv;
|
||||
int argc;
|
||||
} mish_cmd_call_t;
|
||||
|
||||
DECLARE_FIFO(mish_cmd_call_t, mish_call_queue, 4);
|
||||
DEFINE_FIFO(mish_cmd_call_t, mish_call_queue);
|
||||
|
||||
static TAILQ_HEAD(,mish_cmd_t) _cmd_list = TAILQ_HEAD_INITIALIZER(_cmd_list);
|
||||
static mish_call_queue_t _cmd_fifo = {0};
|
||||
|
||||
void __attribute__((weak))
|
||||
mish_register_cmd(
|
||||
const char ** cmd_names,
|
||||
const char ** cmd_help,
|
||||
mish_cmd_handler_p cmd_handler,
|
||||
void * handler_param,
|
||||
int safe)
|
||||
{
|
||||
if (!cmd_names || !cmd_help || !cmd_handler) {
|
||||
fprintf(stderr, "%s invalid parameters\n", __func__);
|
||||
return;
|
||||
}
|
||||
mish_cmd_p cmd = calloc(1, sizeof(*cmd));
|
||||
|
||||
cmd->names = cmd_names;
|
||||
cmd->help = cmd_help;
|
||||
cmd->cmd_cb = cmd_handler;
|
||||
cmd->param_cb = handler_param;
|
||||
cmd->safe = safe;
|
||||
|
||||
// keep the list roughtly sorted
|
||||
mish_cmd_p c, s;
|
||||
TAILQ_FOREACH_SAFE(c, &_cmd_list, self, s) {
|
||||
if (strcmp(cmd_names[0], c->names[0]) < 0) {
|
||||
TAILQ_INSERT_BEFORE(c, cmd, self);
|
||||
return;
|
||||
}
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&_cmd_list, cmd, self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the length of the first word of cmd_line
|
||||
*/
|
||||
static int
|
||||
first_word_length(
|
||||
const char * cmd_line)
|
||||
{
|
||||
const char *d = cmd_line;
|
||||
while (*d && *d != ' ')
|
||||
d++;
|
||||
return d - cmd_line;
|
||||
}
|
||||
|
||||
mish_cmd_p
|
||||
mish_cmd_lookup(
|
||||
const char * cmd_line)
|
||||
{
|
||||
if (!cmd_line)
|
||||
return NULL;
|
||||
int l = first_word_length(cmd_line);
|
||||
mish_cmd_p cmd;
|
||||
TAILQ_FOREACH(cmd, &_cmd_list, self) {
|
||||
for (int i = 0; cmd->names && cmd->names[i]; i++)
|
||||
if (!strncmp(cmd->names[i], cmd_line, l))
|
||||
return cmd;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct _mish_argv_t {
|
||||
char * line;
|
||||
int ac;
|
||||
char * av[0];
|
||||
} _mish_argv_t;
|
||||
|
||||
/*
|
||||
* Duplicate 'line', split it into words, store word pointers in an array,
|
||||
* NULL terminate it. Also return the number of words in the array in argc.
|
||||
*
|
||||
* The returned value is made of two malloc()ed blocks. use mish_argv_free
|
||||
* to free the memory.
|
||||
* It's OK to change any of the pointers. But no not try to realloc() the
|
||||
* vector as it hides a structure
|
||||
*/
|
||||
static char **
|
||||
mish_argv_make(
|
||||
const char * line,
|
||||
int * argc )
|
||||
{
|
||||
const char separator = ' ';
|
||||
_mish_argv_t * r = calloc(1, sizeof(*r));
|
||||
r->line = strdup(line);
|
||||
char *dup = r->line;
|
||||
char quote;
|
||||
enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy };
|
||||
int state = s_newarg;
|
||||
do {
|
||||
switch (state) {
|
||||
case s_newarg:
|
||||
r = realloc(r, sizeof(*r) + ((r->ac + 2) * sizeof(char*)));
|
||||
while (*dup == ' ' || *dup == separator)
|
||||
dup++;
|
||||
r->av[r->ac++] = dup;
|
||||
state = s_startarg;
|
||||
break;
|
||||
case s_startarg:
|
||||
if (*dup == '"' || *dup == '\'') {
|
||||
quote = *dup++;
|
||||
state = s_copyquote;
|
||||
} else
|
||||
state = s_copy;
|
||||
break;
|
||||
case s_copyquote:
|
||||
if (*dup == '\\')
|
||||
state = s_skip;
|
||||
else if (*dup == quote) {
|
||||
state = s_newarg;
|
||||
dup++;
|
||||
if (*dup) *dup++ = 0;
|
||||
} else if (*dup)
|
||||
dup++;
|
||||
break;
|
||||
case s_skip:
|
||||
dup++;
|
||||
state = s_copyquote;
|
||||
break;
|
||||
case s_copy:
|
||||
if (*dup == 0)
|
||||
break;
|
||||
if (*dup != separator)
|
||||
dup++;
|
||||
else {
|
||||
state = s_newarg;
|
||||
if (*dup) *dup++ = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (*dup);
|
||||
r->av[r->ac] = NULL;
|
||||
if (argc)
|
||||
*argc = r->ac;
|
||||
return r->av;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free memory allocated by a mish_argv_make
|
||||
*/
|
||||
void
|
||||
mish_argv_free(
|
||||
char **_av)
|
||||
{
|
||||
if (!_av)
|
||||
return;
|
||||
_mish_argv_t * r = container_of(_av, _mish_argv_t, av);
|
||||
free((void*)r->line);
|
||||
free((void*)r);
|
||||
}
|
||||
|
||||
int
|
||||
mish_cmd_call(
|
||||
const char * cmd_line,
|
||||
void * c)
|
||||
{
|
||||
if (!cmd_line || !*cmd_line)
|
||||
return -1;
|
||||
|
||||
mish_cmd_p cmd = mish_cmd_lookup(cmd_line);
|
||||
if (!cmd) {
|
||||
int l = first_word_length(cmd_line);
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: '%.*s' not found. type 'help'."
|
||||
MISH_COLOR_RESET "\n",
|
||||
l, cmd_line);
|
||||
return -1;
|
||||
}
|
||||
int ac = 0;
|
||||
char ** av = mish_argv_make(cmd_line, &ac);
|
||||
|
||||
if (cmd->safe) {
|
||||
if (!mish_call_queue_isfull(&_cmd_fifo)) {
|
||||
mish_cmd_call_t fe = {
|
||||
.cmd = cmd,
|
||||
.argv = av,
|
||||
.argc = ac,
|
||||
};
|
||||
mish_call_queue_write(&_cmd_fifo, fe);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"mish: cmd FIFO full, make sure to call mish_cmd_poll()!\n");
|
||||
}
|
||||
} else {
|
||||
cmd->cmd_cb(cmd->param_cb ? cmd->param_cb : c, ac, (const char**)av);
|
||||
mish_argv_free(av);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mish_cmd_poll()
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
while (!mish_call_queue_isempty(&_cmd_fifo)) {
|
||||
mish_cmd_call_t c = mish_call_queue_read(&_cmd_fifo);
|
||||
|
||||
c.cmd->cmd_cb(
|
||||
c.cmd->param_cb, c.argc, (const char**)c.argv);
|
||||
mish_argv_free(c.argv);
|
||||
res++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char *_help[] = {
|
||||
"A few of the typical EMACS keys work for editing commands.",
|
||||
"like, ^A-^E, ^W, ^K - ^P,^N to navigate history and ^L to",
|
||||
"redraw.",
|
||||
"BEG/PGUP/DOWN/END to change the view of the backlog buffer.",
|
||||
0,
|
||||
};
|
||||
static void
|
||||
_mish_cmd_help(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
mish_cmd_p cmd;
|
||||
|
||||
if (argc < 2) {
|
||||
printf(MISH_COLOR_GREEN "mish: Key binding\n");
|
||||
for (int i = 0; _help[i]; i++)
|
||||
printf(" %s\n", _help[i]);
|
||||
printf(MISH_COLOR_GREEN "List of commands\n");
|
||||
|
||||
TAILQ_FOREACH(cmd, &_cmd_list, self) {
|
||||
printf(" ");
|
||||
for (int i = 0; cmd->names && cmd->names[i]; i++)
|
||||
printf("%s%s", i > 0 ? "," : "", cmd->names[i]);
|
||||
printf(" - %s\n", cmd->help[0]);
|
||||
}
|
||||
printf(MISH_COLOR_RESET);
|
||||
} else {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
mish_cmd_p cmd = mish_cmd_lookup(argv[i]);
|
||||
|
||||
if (!cmd) {
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: Unknown command '%s'"
|
||||
MISH_COLOR_RESET "\n", argv[i]);
|
||||
continue;
|
||||
}
|
||||
printf(MISH_COLOR_GREEN);
|
||||
for (int i = 0; cmd->names && cmd->names[i]; i++)
|
||||
printf("%s%s", i > 0 ? "," : "", cmd->names[i]);
|
||||
printf("\n");
|
||||
for (int i = 0; cmd->help && cmd->help[i]; i++)
|
||||
printf(" %s\n", cmd->help[i]);
|
||||
printf(MISH_COLOR_RESET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(help, "help");
|
||||
MISH_CMD_HELP(help,
|
||||
"[cmd...] - Display command list, or help for commands",
|
||||
"(optional) [cmd...] will display all the help for [cmd]");
|
||||
MISH_CMD_REGISTER(help, _mish_cmd_help);
|
72
libmish/src/mish_cmd_env.c
Normal file
72
libmish/src/mish_cmd_env.c
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* mish_cmd_env.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mish.h"
|
||||
|
||||
extern char ** environ;
|
||||
|
||||
static void
|
||||
_mish_cmd_env(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
for (int i = 0; environ[i]; i++)
|
||||
if (strncmp(environ[i], "LS_COLORS=", 10))
|
||||
printf("%s\n", environ[i]);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; environ[i]; i++)
|
||||
for (int ei = 1; ei < argc; ei++) {
|
||||
int l = strlen(argv[ei]);
|
||||
if (!strncmp(environ[i], argv[ei], l))
|
||||
printf("%s\n", environ[i]);
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(env, "env");
|
||||
MISH_CMD_HELP(env,
|
||||
"[names ...] display all environment, or variables",
|
||||
"Apart from LS_COLORS: that is just spam.",
|
||||
"If you specify names it'll just show the ones whose name",
|
||||
"start with that prefix");
|
||||
MISH_CMD_REGISTER(env, _mish_cmd_env);
|
||||
|
||||
static void
|
||||
_mish_cmd_setenv(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
for (int ei = 1; ei < argc; ei++) {
|
||||
char *equal = strchr(argv[ei], '=');
|
||||
if (!equal) {
|
||||
printf("mish: setenv: '%s' requires an '='\n", argv[ei]);
|
||||
return;
|
||||
}
|
||||
*equal++ = 0;
|
||||
printf("mish: %s%s%s%s\n", *equal ? "" : "unset ",
|
||||
argv[ei], *equal ? " = ": "",
|
||||
*equal ? equal : "");
|
||||
if (!*equal)
|
||||
unsetenv(argv[ei]);
|
||||
else
|
||||
setenv(argv[ei], equal, 1);
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(setenv, "setenv");
|
||||
MISH_CMD_HELP(setenv,
|
||||
"[<name>=<value>...] set/clear environment variable(s)",
|
||||
"Set <name> to <value> .. if <value> is omitted, clears it.",
|
||||
"The '=' is required, even when clearing.");
|
||||
MISH_CMD_REGISTER(setenv, _mish_cmd_setenv);
|
139
libmish/src/mish_input.c
Normal file
139
libmish/src/mish_input.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* mish_input.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "mish_priv.h"
|
||||
#include "mish_priv_line.h"
|
||||
|
||||
#ifdef MISH_INPUT_TEST
|
||||
#define D(_w) _w
|
||||
#else
|
||||
#define D(_w)
|
||||
#endif
|
||||
|
||||
void
|
||||
_mish_input_init(
|
||||
mish_p m,
|
||||
mish_input_p in,
|
||||
int fd)
|
||||
{
|
||||
TAILQ_INIT(&in->backlog);
|
||||
// fprintf(stderr, "%s %d\n", __func__, fd);
|
||||
in->fd = fd;
|
||||
in->line = NULL;
|
||||
FD_SET(in->fd, &m->select.read);
|
||||
if (in->fd >= m->select.max - 1)
|
||||
m->select.max = in->fd +1;
|
||||
|
||||
int flags = fcntl(fd, F_GETFL, NULL);
|
||||
if (flags == 1) {
|
||||
perror("mish: input F_GETFL");
|
||||
flags = 0;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
|
||||
perror("mish: input F_SETFL");
|
||||
}
|
||||
|
||||
void
|
||||
_mish_input_clear(
|
||||
mish_p m,
|
||||
mish_input_p in)
|
||||
{
|
||||
mish_line_p l;
|
||||
while ((l = TAILQ_FIRST(&in->backlog)) != NULL) {
|
||||
TAILQ_REMOVE(&in->backlog, l, self);
|
||||
free(l);
|
||||
}
|
||||
if (in->line)
|
||||
free(in->line);
|
||||
in->line = NULL;
|
||||
if (in->fd != -1) {
|
||||
FD_CLR(in->fd, &m->select.read);
|
||||
FD_CLR(in->fd, &m->select.write);
|
||||
close(in->fd);
|
||||
in->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
_mish_input_read(
|
||||
mish_p m,
|
||||
fd_set * fds,
|
||||
mish_input_p in)
|
||||
{
|
||||
if (!FD_ISSET(in->fd, fds))
|
||||
return 0;
|
||||
do {
|
||||
if (_mish_line_reserve(&in->line, 80)) {
|
||||
D(printf(" reserve bailed us\n");)
|
||||
break;
|
||||
}
|
||||
ssize_t rd = read(in->fd,
|
||||
in->line->line + in->line->len,
|
||||
in->line->size - in->line->len - 1);
|
||||
if (rd == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
|
||||
break;
|
||||
if (rd <= 0) {
|
||||
close(in->fd);
|
||||
FD_CLR(in->fd, &m->select.read);
|
||||
FD_CLR(in->fd, &m->select.write);
|
||||
in->fd = -1;
|
||||
printf(MISH_COLOR_RED "mish: telnet: disconnected"
|
||||
MISH_COLOR_RESET "\n");
|
||||
return -1;
|
||||
}
|
||||
in->line->len += rd;
|
||||
} while (1);
|
||||
uint8_t * s = (uint8_t*) in->line->line + in->line->done;
|
||||
uint8_t * d = s;
|
||||
int added = in->line->len - in->line->done;
|
||||
D(printf(" buffer added %d done %d len %d size %d\n", added,
|
||||
(int)in->line->done, (int)in->line->len, (int)in->line->size);
|
||||
if (in->line->done)
|
||||
printf(" buffer: '%.*s'\n", in->line->done, in->line->line);)
|
||||
/*
|
||||
* This loop re-parse the data, passes it to the optional handler,
|
||||
* and then store processed lines into the backlog. This is not super
|
||||
* optimal in the case there isn't a process_char() callback, so perhaps
|
||||
* I should make another less 'generic' input parser that doesn't copy
|
||||
* stuff around (in place, often).
|
||||
*/
|
||||
while (added) {
|
||||
int r = MISH_IN_STORE;
|
||||
if (in->process_char)
|
||||
r = in->process_char(m, in, *s);
|
||||
else
|
||||
r = *s == '\n' ? MISH_IN_SPLIT : MISH_IN_STORE;
|
||||
|
||||
if (r == MISH_IN_STORE || r == MISH_IN_SPLIT) {
|
||||
*d++ = *s;
|
||||
in->line->done++;
|
||||
}
|
||||
if (r == MISH_IN_SPLIT) {
|
||||
D(printf(" split size %d remains %d : '%.*s'\n", in->line->done,
|
||||
(int)added, in->line->done-1, in->line->line);)
|
||||
_mish_line_add(&in->backlog, in->line->line, in->line->done);
|
||||
d = (uint8_t*)in->line->line;
|
||||
in->line->len = in->line->done = 0;
|
||||
}
|
||||
s++; added--;
|
||||
}
|
||||
*d = 0; // NUL terminate for debug purpose!
|
||||
D(printf(" exit added %d done %d len %d size %d\n", added,
|
||||
(int)in->line->done, (int)in->line->len, (int)in->line->size);
|
||||
in->line->len = in->line->done;
|
||||
if (in->line->done)
|
||||
printf(" buffer: '%s'\n", in->line->line);)
|
||||
|
||||
return TAILQ_FIRST(&in->backlog) != NULL;
|
||||
}
|
59
libmish/src/mish_line.c
Normal file
59
libmish/src/mish_line.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* mish_line.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "mish_priv.h"
|
||||
#include "mish_priv_line.h"
|
||||
|
||||
static const mish_line_t zero = {};
|
||||
|
||||
mish_line_p
|
||||
_mish_line_add(
|
||||
mish_line_queue_t * q,
|
||||
char * buffer,
|
||||
size_t length )
|
||||
{
|
||||
size_t l = length + 1;
|
||||
mish_line_p nl = (mish_line_p)malloc(sizeof(*nl) + l);
|
||||
*nl = zero;
|
||||
nl->size = l;
|
||||
nl->len = length;
|
||||
memcpy(nl->line, buffer, length);
|
||||
nl->line[l] = 0;
|
||||
nl->stamp = _mish_stamp_ms();
|
||||
TAILQ_INSERT_TAIL(q, nl, self);
|
||||
return nl;
|
||||
}
|
||||
|
||||
int
|
||||
_mish_line_reserve(
|
||||
mish_line_p *line,
|
||||
uint32_t count)
|
||||
{
|
||||
if (!line)
|
||||
return -1;
|
||||
mish_line_p l = *line;
|
||||
if (count < 40)
|
||||
count = 40;
|
||||
if (!l) {
|
||||
l = (mish_line_p)calloc(1, sizeof(mish_line_t) + count);
|
||||
*l = zero;
|
||||
l->size = count;
|
||||
*line = l;
|
||||
}
|
||||
if (l->size + count >= MISH_MAX_LINE_SIZE)
|
||||
return 1;
|
||||
if (l->size - l->len < count) {
|
||||
l = (mish_line_p)realloc(l,
|
||||
sizeof(mish_line_t) + l->size + count);
|
||||
l->size += count;
|
||||
*line = l;
|
||||
}
|
||||
return 0;
|
||||
}
|
273
libmish/src/mish_priv.h
Normal file
273
libmish/src/mish_priv.h
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* mish_priv.h
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef LIBMISH_SRC_MISH_PRIV_H_
|
||||
#define LIBMISH_SRC_MISH_PRIV_H_
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <stdint.h>
|
||||
#include <pthread.h>
|
||||
#include <termios.h>
|
||||
#include "bsd_queue.h"
|
||||
#include "mish_priv_vt.h"
|
||||
#include "mish_priv_line.h"
|
||||
|
||||
struct mish_t;
|
||||
|
||||
/*
|
||||
* The process_char callback of mish_input_t returns one of these.
|
||||
*/
|
||||
enum {
|
||||
MISH_IN_SKIP = 0, // skip current character (don't store)
|
||||
MISH_IN_STORE, // store current character in input buffer
|
||||
MISH_IN_SPLIT, // store current line, add it to backlog
|
||||
};
|
||||
|
||||
/*
|
||||
* This receives stuff from a file descriptor, 'processes' it, and split it
|
||||
* into lines to eventually store new lines into the 'backlog'.
|
||||
*/
|
||||
typedef struct mish_input_t {
|
||||
// lines read from fd are queued in here when \n has been received
|
||||
mish_line_queue_t backlog;
|
||||
uint32_t is_telnet : 1, flush_on_nl : 1;
|
||||
|
||||
// return 1 for the character to be stored, 0 for it to be skipped
|
||||
int (*process_char)(
|
||||
struct mish_t *m,
|
||||
struct mish_input_t *i,
|
||||
uint8_t ch);
|
||||
void * refcon; // reference constant, for the callbacks
|
||||
int fd;
|
||||
mish_line_p line;
|
||||
} mish_input_t, *mish_input_p;
|
||||
|
||||
/* various internal states for the client */
|
||||
enum {
|
||||
MISH_CLIENT_INIT_SENT = (1 << 0),
|
||||
MISH_CLIENT_HAS_WINDOW_SIZE = (1 << 1),
|
||||
MISH_CLIENT_HAS_CURSOR_POS = (1 << 2),
|
||||
MISH_CLIENT_UPDATE_PROMPT = (1 << 3),
|
||||
MISH_CLIENT_UPDATE_WINDOW = (1 << 4),
|
||||
MISH_CLIENT_SCROLLING = (1 << 5),
|
||||
MISH_CLIENT_DELETE = (1 << 6),
|
||||
};
|
||||
|
||||
typedef struct mish_client_t {
|
||||
TAILQ_ENTRY(mish_client_t) self;
|
||||
struct mish_t * mish;
|
||||
uint32_t flags;
|
||||
|
||||
// "coroutine" processing received lines from the stdout/err
|
||||
struct {
|
||||
void * state; // state for the prompt coroutine
|
||||
// _mish_client_interractive_cr() or _mish_client_dumb_cr()
|
||||
void (*process)(
|
||||
struct mish_t * m,
|
||||
struct mish_client_t* c);
|
||||
} cr;
|
||||
int footer_height; // # static lines at bottom of screen
|
||||
int current_vpos;
|
||||
mish_line_p bottom;
|
||||
// Line we are currently sending (or NULL)
|
||||
mish_line_p sending;
|
||||
|
||||
/*
|
||||
* Output sent to the client is made of bits we want to send to move
|
||||
* the cursor and so on, and, the actual lines we captured from the program.
|
||||
* So a vector list is built as the output is built, and it's made of bits
|
||||
* we've copied in "sqb" as well as lines from the mish backlog.
|
||||
* Once we deem a good time to output stuff, we send that vector and wait
|
||||
* until it's all gone before starting again.
|
||||
*/
|
||||
struct {
|
||||
int fd; // index of fd we writev to
|
||||
struct iovec *v; // starts at NULL
|
||||
int count;// how many are filled in
|
||||
int size; // how many are allocated
|
||||
// temporary sequence buffer, for composite output.
|
||||
mish_line_p sqb;
|
||||
size_t total; // total bytes we sent
|
||||
} output;
|
||||
|
||||
char prompt[64];
|
||||
/* Since the prompt will contains more bytes than displayed glyphs,
|
||||
* we keep track of the number of glyphs so we can reposition the
|
||||
* cursor accurately. */
|
||||
int prompt_gc; // prompt real glyph count
|
||||
|
||||
/*
|
||||
* Unfiltered console input from the client. This get stored in there
|
||||
* until the input code notices any output has been sent. Then it is
|
||||
* passed down to 'vts'
|
||||
*/
|
||||
mish_input_t input;
|
||||
/*
|
||||
* this handles the vt sequences received at the command prompt,
|
||||
* like direction keys etc -also handles UTF8 sequences- (but doesn't do
|
||||
* anything with them). It is also subverted by the telnet decoder to
|
||||
* do it's own things, separately.
|
||||
* Sequences coming out are processed in mish_client_input.
|
||||
*/
|
||||
mish_vt_sequence_t vts;
|
||||
/*
|
||||
* Stuff received from 'input' side gets filtered via 'vts' and
|
||||
* stored in this.
|
||||
* Once a command has been validated, it is added back to the input
|
||||
* backlog to make this client 'history'
|
||||
*/
|
||||
mish_line_p cmd;
|
||||
|
||||
struct {
|
||||
int w, h; } window_size; // valid if MISH_CLIENT_HAS_WINDOW_SIZE
|
||||
struct {
|
||||
int x, y; } cursor_pos; // valid if MISH_CLIENT_HAS_CURSOR_POS
|
||||
} mish_client_t, *mish_client_p;
|
||||
|
||||
// supplement the public ones from mish.h
|
||||
enum {
|
||||
MISH_QUIT = (1 << 31),
|
||||
// the process console we started was a tty, so has terminal settings
|
||||
MISH_CONSOLE_TTY = (1 << 30),
|
||||
};
|
||||
|
||||
typedef struct mish_t {
|
||||
uint32_t flags;
|
||||
struct termios orig_termios; // original terminal settings
|
||||
int originals[2]; // backup the original 1,2 fds
|
||||
/* These are the ones we use to read the output from the main program. */
|
||||
mish_input_t origin[2];
|
||||
uint64_t stamp_start;
|
||||
|
||||
TAILQ_HEAD(, mish_client_t) clients;
|
||||
mish_client_p console; // client that is also the original terminal.
|
||||
|
||||
pthread_t capture; // libmish main thread
|
||||
pthread_t main; // todo: allow pause/stop/resume?
|
||||
|
||||
struct {
|
||||
mish_line_queue_t log;
|
||||
unsigned int size; // number of lines in backlog
|
||||
size_t alloc; // number of bytes in the backlog
|
||||
} backlog;
|
||||
struct {
|
||||
int listen; // listen socket
|
||||
int port; // port we're listening on
|
||||
} telnet;
|
||||
// Used by the select thread in mish_capture_select.c
|
||||
struct {
|
||||
fd_set read, write;
|
||||
int max;
|
||||
} select;
|
||||
} mish_t, *mish_p;
|
||||
|
||||
mish_client_p
|
||||
mish_client_new(
|
||||
mish_p m,
|
||||
int in,
|
||||
int out,
|
||||
int is_tty);
|
||||
void
|
||||
mish_client_delete(
|
||||
mish_p m,
|
||||
mish_client_p c);
|
||||
|
||||
/*
|
||||
* Sequence buffer handling
|
||||
*/
|
||||
int
|
||||
_mish_send_flush(
|
||||
mish_p m,
|
||||
mish_client_p c);
|
||||
void
|
||||
_mish_send_queue(
|
||||
mish_client_p c,
|
||||
const char * b);
|
||||
void
|
||||
_mish_send_queue_fmt(
|
||||
mish_client_p c,
|
||||
const char *fmt, ...);
|
||||
void
|
||||
_mish_send_queue_line(
|
||||
mish_client_p c,
|
||||
mish_line_p line );
|
||||
|
||||
//! Parse current input buffer fro VT sequences, like keys.
|
||||
int
|
||||
_mish_client_vt_parse_input(
|
||||
struct mish_t *m,
|
||||
struct mish_input_t *in,
|
||||
uint8_t ich);
|
||||
|
||||
/*
|
||||
* This is the main interactive client coroutine. This one is interesting.
|
||||
*/
|
||||
void
|
||||
_mish_client_interractive_cr(
|
||||
mish_p m,
|
||||
mish_client_p c);
|
||||
void
|
||||
_mish_client_dumb_cr(
|
||||
mish_p m,
|
||||
mish_client_p c);
|
||||
|
||||
uint64_t
|
||||
_mish_stamp_ms();
|
||||
|
||||
/*
|
||||
* telnet handling
|
||||
*/
|
||||
int
|
||||
mish_telnet_prepare(
|
||||
mish_p m,
|
||||
uint16_t port);
|
||||
int
|
||||
mish_telnet_in_check(
|
||||
mish_p m,
|
||||
fd_set * r);
|
||||
void
|
||||
mish_telnet_send_init(
|
||||
mish_client_p c);
|
||||
int
|
||||
_mish_telnet_parse(
|
||||
mish_client_p c,
|
||||
uint8_t ch);
|
||||
|
||||
/*
|
||||
* input handling
|
||||
*/
|
||||
void
|
||||
_mish_input_init(
|
||||
mish_p m,
|
||||
mish_input_p in,
|
||||
int fd);
|
||||
void
|
||||
_mish_input_clear(
|
||||
mish_p m,
|
||||
mish_input_p in);
|
||||
int
|
||||
_mish_input_read(
|
||||
mish_p m,
|
||||
fd_set * fds,
|
||||
mish_input_p in);
|
||||
|
||||
/*
|
||||
* Capture thread function
|
||||
*/
|
||||
void *
|
||||
_mish_capture_select(
|
||||
void *param);
|
||||
|
||||
/*
|
||||
* https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_output_sequences
|
||||
*/
|
||||
#define MISH_COLOR_RED "\033[38;5;125m"
|
||||
#define MISH_COLOR_GREEN "\033[38;5;28m"
|
||||
#define MISH_COLOR_RESET "\033[0m"
|
||||
|
||||
#endif /* LIBMISH_SRC_MISH_PRIV_H_ */
|
37
libmish/src/mish_priv_cmd.h
Normal file
37
libmish/src/mish_priv_cmd.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* mish_priv_cmd.h
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef LIBMISH_SRC_MISH_PRIV_CMD_H_
|
||||
#define LIBMISH_SRC_MISH_PRIV_CMD_H_
|
||||
|
||||
/*
|
||||
* I decided that the command list would't be attached to the mish_t,
|
||||
*
|
||||
* In any other API I would, for consistency sake, but here I'm more
|
||||
* interested in having a convenient way to add commands, regardless of the
|
||||
* state of mish_t, and have macro that register them before main() is called
|
||||
* and that sort of things.
|
||||
*
|
||||
* In the same vein, I also don't provide a way to remove a command, I don't
|
||||
* think it's terribly necessary at the minute
|
||||
*/
|
||||
#include "bsd_queue.h"
|
||||
|
||||
typedef void (*mish_cmd_handler_p)(
|
||||
void * param,
|
||||
int argc,
|
||||
const char *argv[]);
|
||||
#define _LIBMISH_HAS_CMD_HANDLER_
|
||||
|
||||
int
|
||||
mish_cmd_call(
|
||||
const char * cmd_line,
|
||||
void * c);
|
||||
|
||||
|
||||
#endif /* LIBMISH_SRC_MISH_PRIV_CMD_H_ */
|
47
libmish/src/mish_priv_line.h
Normal file
47
libmish/src/mish_priv_line.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* mish_priv_line.h
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef LIBMISH_SRC_MISH_PRIV_LINE_H_
|
||||
#define LIBMISH_SRC_MISH_PRIV_LINE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bsd_queue.h"
|
||||
|
||||
#define MISH_MAX_LINE_SIZE 0xffff
|
||||
|
||||
struct mish_line_t {
|
||||
TAILQ_ENTRY(mish_line_t) self;
|
||||
/* The stamp is made with _mish_get_stamp, which returns epoch
|
||||
* milliseconds, so we don't need the 64 bits for the stamp here,
|
||||
* lets say 34 bits seconds + 10 bits milliseconds
|
||||
*/
|
||||
uint64_t stamp : 44;
|
||||
uint64_t err: 1, use: 4, draw_stamp: 1,
|
||||
size : 16, len: 16, // len <= size
|
||||
done: 16; // done <= len
|
||||
char line[0];
|
||||
};
|
||||
|
||||
typedef struct mish_line_t mish_line_t, *mish_line_p;
|
||||
|
||||
typedef TAILQ_HEAD(mish_line_queue_t, mish_line_t) mish_line_queue_t;
|
||||
|
||||
/*
|
||||
* mish_line tools
|
||||
*/
|
||||
int
|
||||
_mish_line_reserve(
|
||||
mish_line_p *line,
|
||||
uint32_t count);
|
||||
mish_line_p
|
||||
_mish_line_add(
|
||||
mish_line_queue_t * q,
|
||||
char * buffer,
|
||||
size_t length );
|
||||
|
||||
#endif /* LIBMISH_SRC_MISH_PRIV_LINE_H_ */
|
48
libmish/src/mish_priv_vt.h
Normal file
48
libmish/src/mish_priv_vt.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* mish_priv_vt.h
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef LIBMISH_SRC_MISH_PRIV_VT_H_
|
||||
#define LIBMISH_SRC_MISH_PRIV_VT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Input sequence decoder, deals with all the ESC bits and
|
||||
* any UTF8 bits that gets encountered
|
||||
*/
|
||||
#define MISH_VT_RAW 0
|
||||
#define MISH_VT_TELNET (0xff)
|
||||
#define MISH_VT_ESC (0x1b)
|
||||
#define MISH_VT_CSI ((MISH_VT_ESC << 8) | ('['))
|
||||
#define MISH_VT_CSIQ ((MISH_VT_CSI << 8) | ('?'))
|
||||
#define MISH_VT_UTF8 0x80
|
||||
|
||||
// allow writing MISH_VT_SEQ(CSI, 'H')
|
||||
#define MISH_VT_SEQ(_kind, _ch) (((MISH_VT_ ## _kind) << 8) | (_ch))
|
||||
|
||||
typedef struct mish_vt_sequence_t {
|
||||
uint32_t seq; // ESC, CSI, CSI?
|
||||
int p[8];
|
||||
union {
|
||||
struct {
|
||||
uint32_t pc : 4, // CSI; parameter count
|
||||
seq_want : 4, // how many more bytes we want (UTF8)
|
||||
done : 1, // sequence is done
|
||||
error : 1; // sequence was not valid
|
||||
};
|
||||
uint32_t flags;
|
||||
};
|
||||
uint32_t glyph; // for RAW and UTF8
|
||||
} mish_vt_sequence_t, *mish_vt_sequence_p;
|
||||
|
||||
int
|
||||
_mish_vt_sequence_char(
|
||||
mish_vt_sequence_p s,
|
||||
uint8_t ch);
|
||||
|
||||
#endif /* LIBMISH_SRC_MISH_PRIV_VT_H_ */
|
200
libmish/src/mish_send.c
Normal file
200
libmish/src/mish_send.c
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* mish_send.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "mish_priv.h"
|
||||
#include "mish_priv_line.h"
|
||||
|
||||
/*
|
||||
* We arrive here with an array of iovec buffers already prepared by
|
||||
* _mish_send_queue_*();
|
||||
* This is called repeatedly, and we continually call writev, gather how
|
||||
* many bytes have been written, and mark each corresponding buffer as done.
|
||||
* Once all the iovec buffers have been filled, we're done!
|
||||
*
|
||||
* All of this is made to reduce to the max the number of 'write' system
|
||||
* calls.
|
||||
*
|
||||
* There are two kinds of buffers in the vectors:
|
||||
* 1) the "backlog" lines themselves. These have a pointer, all is well and good.
|
||||
* 2) sequences we are making up on the fly, like prompt, colors etc; these
|
||||
* are allocated in a 'sqb' (sequence buffer) which can grow (via realloc)
|
||||
* as more stuff is added.
|
||||
* Therefore, we can't pre-populate the vector array with the iov_base
|
||||
* pointers, we keep these to NULL, and in this function, the first thing
|
||||
* we do is derivate the pointers from their 'iov_len' in the current 'sqb'.
|
||||
*
|
||||
* Oh and we "lock" sqb to prevent any other things to be added.
|
||||
*/
|
||||
int
|
||||
_mish_send_flush(
|
||||
mish_p m,
|
||||
mish_client_p c)
|
||||
{
|
||||
/* If it's a new sequence, 'lock' it and prepare it's iovec pointers */
|
||||
if (c->output.sqb) {
|
||||
if (!c->output.sqb->done) {
|
||||
/*
|
||||
* Here we construct the base address of out character buffers.
|
||||
* They start with a NULL address as the sqb could grow when
|
||||
* adding sequences.
|
||||
* So once we are ready to send, we need to compute the
|
||||
* base addresses of everyone that ought to point into the sqb,
|
||||
* then we 'lock' it by marking it 'done'.
|
||||
*/
|
||||
char * base = c->output.sqb->line;
|
||||
for (int i = 0; i < c->output.count; i++) {
|
||||
if (!c->output.v[i].iov_base) {
|
||||
c->output.v[i].iov_base = base;
|
||||
base += c->output.v[i].iov_len;
|
||||
}
|
||||
}
|
||||
c->output.sqb->done = 1; // "lock" the sqb until all is sent.
|
||||
}
|
||||
}
|
||||
if (!c->output.count)
|
||||
return 0;
|
||||
/* if we don't have 'permission' to write yet, ask for it */
|
||||
if (!FD_ISSET(c->output.fd, &m->select.write)) {
|
||||
FD_SET(c->output.fd, &m->select.write);
|
||||
return 1;
|
||||
}
|
||||
/* skip what we've already done */
|
||||
struct iovec * io = c->output.v;
|
||||
int ioc = c->output.count;
|
||||
while (io->iov_len == 0 && ioc) {
|
||||
io++; ioc--;
|
||||
}
|
||||
int res = 1;
|
||||
if (ioc) {
|
||||
ssize_t got = writev(c->output.fd, io, ioc);
|
||||
if (got == -1) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
// we've been close down?
|
||||
FD_CLR(c->output.fd, &m->select.write);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* fill up vectors with what was written */
|
||||
while (got > 0) {
|
||||
ssize_t b = got > io->iov_len ? io->iov_len : got;
|
||||
io->iov_len -= b;
|
||||
io->iov_base += b;
|
||||
if (io->iov_len == 0) {
|
||||
io++;
|
||||
ioc--;
|
||||
}
|
||||
got -= b;
|
||||
}
|
||||
}
|
||||
if (ioc == 0) { // done!
|
||||
done:
|
||||
res = 0;
|
||||
c->output.count = 0;
|
||||
// also flush out temporary buffers, and unlock it
|
||||
if (c->output.sqb) {
|
||||
c->output.sqb->done = 0;
|
||||
c->output.sqb->len = 0;
|
||||
}
|
||||
/* if nothing else is ready to send, clear us from the select loop */
|
||||
if (!c->sending)
|
||||
FD_CLR(c->output.fd, &m->select.write);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add up a bit of output to something we want to send as a sequence
|
||||
*/
|
||||
static char *
|
||||
_mish_send_prep(
|
||||
mish_client_p c,
|
||||
size_t l)
|
||||
{
|
||||
if (c->output.sqb && c->output.sqb->done) {
|
||||
fprintf(stderr, "%s problem\n", __func__);
|
||||
abort();
|
||||
}
|
||||
c->output.total += l;
|
||||
/* allocate enough in the character buffer */
|
||||
_mish_line_reserve(&c->output.sqb, l + 1);
|
||||
/* allocate enough in the vector buffer */
|
||||
if (c->output.count == c->output.size) {
|
||||
c->output.size += 8;
|
||||
c->output.v = realloc(c->output.v, c->output.size * sizeof(c->output.v[0]));
|
||||
}
|
||||
/* this is where the calling function will copy it's data */
|
||||
char * res = (char*)c->output.sqb->line + c->output.sqb->len;
|
||||
c->output.sqb->len += l;
|
||||
/* try to concatenate buffers together, if they are 'ours' */
|
||||
if (c->output.count && c->output.v[c->output.count-1].iov_base == NULL) {
|
||||
struct iovec *v = c->output.v + c->output.count - 1;
|
||||
v->iov_len += l;
|
||||
} else {
|
||||
struct iovec *v = c->output.v + c->output.count;
|
||||
v->iov_base = NULL; // important, see top of the file for details
|
||||
v->iov_len = l;
|
||||
v++;
|
||||
c->output.count++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add up a bit of output to something we want to send as a sequence
|
||||
*/
|
||||
void
|
||||
_mish_send_queue(
|
||||
mish_client_p c,
|
||||
const char * b)
|
||||
{
|
||||
int l = strlen(b);
|
||||
char *d = _mish_send_prep(c, l);
|
||||
|
||||
strcpy(d, b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as above, but with formatting.
|
||||
*/
|
||||
void
|
||||
_mish_send_queue_fmt(
|
||||
mish_client_p c,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int l = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
char *d = _mish_send_prep(c, l);
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(d, l + 1, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
_mish_send_queue_line(
|
||||
mish_client_p c,
|
||||
mish_line_p line )
|
||||
{
|
||||
/* allocate enough in the vector buffer */
|
||||
if (c->output.count == c->output.size) {
|
||||
c->output.size += 8;
|
||||
c->output.v = realloc(c->output.v, c->output.size * sizeof(c->output.v[0]));
|
||||
}
|
||||
struct iovec *v = c->output.v + c->output.count;
|
||||
v->iov_base = line->line;
|
||||
v->iov_len = line->len;
|
||||
c->output.count++;
|
||||
c->output.total += line->len;
|
||||
}
|
282
libmish/src/mish_session.c
Normal file
282
libmish/src/mish_session.c
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* mish_session.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> // for isatty etc
|
||||
|
||||
#ifdef __MACH__
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
#include <util.h> // for openpty
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pty.h>
|
||||
#endif
|
||||
|
||||
#include "mish_priv.h"
|
||||
#include "mish.h"
|
||||
|
||||
/*
|
||||
* get a localtime equivalent, with milliseconds added
|
||||
*/
|
||||
uint64_t
|
||||
_mish_stamp_ms()
|
||||
{
|
||||
struct timespec tim;
|
||||
#ifdef __MACH__
|
||||
clock_serv_t cclock;
|
||||
mach_timespec_t mts;
|
||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
||||
clock_get_time(cclock, &mts);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
tim.tv_sec = mts.tv_sec;
|
||||
tim.tv_nsec = mts.tv_nsec;
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &tim);
|
||||
#endif
|
||||
|
||||
uint64_t res = (tim.tv_sec * 1000) + (tim.tv_nsec / 1000000);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* need to keep this around for atexit()
|
||||
*/
|
||||
static mish_p _mish = NULL;
|
||||
|
||||
static void
|
||||
_mish_atexit()
|
||||
{
|
||||
if (_mish)
|
||||
mish_terminate(_mish);
|
||||
}
|
||||
|
||||
mish_p
|
||||
mish_prepare(
|
||||
unsigned long caps )
|
||||
{
|
||||
/* In production, of if you want to make sure mish is disabled without
|
||||
* re-linking, set this env variable.
|
||||
*/
|
||||
if (getenv("MISH_OFF")) {
|
||||
if (atoi(getenv("MISH_OFF"))) {
|
||||
unsetenv("MISH_TELNET_PORT");
|
||||
printf("mish: Disabled my MISH_OFF\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mish_p m = (mish_p)calloc(1, sizeof(*m));
|
||||
|
||||
FD_ZERO(&m->select.read);
|
||||
FD_ZERO(&m->select.write);
|
||||
TAILQ_INIT(&m->backlog.log);
|
||||
TAILQ_INIT(&m->clients);
|
||||
m->flags = caps;
|
||||
int tty = 0;
|
||||
if (getenv("MISH_TTY")) {
|
||||
tty = atoi(getenv("MISH_TTY"));
|
||||
} else
|
||||
tty = (isatty(0) && isatty(1) && isatty(2)) ||
|
||||
(caps & MISH_CAP_FORCE_PTY);
|
||||
int io[2]; // stdout pipe
|
||||
int ie[2]; // stderr pipe
|
||||
#if !defined(__wasm__)
|
||||
{
|
||||
char pty[80];
|
||||
if (openpty(&io[0], &io[1], pty, NULL, NULL) == -1) {
|
||||
perror("openpty");
|
||||
goto error;
|
||||
}
|
||||
if (!(caps & MISH_CAP_NO_STDERR)) {
|
||||
if (openpty(&ie[0], &ie[1], pty, NULL, NULL) == -1) {
|
||||
perror("openpty");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (tty)
|
||||
m->flags |= MISH_CONSOLE_TTY;
|
||||
if (tcgetattr(0, &m->orig_termios))
|
||||
perror("tcgetattr");
|
||||
struct termios raw = m->orig_termios;
|
||||
raw.c_iflag &= ~(ICRNL | IXON);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // ISIG
|
||||
if (tcsetattr(0, TCSAFLUSH, &raw))
|
||||
perror("tcsetattr");
|
||||
}
|
||||
#endif
|
||||
if (!(caps & MISH_CAP_NO_TELNET)) {
|
||||
uint16_t port = 0; // suggested telnet port
|
||||
if (getenv("MISH_TELNET_PORT"))
|
||||
port = atoi(getenv("MISH_TELNET_PORT"));
|
||||
|
||||
if (mish_telnet_prepare(m, port) == 0) {
|
||||
char port[8];
|
||||
snprintf(port, sizeof(port), "%d", m->telnet.port);
|
||||
setenv("MISH_TELNET_PORT", port, 1);
|
||||
} else
|
||||
unsetenv("MISH_TELNET_PORT");
|
||||
}
|
||||
|
||||
// backup the existing descriptors, to make a 'client', we replace
|
||||
// the original 1,2 with out own pipe/pty
|
||||
m->originals[0] = dup(1);
|
||||
m->originals[1] = dup(2);
|
||||
// we do another dup(0,1) here, as that client will close() them
|
||||
m->console = mish_client_new(m, dup(0), dup(1), tty);
|
||||
m->stamp_start = _mish_stamp_ms();
|
||||
|
||||
_mish_input_init(m, &m->origin[0], io[0]);
|
||||
if (dup2(io[1], 1) == -1) {
|
||||
perror("dup2");
|
||||
goto error;
|
||||
}
|
||||
if (!(caps & MISH_CAP_NO_STDERR)) {
|
||||
_mish_input_init(m, &m->origin[1], ie[0]);
|
||||
if (dup2(ie[1], 2) == -1) {
|
||||
perror("dup2");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
atexit(_mish_atexit);
|
||||
// m->main = pthread_self();
|
||||
// TODO: Make an epoll polling thread for linux
|
||||
pthread_create(&m->capture, NULL, _mish_capture_select, m);
|
||||
|
||||
_mish = m;
|
||||
return m;
|
||||
error:
|
||||
if (io[0]) {
|
||||
close(io[0]);
|
||||
close(io[1]);
|
||||
}
|
||||
if (ie[0]) {
|
||||
close(ie[0]);
|
||||
close(ie[1]);
|
||||
}
|
||||
free(m);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
mish_get_flags(
|
||||
struct mish_t *m)
|
||||
{
|
||||
return m ? m->flags : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This can be called at any time; if the thread is running, tell it to
|
||||
* close everything and exit... then wait for a little bit for it to
|
||||
* terminate, then restore the terminal.
|
||||
*/
|
||||
void
|
||||
mish_terminate(
|
||||
mish_p m)
|
||||
{
|
||||
if (!_mish)
|
||||
return;
|
||||
// restore normal IOs
|
||||
dup2(m->originals[0], 1);
|
||||
dup2(m->originals[1], 2);
|
||||
// the thread does this, but we also do it in case this function is
|
||||
// called from another exit(x)
|
||||
#if !defined(__wasm__)
|
||||
if ((m->flags & MISH_CONSOLE_TTY) &&
|
||||
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
|
||||
perror("mish_terminate tcsetattr");
|
||||
#endif
|
||||
close(m->originals[0]); close(m->originals[1]);
|
||||
if (m->capture) {
|
||||
m->flags |= MISH_QUIT;
|
||||
// this will wake the select() call from sleep
|
||||
if (write(1, "\n", 1))
|
||||
;
|
||||
|
||||
time_t start = time(NULL);
|
||||
time_t now;
|
||||
while (((now = time(NULL)) - start < 2) && m->capture)
|
||||
usleep(1000);
|
||||
}
|
||||
printf("\033[4l\033[;r\033[999;1H"); fflush(stdout);
|
||||
printf("%s done\n", __func__);
|
||||
free(m);
|
||||
_mish = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_mish_cmd_quit(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: Quitting."
|
||||
MISH_COLOR_RESET "\n");
|
||||
|
||||
mish_p m = ((mish_client_p)param)->mish;
|
||||
m->flags |= MISH_QUIT;
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(quit, "q", "quit");
|
||||
MISH_CMD_HELP(quit,
|
||||
"exit running program",
|
||||
"Close all clients and exit(0)");
|
||||
MISH_CMD_REGISTER(quit, _mish_cmd_quit);
|
||||
|
||||
#define VT_COL(_c) "\033[" #_c "G"
|
||||
|
||||
static void
|
||||
_mish_cmd_mish(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
printf(MISH_COLOR_RED
|
||||
"mish: mish command."
|
||||
MISH_COLOR_RESET "\n");
|
||||
|
||||
mish_p m = ((mish_client_p)param)->mish;
|
||||
printf("Backlog: %6d lines (%5dKB)" VT_COL(40) "Telnet Port: %5d\n",
|
||||
m->backlog.size,
|
||||
(int)m->backlog.alloc / 1024,
|
||||
m->telnet.port);
|
||||
#if 0
|
||||
printf(" read: ");
|
||||
for (int i = 0; i < m->select.max; i++)
|
||||
if (FD_ISSET(i, &m->select.read)) printf("%d ", i);
|
||||
printf(VT_COL(40) " write: ");
|
||||
for (int i = 0; i < m->select.max; i++)
|
||||
if (FD_ISSET(i, &m->select.write)) printf("%d ", i);
|
||||
printf("\n");
|
||||
#endif
|
||||
mish_client_p c;
|
||||
TAILQ_FOREACH(c, &m->clients, self) {
|
||||
printf(" Client: r: %d w: %d %s %s\n",
|
||||
c->input.fd, c->output.fd,
|
||||
c->input.is_telnet ? "telnet session" :
|
||||
c == m->console ? "console" : "*unknown*",
|
||||
c == m->console ?
|
||||
m->flags & MISH_CONSOLE_TTY ? "(tty)": "(dumb)"
|
||||
: "");
|
||||
printf(" max sizes: vector: %d input: %d\n",
|
||||
c->output.size, c->input.line ? c->input.line->size : 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(mish, "mish");
|
||||
MISH_CMD_HELP(mish,
|
||||
"Displays mish status.",
|
||||
"Show status and a few bits of internals.");
|
||||
MISH_CMD_REGISTER(mish, _mish_cmd_mish);
|
||||
|
241
libmish/src/mish_telnet.c
Normal file
241
libmish/src/mish_telnet.c
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* mish_telnet.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "mish_priv.h"
|
||||
|
||||
#ifdef DEBUG_TELNET
|
||||
#define TELCMDS
|
||||
#define TELOPTS
|
||||
#define TV(w) w
|
||||
#else
|
||||
#define TV(w)
|
||||
#endif
|
||||
#include <arpa/telnet.h>
|
||||
|
||||
/*
|
||||
* Ask remote telnet client to turn off echo, and send us the NAWS
|
||||
*/
|
||||
void
|
||||
mish_telnet_send_init(
|
||||
mish_client_p c)
|
||||
{
|
||||
const unsigned char init[] = {
|
||||
IAC, DO, TELOPT_ECHO,
|
||||
IAC, DO, TELOPT_NAWS,
|
||||
IAC, WILL, TELOPT_ECHO,
|
||||
IAC, WILL, TELOPT_SGA,
|
||||
0,
|
||||
};
|
||||
_mish_send_queue(c, (char*)init);
|
||||
}
|
||||
|
||||
/*
|
||||
* This looks for the TELNET IAC prefix, and handles /some/ of them.
|
||||
* The one we're really interested in anyway is the NAWS aka Window Size
|
||||
* sequence.
|
||||
* All the other ones are ignored, but parsed. Ish.
|
||||
*/
|
||||
int
|
||||
_mish_telnet_parse(
|
||||
mish_client_p c,
|
||||
uint8_t ch)
|
||||
{
|
||||
TV(printf("%02x(%d) %s (seq %06x)\n",
|
||||
ch, ch, ch >= xEOF ? telcmds[ch-xEOF] : "",
|
||||
c->vts.seq);)
|
||||
/*
|
||||
* To handle telnet sequences, we pause the VT decoder
|
||||
*/
|
||||
switch (c->vts.seq) {
|
||||
case MISH_VT_RAW:
|
||||
if (ch == IAC) {
|
||||
c->vts.seq = MISH_VT_TELNET;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case MISH_VT_TELNET: {
|
||||
switch (ch) {
|
||||
case WILL:
|
||||
case WONT:
|
||||
case DO:
|
||||
case DONT:
|
||||
case SB:
|
||||
c->vts.seq = c->vts.seq << 8 | ch;
|
||||
return 1;
|
||||
default:
|
||||
case SE:
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
return 1;
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_SEQ(TELNET, IAC):
|
||||
TV(printf("IAC %d %s\n\n", ch, telopts[ch]);)
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
break; // let this one thru!!!
|
||||
case MISH_VT_SEQ(TELNET, WILL):
|
||||
TV(printf("WILL %d %s\n\n", ch, telopts[ch]);)
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
return 1;
|
||||
case MISH_VT_SEQ(TELNET, WONT):
|
||||
TV(printf("WONT %d %s\n", ch, telopts[ch]);)
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
return 1;
|
||||
case MISH_VT_SEQ(TELNET, DO):
|
||||
TV(printf("DO %d %s\n", ch, telopts[ch]);)
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
return 1;
|
||||
case MISH_VT_SEQ(TELNET, DONT):
|
||||
TV(printf("DONT %d %s\n", ch, telopts[ch]);)
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
return 1;
|
||||
case MISH_VT_SEQ(TELNET, SB):
|
||||
TV(printf("SB %d %s\n", ch, telopts[ch]);)
|
||||
c->vts.seq_want = 0;
|
||||
c->vts.p[0] = 0;
|
||||
switch (ch) {
|
||||
case TELOPT_NAWS:
|
||||
c->vts.seq = c->vts.seq << 8 | ch;
|
||||
return 1;
|
||||
default:
|
||||
if (ch == IAC) {
|
||||
c->vts.seq = MISH_VT_TELNET;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
case (MISH_VT_SEQ(TELNET, SB) << 8 | (TELOPT_NAWS)): {
|
||||
switch (c->vts.seq_want++) {
|
||||
case 0:
|
||||
c->vts.p[0] = 0;
|
||||
c->vts.p[1] = ch;
|
||||
return 1;
|
||||
case 1: c->vts.p[1] = (c->vts.p[1] << 8) | ch; return 1;
|
||||
case 2: c->vts.p[0] = ch; return 1;
|
||||
case 3: c->vts.p[0] = (c->vts.p[0] << 8) | ch; /* fallthru */
|
||||
}
|
||||
if (c->vts.seq_want == 4) {
|
||||
TV(printf("GOT NAWS: %dx%d\n", c->vts.p[0], c->vts.p[1]);)
|
||||
c->window_size.h = c->vts.p[0];
|
||||
c->window_size.w = c->vts.p[1];
|
||||
c->flags |= MISH_CLIENT_HAS_WINDOW_SIZE |
|
||||
MISH_CLIENT_UPDATE_WINDOW;
|
||||
c->vts.seq = MISH_VT_RAW;
|
||||
c->vts.done = 1;
|
||||
return 1;
|
||||
}
|
||||
} return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern const char *__progname;
|
||||
|
||||
/*
|
||||
* not going to go thru all the bits regarding sockets here, you know it.
|
||||
*
|
||||
* We allocate a random port, and listen to it, then add it to the
|
||||
* main select() thread.
|
||||
*/
|
||||
int
|
||||
mish_telnet_prepare(
|
||||
mish_p m,
|
||||
uint16_t port)
|
||||
{
|
||||
m->telnet.listen = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int flag = 1;
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
if (setsockopt(m->telnet.listen, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)))
|
||||
perror("SO_REUSEADDR");
|
||||
|
||||
struct sockaddr_in b = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
// attempts to generate a pseudo random port based on the executable name
|
||||
// before having to rely on random(). This gives a chance of 'unique' ports
|
||||
// per programs.
|
||||
if (port == 0) {
|
||||
for (int i = 0; __progname[i]; i++)
|
||||
port += __progname[i] + i;
|
||||
if (port < 1024)
|
||||
port += 1024;
|
||||
port = port & 0x3fff;
|
||||
}
|
||||
|
||||
int tries = 5;
|
||||
do {
|
||||
b.sin_port = htons(port);
|
||||
if (bind(m->telnet.listen, (struct sockaddr *)&b, sizeof(b)) == -1) {
|
||||
fprintf(stderr, "%s can't bind %d\n", __func__, ntohs(b.sin_port));
|
||||
port = port + (random() & 0x3ff);
|
||||
continue;
|
||||
}
|
||||
m->telnet.port = ntohs(b.sin_port);
|
||||
printf(MISH_COLOR_GREEN
|
||||
"mish: telnet port on %d"
|
||||
MISH_COLOR_RESET "\n",
|
||||
m->telnet.port);
|
||||
break;
|
||||
} while (tries-- > 0);
|
||||
if (tries) {
|
||||
if (listen(m->telnet.listen, 2) == -1) {
|
||||
perror("mish_telnet_prepare listen");
|
||||
goto error;
|
||||
}
|
||||
FD_SET(m->telnet.listen, &m->select.read);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "mish: %s failed\n", __func__);
|
||||
error:
|
||||
close(m->telnet.listen);
|
||||
m->telnet.listen = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accept a new connection from the telnet port, add a new client
|
||||
*/
|
||||
int
|
||||
mish_telnet_in_check(
|
||||
mish_p m,
|
||||
fd_set * r)
|
||||
{
|
||||
if (!FD_ISSET(m->telnet.listen, r))
|
||||
return 0;
|
||||
|
||||
struct sockaddr_in a = {};
|
||||
socklen_t al = sizeof(a);
|
||||
int tf = accept(m->telnet.listen, (struct sockaddr *) &a, &al);
|
||||
if (tf < 0) {
|
||||
perror(__func__);
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* it is necessary to have TWO file descriptors here, otherwise
|
||||
* the FD_SET/FD_CLR logic could be confusing as using the same bits.
|
||||
* a dup() isn't terribly expensive and guarantees we don't have to
|
||||
* worry about it.
|
||||
*/
|
||||
mish_client_p c = mish_client_new(m, tf, dup(tf), 1 /* tty */);
|
||||
c->input.is_telnet = 1;
|
||||
|
||||
printf(MISH_COLOR_GREEN
|
||||
"mish: telnet: connected."
|
||||
MISH_COLOR_RESET "\n");
|
||||
|
||||
return 0;
|
||||
}
|
105
libmish/src/mish_vt.c
Normal file
105
libmish/src/mish_vt.c
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* mish_vt.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "mish_priv_vt.h"
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(_a) (sizeof(_a) / sizeof((_a)[0]))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Handle sequences of VT100 and UTF8 chars.
|
||||
*
|
||||
* Uses the sequence 's' as temporary buffer, and returns 1 when a complete
|
||||
* sequence OR a glyph has been decoded.
|
||||
*/
|
||||
int
|
||||
_mish_vt_sequence_char(
|
||||
mish_vt_sequence_p s,
|
||||
uint8_t ch)
|
||||
{
|
||||
// if previous sequence was done, reset it.
|
||||
if (s->done)
|
||||
s->flags = s->seq = s->glyph = s->pc = s->p[0] = 0;
|
||||
switch (s->seq) {
|
||||
case MISH_VT_RAW: {
|
||||
switch (ch) {
|
||||
case 0x1b:
|
||||
s->seq = MISH_VT_ESC;
|
||||
break;
|
||||
default: {
|
||||
if (ch & 0x80) { // UTF8 !??!
|
||||
int mask = 0x40;
|
||||
s->seq_want = 0;
|
||||
while ((ch & mask) && s->seq_want < 5) {
|
||||
s->seq_want++;
|
||||
mask >>= 1;
|
||||
}
|
||||
// clear header bits (ought to be 2; 1 allows encoding of 0xffffff)
|
||||
s->glyph = ch & (0xff >> (s->seq_want + 1));
|
||||
s->seq = MISH_VT_UTF8;
|
||||
} else {
|
||||
s->seq = (s->seq << 8) | ch;
|
||||
s->glyph = ch; // technically it's a glyph
|
||||
s->done = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_ESC: {
|
||||
if (ch == '[')
|
||||
s->seq = MISH_VT_CSI;
|
||||
else {
|
||||
s->seq = (s->seq << 8) | ch;
|
||||
s->done = 1;
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_CSIQ:
|
||||
case MISH_VT_CSI: {
|
||||
switch (ch) {
|
||||
case '?': {
|
||||
// note this would still accept somelike like CSI ? 00?; 1
|
||||
if ((s->seq == MISH_VT_CSIQ) || s->pc) {
|
||||
s->done = s->error = 1;
|
||||
} else
|
||||
s->seq = MISH_VT_CSIQ;
|
||||
} break;
|
||||
case '0' ... '9':
|
||||
s->p[s->pc] = (s->p[s->pc] * 10) + (ch - '0');
|
||||
break;
|
||||
case ';': {
|
||||
if (s->pc < ARRAY_SIZE(s->p)) {
|
||||
s->pc++;
|
||||
s->p[s->pc] = 0;
|
||||
} else {
|
||||
s->done = s->error = 1;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
if (s->p[s->pc])
|
||||
s->pc++;
|
||||
s->seq = (s->seq << 8) | ch;
|
||||
s->done = 1;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case MISH_VT_UTF8: {
|
||||
s->glyph = (s->glyph << 6) | (ch & 0x3f);
|
||||
if (s->seq_want) s->seq_want--;
|
||||
s->done = s->seq_want == 0;
|
||||
} break;
|
||||
}
|
||||
#ifdef MISH_VT_DEBUG
|
||||
if (s->done)
|
||||
fprintf(stderr, "in %x(%c) state %x done:%d error:%d pc:%d/%d\n",
|
||||
ch, ch < ' '?'.':ch,s->seq, s->done, s->error,
|
||||
s->pc, (int)ARRAY_SIZE(s->p));
|
||||
#endif
|
||||
return s->done;
|
||||
}
|
95
libmish/tests/mish_argv_make_test.c
Normal file
95
libmish/tests/mish_argv_make_test.c
Normal file
@ -0,0 +1,95 @@
|
||||
// small test unit for mish_argv_make
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
typedef struct _mish_argv_t {
|
||||
char * line;
|
||||
int ac;
|
||||
char * av[0];
|
||||
} _mish_argv_t;
|
||||
/*
|
||||
* Duplicate 'line', split it into words, store word pointers in an array,
|
||||
* NULL terminate it. Also return the number of words in the array in argc.
|
||||
*
|
||||
* The returned value is made of two malloc()ed blocks. use mish_argv_free
|
||||
* to free the memory.
|
||||
* It's OK to change any of the pointers. But no not try to realloc() the
|
||||
* vector as it hides a structure
|
||||
*/
|
||||
static char **
|
||||
mish_argv_make(
|
||||
const char * line,
|
||||
int * argc )
|
||||
{
|
||||
const char separator = ' ';
|
||||
_mish_argv_t * r = calloc(1, sizeof(*r));
|
||||
r->line = strdup(line);
|
||||
char *dup = r->line;
|
||||
char quote;
|
||||
enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy };
|
||||
int state = s_newarg;
|
||||
do {
|
||||
switch (state) {
|
||||
case s_newarg:
|
||||
r = realloc(r, sizeof(*r) + ((r->ac + 2) * sizeof(char*)));
|
||||
while (*dup == ' ' || *dup == separator)
|
||||
dup++;
|
||||
r->av[r->ac++] = dup;
|
||||
state = s_startarg;
|
||||
break;
|
||||
case s_startarg:
|
||||
if (*dup == '"' || *dup == '\'') {
|
||||
quote = *dup++;
|
||||
state = s_copyquote;
|
||||
} else
|
||||
state = s_copy;
|
||||
break;
|
||||
case s_copyquote:
|
||||
if (*dup == '\\')
|
||||
state = s_skip;
|
||||
else if (*dup == quote) {
|
||||
state = s_newarg;
|
||||
dup++;
|
||||
if (*dup) *dup++ = 0;
|
||||
} else if (*dup)
|
||||
dup++;
|
||||
break;
|
||||
case s_skip:
|
||||
dup++;
|
||||
state = s_copyquote;
|
||||
break;
|
||||
case s_copy:
|
||||
if (*dup == 0)
|
||||
break;
|
||||
if (*dup != separator)
|
||||
dup++;
|
||||
else {
|
||||
state = s_newarg;
|
||||
if (*dup) *dup++ = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (*dup);
|
||||
r->av[r->ac] = NULL;
|
||||
if (argc)
|
||||
*argc = r->ac;
|
||||
return r->av;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int argc = 0;
|
||||
char **argv = mish_argv_make("testing \"one escape two\" lala ", &argc);
|
||||
|
||||
printf("argc = %d\n", argc);
|
||||
for (int i = 0; argv[i]; i++)
|
||||
printf("%2d:'%s'\n", i, argv[i]);
|
||||
|
||||
argv = mish_argv_make("command with some \" quoted\\\"words \" should work\n", &argc);
|
||||
|
||||
printf("argc = %d\n", argc);
|
||||
for (int i = 0; argv[i]; i++)
|
||||
printf("%2d:'%s'\n", i, argv[i]);
|
||||
|
||||
}
|
20
libmish/tests/mish_cmd_test.c
Normal file
20
libmish/tests/mish_cmd_test.c
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* mish_cmd_test.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mish_cmd.c"
|
||||
|
||||
int main()
|
||||
{
|
||||
int argc;
|
||||
char ** argv = mish_argv_make(
|
||||
"command with some \" quoted\\\"words \" should work\n", &argc);
|
||||
|
||||
for (int i = 0; i < argc; i++)
|
||||
printf("%2d: '%s'\n", i, argv[i]);
|
||||
mish_argv_free(argv);
|
||||
}
|
94
libmish/tests/mish_debug_test.c
Normal file
94
libmish/tests/mish_debug_test.c
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* mish_debug_test.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTE this program is made for debug purpose only, it basically give you
|
||||
* telnet command access remotely
|
||||
* IT IS JUST MADE TO DEBUG LIBMISH, DO NOT USE IN PROD!
|
||||
*/
|
||||
#define _GNU_SOURCE // for asprintf
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "mish.h"
|
||||
|
||||
static void
|
||||
sigchld_handler(int s)
|
||||
{
|
||||
int status = 0;
|
||||
pid_t pid = waitpid(-1, &status, WNOHANG);
|
||||
if (pid > 0) {
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status))
|
||||
fprintf(stderr, "pid %d returned %d\n", (int)pid, WEXITSTATUS(status));
|
||||
if (WIFSIGNALED(status))
|
||||
fprintf(stderr, "pid %d terminated with %s\n",
|
||||
(int)pid, strsignal(WTERMSIG(status)));
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
signal(SIGCHLD, sigchld_handler);
|
||||
mish_prepare(0);
|
||||
|
||||
while (1) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
_shell_which(
|
||||
const char * cmd)
|
||||
{
|
||||
if (cmd[0] == '.' || cmd[0] == '/')
|
||||
return strdup(cmd);
|
||||
char * path = strdup(getenv("PATH"));
|
||||
char * cur = path, *e;
|
||||
while ((e = strsep(&cur, ":")) != NULL) {
|
||||
char *pe;
|
||||
asprintf(&pe, "%s/%s", e, cmd);
|
||||
struct stat st = {};
|
||||
if (stat(pe, &st) == 0) {
|
||||
return pe;
|
||||
}
|
||||
free(pe);
|
||||
}
|
||||
fprintf(stderr, "%s: command not found\n", cmd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
_test_shell_cnt(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
const char * av[argc + 1];
|
||||
av[0] = _shell_which(argv[1]);
|
||||
if (av[0] == NULL)
|
||||
return;
|
||||
for (int ai = 1; ai <= argc; ai++)
|
||||
av[ai] = argv[ai+1];
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execv(av[0], (void*)av);
|
||||
perror(av[0]);
|
||||
close(0);close(1);close(2);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(shell, "shell");
|
||||
MISH_CMD_HELP(shell,
|
||||
"run a shell command",
|
||||
"test command for libmish!");
|
||||
MISH_CMD_REGISTER(shell, _test_shell_cnt);
|
146
libmish/tests/mish_input_test.c
Normal file
146
libmish/tests/mish_input_test.c
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* This is a stress/scenario test for the mish_input_read function
|
||||
*/
|
||||
#define MISH_INPUT_TEST
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mish.h"
|
||||
|
||||
static uint8_t *words = NULL;
|
||||
static size_t words_size = 0;
|
||||
static size_t words_offset = 0;
|
||||
|
||||
static int ccount = 0;
|
||||
|
||||
static ssize_t _test_read(int fd, void *w, size_t count) {
|
||||
errno = 0;
|
||||
|
||||
if (!(ccount++ % 17)) {
|
||||
errno = EAGAIN;
|
||||
printf("%s fake EAGAIN\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (words_offset + count > words_size)
|
||||
count = words_size - words_offset;
|
||||
|
||||
memcpy(w, words + words_offset, count);
|
||||
words_offset += count;
|
||||
if (!count) {
|
||||
printf("%s all input sent\n", __func__);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#define read(_a,_b,_c) _test_read(_a,_b,_c)
|
||||
|
||||
#include "mish_input.c"
|
||||
#include "mish_line.c"
|
||||
|
||||
#undef read
|
||||
|
||||
/* duplicate from mish_session.c */
|
||||
/*
|
||||
* get a localtime equivalent, with milliseconds added
|
||||
*/
|
||||
uint64_t
|
||||
_mish_stamp_ms()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
const char *filen = "/usr/share/dict/american-english";
|
||||
struct stat st;
|
||||
int fd;
|
||||
if ((lstat(filen, &st) == -1) || (fd = open(filen, O_RDONLY)) == -1) {
|
||||
perror(filen);
|
||||
exit(1);
|
||||
}
|
||||
words_size = st.st_size;
|
||||
words = malloc(words_size + 1);
|
||||
if (read(fd, words, st.st_size) != st.st_size) {
|
||||
perror("EOF unexpected");
|
||||
exit(1);
|
||||
}
|
||||
words[words_size] = 0;
|
||||
printf("File %s read\n", filen);
|
||||
|
||||
/* That's not needed for the test, but init() relies on it */
|
||||
mish_t mish = {};
|
||||
mish_p m = &mish;
|
||||
FD_ZERO(&m->select.read);
|
||||
FD_ZERO(&m->select.write);
|
||||
TAILQ_INIT(&m->backlog.log);
|
||||
TAILQ_INIT(&m->clients);
|
||||
|
||||
/* we don't need the file descriptor as we bypass "read" but the
|
||||
* init function relies on it so here is a socketpair for it to play
|
||||
* with */
|
||||
int sk[2];
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk)) {
|
||||
perror("socketpair");
|
||||
exit(1);
|
||||
}
|
||||
mish_input_t input = {};
|
||||
_mish_input_init(m, &input, sk[0]);
|
||||
|
||||
uint8_t *cursor = words;
|
||||
int linec = 0;
|
||||
mish_line_p ln = NULL;
|
||||
|
||||
/*
|
||||
* Right so now we can actually test the function. We keep a running
|
||||
* pointer on the source data, and the "last" line we compared, and
|
||||
* compared line's content with the source buffer. If all goes
|
||||
* well, they should match exactly.
|
||||
*/
|
||||
do {
|
||||
_mish_input_read(m, &m->select.read, &input);
|
||||
printf(" read up to o: %d/%d\n", (int)words_offset, (int)words_size);
|
||||
|
||||
if (!ln)
|
||||
ln = TAILQ_FIRST(&input.backlog);
|
||||
else if (TAILQ_NEXT(ln, self))
|
||||
ln = TAILQ_NEXT(ln, self);
|
||||
else
|
||||
continue;
|
||||
while (ln) {
|
||||
linec++;
|
||||
// printf("line %4d: %s", linec, ln->line);
|
||||
|
||||
uint8_t * l = (uint8_t*)ln->line;
|
||||
for (int ic = 0; ic < ln->len; ic++, cursor++) {
|
||||
if (l[ic] != *cursor) {
|
||||
printf("ERROR: Mismatch Offset %ld char #%d/%d (%x/%x) line %d: %s\n",
|
||||
cursor - words, ic,
|
||||
ln->len,
|
||||
l[ic], *cursor,
|
||||
linec, l);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (TAILQ_LAST(&input.backlog, mish_line_queue_t) == ln)
|
||||
break;
|
||||
ln = TAILQ_NEXT(ln, self);
|
||||
}
|
||||
} while (words_offset < words_size);
|
||||
printf("%d lines were read and compared\n", linec);
|
||||
done:
|
||||
exit(0);
|
||||
|
||||
}
|
||||
|
43
libmish/tests/mish_test.c
Normal file
43
libmish/tests/mish_test.c
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* mish_test.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mish.h"
|
||||
|
||||
int cnt = 0;
|
||||
|
||||
int main()
|
||||
{
|
||||
mish_prepare(0);
|
||||
|
||||
while (1) {
|
||||
sleep(1);
|
||||
printf("Count %d\n", cnt++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* And here is a command line action that can reset the 'cnt' variable */
|
||||
static void _test_set_cnt(void * param, int argc, const char * argv[])
|
||||
{
|
||||
if (argc > 1) {
|
||||
cnt = atoi(argv[1]);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%s: syntax 'set XXX' to set the variable\n", argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
MISH_CMD_NAMES(set, "set");
|
||||
MISH_CMD_HELP(set,
|
||||
"set 'cnt' variable",
|
||||
"test command for libmish!");
|
||||
MISH_CMD_REGISTER(set, _test_set_cnt);
|
33
libmish/tests/mish_vt_test.c
Normal file
33
libmish/tests/mish_vt_test.c
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* mish_vt_test.c
|
||||
*
|
||||
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "../src/mish_vt.c"
|
||||
|
||||
/* small test unit for the VT decoder, mostly for UTF8 */
|
||||
int main()
|
||||
{
|
||||
const char * input = "Hello 😈 \033[1mThere\033[0m";
|
||||
|
||||
mish_vt_sequence_t sq = {};
|
||||
|
||||
printf("%s\n", input);
|
||||
const char *s = input;
|
||||
int cg = 0;
|
||||
while (*s) {
|
||||
if (_mish_vt_sequence_char(&sq, *s)) {
|
||||
printf("glyph s:%08x g:%08x\n", sq.seq, sq.glyph);
|
||||
if (sq.glyph)
|
||||
cg++;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
printf("%d glyphs in string\n", cg);
|
||||
}
|
322
nuklear/mii_emu.c
Normal file
322
nuklear/mii_emu.c
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* mii_emu.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
|
||||
#define NK_INCLUDE_FIXED_TYPES
|
||||
#define NK_INCLUDE_STANDARD_IO
|
||||
#define NK_INCLUDE_STANDARD_VARARGS
|
||||
#define NK_INCLUDE_DEFAULT_ALLOCATOR
|
||||
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
|
||||
#define NK_INCLUDE_FONT_BAKING
|
||||
#define NK_INCLUDE_DEFAULT_FONT
|
||||
#define NK_BUTTON_TRIGGER_ON_RELEASE
|
||||
#include "nuklear.h"
|
||||
|
||||
#define NK_XLIB_GL3_IMPLEMENTATION
|
||||
#define NK_XLIB_LOAD_OPENGL_EXTENSIONS
|
||||
#include "nuklear_xlib_gl3.h"
|
||||
|
||||
#include "mii.h"
|
||||
#include "mish.h"
|
||||
|
||||
#define WINDOW_WIDTH 1280
|
||||
#define WINDOW_HEIGHT 720 // (10 + (192) * 3)
|
||||
|
||||
#define MAX_VERTEX_BUFFER 512 * 1024
|
||||
#define MAX_ELEMENT_BUFFER 128 * 1024
|
||||
|
||||
void
|
||||
mii_nuklear(
|
||||
mii_t *mii,
|
||||
struct nk_context *ctx);
|
||||
void
|
||||
mii_nuklear_init(
|
||||
mii_t *mii,
|
||||
struct nk_context *ctx);
|
||||
/* ===============================================================
|
||||
*
|
||||
* DEMO
|
||||
*
|
||||
* ===============================================================*/
|
||||
struct XWindow {
|
||||
Display *dpy;
|
||||
Window win;
|
||||
XVisualInfo *vis;
|
||||
Colormap cmap;
|
||||
XSetWindowAttributes swa;
|
||||
XWindowAttributes attr;
|
||||
GLXFBConfig fbc;
|
||||
Atom wm_delete_window;
|
||||
int width, height;
|
||||
};
|
||||
static int gl_err = nk_false;
|
||||
static int gl_error_handler(Display *dpy, XErrorEvent *ev)
|
||||
{NK_UNUSED(dpy); NK_UNUSED(ev); gl_err = nk_true;return 0;}
|
||||
|
||||
static void
|
||||
die(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputs("\n", stderr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int
|
||||
has_extension(const char *string, const char *ext)
|
||||
{
|
||||
const char *start, *where, *term;
|
||||
where = strchr(ext, ' ');
|
||||
if (where || *ext == '\0')
|
||||
return nk_false;
|
||||
|
||||
for (start = string;;) {
|
||||
where = strstr((const char*)start, ext);
|
||||
if (!where) break;
|
||||
term = where + strlen(ext);
|
||||
if (where == start || *(where - 1) == ' ') {
|
||||
if (*term == ' ' || *term == '\0')
|
||||
return nk_true;
|
||||
}
|
||||
start = term;
|
||||
}
|
||||
return nk_false;
|
||||
}
|
||||
|
||||
struct nk_font *nk_main_font = NULL;
|
||||
|
||||
/* global now, mish commands use it */
|
||||
mii_t g_mii = {};
|
||||
|
||||
int
|
||||
main(
|
||||
int argc,
|
||||
const char *argv[])
|
||||
{
|
||||
/* Platform */
|
||||
int running = 1;
|
||||
struct XWindow win;
|
||||
GLXContext glContext;
|
||||
struct nk_context *ctx;
|
||||
struct nk_colorf bg;
|
||||
|
||||
mii_init(&g_mii);
|
||||
int idx = 0;
|
||||
uint32_t flags = MII_INIT_DEFAULT;
|
||||
int r = mii_argv_parse(&g_mii, argc, argv, &idx, &flags);
|
||||
if (r == 0) {
|
||||
printf("mii: Invalid argument %s, skipped\n", argv[idx]);
|
||||
} else if (r == -1)
|
||||
exit(1);
|
||||
mii_prepare(&g_mii, flags);
|
||||
mish_prepare(1);
|
||||
printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT"));
|
||||
|
||||
memset(&win, 0, sizeof(win));
|
||||
win.dpy = XOpenDisplay(NULL);
|
||||
if (!win.dpy) die("Failed to open X display\n");
|
||||
{
|
||||
/* check glx version */
|
||||
int glx_major, glx_minor;
|
||||
if (!glXQueryVersion(win.dpy, &glx_major, &glx_minor))
|
||||
die("[X11]: Error: Failed to query OpenGL version\n");
|
||||
if ((glx_major == 1 && glx_minor < 3) || (glx_major < 1))
|
||||
die("[X11]: Error: Invalid GLX version!\n");
|
||||
}
|
||||
{
|
||||
/* find and pick matching framebuffer visual */
|
||||
int fb_count;
|
||||
static GLint attr[] = {
|
||||
GLX_X_RENDERABLE, True,
|
||||
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
||||
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
||||
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
GLX_ALPHA_SIZE, 8,
|
||||
GLX_DEPTH_SIZE, 24,
|
||||
GLX_STENCIL_SIZE, 8,
|
||||
GLX_DOUBLEBUFFER, True,
|
||||
None
|
||||
};
|
||||
GLXFBConfig *fbc;
|
||||
fbc = glXChooseFBConfig(win.dpy, DefaultScreen(win.dpy), attr, &fb_count);
|
||||
if (!fbc) die("[X11]: Error: failed to retrieve framebuffer configuration\n");
|
||||
{
|
||||
/* pick framebuffer with most samples per pixel */
|
||||
int i;
|
||||
int fb_best = -1, best_num_samples = -1;
|
||||
for (i = 0; i < fb_count; ++i) {
|
||||
XVisualInfo *vi = glXGetVisualFromFBConfig(win.dpy, fbc[i]);
|
||||
if (vi) {
|
||||
int sample_buffer, samples;
|
||||
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLE_BUFFERS, &sample_buffer);
|
||||
glXGetFBConfigAttrib(win.dpy, fbc[i], GLX_SAMPLES, &samples);
|
||||
if ((fb_best < 0) || (sample_buffer && samples > best_num_samples))
|
||||
fb_best = i, best_num_samples = samples;
|
||||
XFree(vi);
|
||||
}
|
||||
}
|
||||
win.fbc = fbc[fb_best];
|
||||
XFree(fbc);
|
||||
win.vis = glXGetVisualFromFBConfig(win.dpy, win.fbc);
|
||||
}
|
||||
}
|
||||
{
|
||||
/* create window */
|
||||
win.cmap = XCreateColormap(win.dpy, RootWindow(win.dpy, win.vis->screen), win.vis->visual, AllocNone);
|
||||
win.swa.colormap = win.cmap;
|
||||
win.swa.background_pixmap = None;
|
||||
win.swa.border_pixel = 0;
|
||||
win.swa.event_mask =
|
||||
ExposureMask | KeyPressMask | KeyReleaseMask |
|
||||
ButtonPress | ButtonReleaseMask| ButtonMotionMask |
|
||||
Button1MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask|
|
||||
PointerMotionMask| StructureNotifyMask;
|
||||
win.win = XCreateWindow(win.dpy, RootWindow(win.dpy, win.vis->screen), 0, 0,
|
||||
WINDOW_WIDTH, WINDOW_HEIGHT, 0, win.vis->depth, InputOutput,
|
||||
win.vis->visual, CWBorderPixel|CWColormap|CWEventMask, &win.swa);
|
||||
if (!win.win) die("[X11]: Failed to create window\n");
|
||||
XFree(win.vis);
|
||||
|
||||
char title[128];
|
||||
sprintf(title, "MII //e Emulator");
|
||||
char *telnet = getenv("MISH_TELNET_PORT");
|
||||
if (telnet)
|
||||
sprintf(title + strlen(title), " (telnet port %s)", telnet);
|
||||
else
|
||||
sprintf(title + strlen(title), " (telnet disabled)");
|
||||
XStoreName(win.dpy, win.win, title);
|
||||
XMapWindow(win.dpy, win.win);
|
||||
win.wm_delete_window = XInternAtom(win.dpy, "WM_DELETE_WINDOW", False);
|
||||
XSetWMProtocols(win.dpy, win.win, &win.wm_delete_window, 1);
|
||||
}
|
||||
{
|
||||
/* create opengl context */
|
||||
typedef GLXContext(*glxCreateContext)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
|
||||
int(*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(gl_error_handler);
|
||||
const char *extensions_str = glXQueryExtensionsString(win.dpy, DefaultScreen(win.dpy));
|
||||
glxCreateContext create_context = (glxCreateContext)
|
||||
glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
|
||||
|
||||
gl_err = nk_false;
|
||||
if (!has_extension(extensions_str, "GLX_ARB_create_context") || !create_context) {
|
||||
fprintf(stdout, "[X11]: glXCreateContextAttribARB() not found...\n");
|
||||
fprintf(stdout, "[X11]: ... using old-style GLX context\n");
|
||||
glContext = glXCreateNewContext(win.dpy, win.fbc, GLX_RGBA_TYPE, 0, True);
|
||||
} else {
|
||||
GLint attr[] = {
|
||||
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
|
||||
GLX_CONTEXT_MINOR_VERSION_ARB, 0,
|
||||
None
|
||||
};
|
||||
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
|
||||
XSync(win.dpy, False);
|
||||
if (gl_err || !glContext) {
|
||||
/* Could not create GL 3.0 context. Fallback to old 2.x context.
|
||||
* If a version below 3.0 is requested, implementations will
|
||||
* return the newest context version compatible with OpenGL
|
||||
* version less than version 3.0.*/
|
||||
attr[1] = 1; attr[3] = 0;
|
||||
gl_err = nk_false;
|
||||
fprintf(stdout, "[X11] Failed to create OpenGL 3.0 context\n");
|
||||
fprintf(stdout, "[X11] ... using old-style GLX context!\n");
|
||||
glContext = create_context(win.dpy, win.fbc, 0, True, attr);
|
||||
}
|
||||
}
|
||||
XSync(win.dpy, False);
|
||||
XSetErrorHandler(old_handler);
|
||||
if (gl_err || !glContext)
|
||||
die("[X11]: Failed to create an OpenGL context\n");
|
||||
glXMakeCurrent(win.dpy, win.win, glContext);
|
||||
}
|
||||
//extern const unsigned char mii_proggy_data[];
|
||||
//extern const unsigned int mii_proggy_size;
|
||||
extern const unsigned char mii_droid_data[];
|
||||
extern const unsigned int mii_droid_size;
|
||||
|
||||
ctx = nk_x11_init(win.dpy, win.win);
|
||||
/* Load Fonts: if none of these are loaded a default font will be used */
|
||||
{
|
||||
struct nk_font_atlas *atlas;
|
||||
nk_x11_font_stash_begin(&atlas);
|
||||
struct nk_font_config cfg = nk_font_config(0);
|
||||
#if 0
|
||||
nk_rune ranges[] = {
|
||||
0x0020, 0x007E, /* Ascii */
|
||||
0x00A1, 0x00FF, /* Symbols + Umlaute */
|
||||
0
|
||||
};
|
||||
#endif
|
||||
cfg.range = nk_font_default_glyph_ranges();
|
||||
cfg.oversample_h = cfg.oversample_v = 1;
|
||||
cfg.pixel_snap = true;
|
||||
nk_main_font = nk_font_atlas_add_from_memory(atlas,
|
||||
(void*)mii_droid_data, mii_droid_size, 20, &cfg);
|
||||
nk_x11_font_stash_end();
|
||||
/*nk_style_load_all_cursors(ctx, atlas->cursors);*/
|
||||
if (nk_main_font)
|
||||
nk_style_set_font(ctx, &nk_main_font->handle);
|
||||
}
|
||||
|
||||
mii_nuklear_init(&g_mii, ctx);
|
||||
|
||||
bg.r = 0.0f; bg.g = 0.0f; bg.b = 0.0f; bg.a = 1.0f;
|
||||
// bg.r = 0.8f; bg.g = 0.8f; bg.b = 0.8f; bg.a = 1.0f;
|
||||
while (running) {
|
||||
/* Input */
|
||||
XEvent evt;
|
||||
nk_input_begin(ctx);
|
||||
while (XPending(win.dpy)) {
|
||||
XNextEvent(win.dpy, &evt);
|
||||
if (evt.type == ClientMessage) goto cleanup;
|
||||
if (XFilterEvent(&evt, win.win)) continue;
|
||||
nk_x11_handle_event(&evt);
|
||||
}
|
||||
nk_input_end(ctx);
|
||||
|
||||
/* GUI */
|
||||
mii_nuklear(&g_mii, ctx);
|
||||
|
||||
/* Draw */
|
||||
XGetWindowAttributes(win.dpy, win.win, &win.attr);
|
||||
glViewport(0, 0, win.width, win.height);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glClearColor(bg.r, bg.g, bg.b, bg.a);
|
||||
/* IMPORTANT: `nk_x11_render` modifies some global OpenGL state
|
||||
* with blending, scissor, face culling, depth test and viewport and
|
||||
* defaults everything back into a default state.
|
||||
* Make sure to either a.) save and restore or b.) reset your own state after
|
||||
* rendering the UI. */
|
||||
nk_x11_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
|
||||
glXSwapBuffers(win.dpy, win.win);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
nk_x11_shutdown();
|
||||
glXMakeCurrent(win.dpy, 0, 0);
|
||||
glXDestroyContext(win.dpy, glContext);
|
||||
XUnmapWindow(win.dpy, win.win);
|
||||
XFreeColormap(win.dpy, win.cmap);
|
||||
XDestroyWindow(win.dpy, win.win);
|
||||
XCloseDisplay(win.dpy);
|
||||
return 0;
|
||||
|
||||
}
|
9
nuklear/mii_fonts.c
Normal file
9
nuklear/mii_fonts.c
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
|
||||
#define INCBIN_PREFIX mii_
|
||||
#include "incbin.h"
|
||||
|
||||
INCBIN(proggy, "fonts/ProggyClean.ttf");
|
||||
INCBIN(droid, "fonts/DroidSans.ttf");
|
||||
|
436
nuklear/mii_mish.c
Normal file
436
nuklear/mii_mish.c
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
* mii_mish.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_sw.h"
|
||||
#include "mii_65c02_ops.h"
|
||||
#include "mii_65c02_disasm.h"
|
||||
|
||||
extern mii_t g_mii;
|
||||
|
||||
void
|
||||
_mii_mish_text(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
// load 0x400, calculate the 24 line addresses from the code in video
|
||||
// and show the 40 or 80 chars, depending on col80
|
||||
mii_t * mii = &g_mii;
|
||||
uint16_t a = 0x400;
|
||||
int page2 = mii_read_one(mii, SWPAGE2);
|
||||
// int col80 = mii_read_one(mii, SW80COL);
|
||||
for (int li = 0; li < 24; li++) {
|
||||
int i = li;
|
||||
a = (0x400 + (0x400 * page2));
|
||||
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
|
||||
printf("%04x: ", a);
|
||||
for (int ci = 0; ci < 40; ci++) {
|
||||
uint8_t c = (mii_read_one(mii, a++) & 0x3f);
|
||||
printf("%c", c >= 0x20 && c < 0x7f ? c : '.');
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_mish_cmd(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
const char * state[] = { "RUNNING", "STOPPED", "STEP" };
|
||||
mii_t * mii = &g_mii;
|
||||
if (!argv[1]) {
|
||||
show_state:
|
||||
printf("mii: %s Target speed: %.3fMHz Current: %.3fMHz\n",
|
||||
state[mii->state], mii->speed, mii->speed_current);
|
||||
mii_dump_run_trace(mii);
|
||||
mii_dump_trace_state(mii);
|
||||
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
bool text = !!mii_bank_peek(main, SWTEXT);
|
||||
bool page2 = !!mii_bank_peek(main, SWPAGE2);
|
||||
bool col80 = !!mii_bank_peek(main, SW80COL);
|
||||
bool mixed = !!mii_bank_peek(main, SWMIXED);
|
||||
bool hires = !!mii_bank_peek(main, SWHIRES);
|
||||
bool dhires = !!mii_bank_peek(main, SWRDDHIRES);
|
||||
|
||||
printf("text:%d page2:%d col80:%d mixed:%d hires:%d dhires:%d\n",
|
||||
text, page2, col80, mixed, hires, dhires);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "reset")) {
|
||||
mii_reset(mii, 0);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "mem")) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
printf("%02x: ", i * 16);
|
||||
for (int j = 0; j < 16; j++)
|
||||
printf("%d:%d ", mii->mem[(i * 16) + j].read,
|
||||
mii->mem[(i * 16) + j].write);
|
||||
printf("\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "trace")) {
|
||||
mii->trace_cpu = !mii->trace_cpu;
|
||||
printf("trace_cpu %d\n", mii->trace_cpu);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "poke")) {
|
||||
if (argc < 4) {
|
||||
printf("poke: missing argument\n");
|
||||
return;
|
||||
}
|
||||
uint16_t addr = strtol(argv[2], NULL, 16);
|
||||
uint8_t val = strtol(argv[3], NULL, 16);
|
||||
mii_mem_access(mii, addr, &val, false, true);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "peek")) {
|
||||
if (argc < 3) {
|
||||
printf("peek: missing argument\n");
|
||||
return;
|
||||
}
|
||||
uint16_t addr = strtol(argv[2], NULL, 16);
|
||||
uint8_t val;
|
||||
mii_mem_access(mii, addr, &val, false, false);
|
||||
printf("%04x: %02x\n", addr, val);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "speed")) {
|
||||
if (argc < 3) {
|
||||
printf("speed: missing argument\n");
|
||||
return;
|
||||
}
|
||||
float speed = atof(argv[2]);
|
||||
if (speed < 0.1 || speed > 30.0) {
|
||||
printf("speed: speed must be between 0.0 and 30.0\n");
|
||||
return;
|
||||
}
|
||||
mii->speed = speed;
|
||||
printf("speed: %.3fMHz\n", speed);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "stop")) {
|
||||
mii->state = MII_STOPPED;
|
||||
goto show_state;
|
||||
}
|
||||
printf("mii: unknown command %s\n", argv[1]);
|
||||
}
|
||||
|
||||
void
|
||||
_mii_mish_bp(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
mii_t * mii = &g_mii;
|
||||
if (!argv[1] || !strcmp(argv[1], "list")) {
|
||||
printf("breakpoints: map %04x\n", mii->debug.bp_map);
|
||||
for (int i = 0; i < (int)sizeof(mii->debug.bp_map)*8; i++) {
|
||||
printf("%2d %c %04x %c%c%c size:%2d\n", i,
|
||||
(mii->debug.bp_map & (1 << i)) ? '*' : ' ',
|
||||
mii->debug.bp[i].addr,
|
||||
(mii->debug.bp[i].kind & MII_BP_R) ? 'r' : '-',
|
||||
(mii->debug.bp[i].kind & MII_BP_W) ? 'w' : '-',
|
||||
(mii->debug.bp[i].kind & MII_BP_STICKY) ? 's' : '-',
|
||||
mii->debug.bp[i].size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const char *p = argv[1];
|
||||
if (p[0] == '+') {
|
||||
p++;
|
||||
int addr = strtol(p, NULL, 16);
|
||||
int kind = 0;
|
||||
if (strchr(p, 'r'))
|
||||
kind |= MII_BP_R;
|
||||
if (strchr(p, 'w'))
|
||||
kind |= MII_BP_W;
|
||||
if (strchr(p, 's'))
|
||||
kind |= MII_BP_STICKY;
|
||||
if (!kind || kind == MII_BP_STICKY)
|
||||
kind |= MII_BP_R;
|
||||
int size = 1;
|
||||
if (argv[2])
|
||||
size = strtol(argv[2], NULL, 16);
|
||||
if (!size) size++;
|
||||
for (int i = 0; i < (int)sizeof(mii->debug.bp_map)*8; i++) {
|
||||
if (!(mii->debug.bp_map & (1 << i)) || mii->debug.bp[i].addr == addr) {
|
||||
mii->debug.bp_map |= 1 << i;
|
||||
mii->debug.bp[i].addr = addr;
|
||||
mii->debug.bp[i].kind = kind;
|
||||
mii->debug.bp[i].size = size;
|
||||
printf("breakpoint %d set at %04x size %d\n", i, addr, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p[0] == '-') {
|
||||
p++;
|
||||
int idx = strtol(p, NULL, 10);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
mii->debug.bp_map &= ~(1 << idx);
|
||||
printf("breakpoint %d cleared\n", idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_mii_mish_il(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
static uint16_t addr = 0x800;
|
||||
if (argv[1]) {
|
||||
addr = strtol(argv[1], NULL, 16);
|
||||
if (addr >= 0xffff)
|
||||
addr = 0xfff0;
|
||||
}
|
||||
mii_t * mii = &g_mii;
|
||||
|
||||
for (int li = 0; li < 20; li++) {
|
||||
uint8_t op[16];
|
||||
for (int bi = 0; bi < 4; bi++)
|
||||
mii_mem_access(mii, addr + bi, op + bi, false, false);
|
||||
char dis[64];
|
||||
addr += mii_cpu_disasm_one(op, addr, dis, sizeof(dis),
|
||||
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
|
||||
printf("%s\n", dis);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_mish_dm(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
static uint16_t addr = 0x800;
|
||||
if (argv[1]) {
|
||||
addr = strtol(argv[1], NULL, 16);
|
||||
if (addr >= 0xffff)
|
||||
addr = 0xfff0;
|
||||
}
|
||||
mii_t * mii = &g_mii;
|
||||
if (!strcmp(argv[0], "dm")) {
|
||||
printf("dm: %04x\n", addr);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
printf("%04x: ", addr);
|
||||
for (int j = 0; j < 16; j++)
|
||||
printf("%02x ", mii_read_one(mii, addr++));
|
||||
printf("\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[0], "db")) {
|
||||
printf("%s: %04x: ", argv[0], addr);
|
||||
printf("%02x ", mii_read_one(mii, addr++));
|
||||
printf("\n");
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[0], "dw") || !strcmp(argv[0], "da")) {
|
||||
printf("%s: %04x: ", argv[0], addr);
|
||||
uint8_t l = mii_read_one(mii, addr++);
|
||||
uint8_t h = mii_read_one(mii, addr++);
|
||||
printf("%02x%02x", h, l);
|
||||
printf("\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_mii_mish_step(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
if (argv[0][0] == 's') {
|
||||
mii_t * mii = &g_mii;
|
||||
if (argv[1]) {
|
||||
int n = strtol(argv[1], NULL, 10);
|
||||
mii->trace.step_inst = n;
|
||||
} else
|
||||
mii->trace.step_inst = 1;
|
||||
mii->state = MII_STEP;
|
||||
return;
|
||||
}
|
||||
if (argv[0][0] == 'n') {
|
||||
mii_t * mii = &g_mii;
|
||||
// read current opcode, find how how many bytes it take,
|
||||
// then put a temporary breakpoint to the next PC.
|
||||
// all of that if this is not a relative branch of course, in
|
||||
// which case we use a normal 'step' behaviour
|
||||
uint8_t op;
|
||||
mii_mem_access(mii, mii->cpu.PC, &op, false, false);
|
||||
if (op == 0x20) {
|
||||
// set a temp breakpoint on reading 3 bytes from PC
|
||||
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
|
||||
if ((mii->debug.bp_map & (1 << i)))
|
||||
continue;
|
||||
mii->debug.bp[i].addr = mii->cpu.PC + 3;
|
||||
mii->debug.bp[i].kind = MII_BP_R;
|
||||
mii->debug.bp[i].size = 1;
|
||||
mii->debug.bp[i].silent = 1;
|
||||
mii->debug.bp_map |= 1 << i;
|
||||
mii->state = MII_RUNNING;
|
||||
return;
|
||||
}
|
||||
printf("no more breakpoints available\n");
|
||||
} else {
|
||||
mii->trace.step_inst = 1;
|
||||
mii->state = MII_STEP;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (argv[0][0] == 'c') {
|
||||
mii_t * mii = &g_mii;
|
||||
mii->trace.step_inst = 0;
|
||||
mii->state = MII_RUNNING;
|
||||
return;
|
||||
}
|
||||
if (argv[0][0] == 'h') {
|
||||
mii_t * mii = &g_mii;
|
||||
mii->trace.step_inst = 0;
|
||||
mii->state = MII_STOPPED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern int g_mii_audio_record_fd;
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static void
|
||||
_mii_mish_audio(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
printf("audio: missing argument\n");
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "record")) {
|
||||
if (g_mii_audio_record_fd != -1) {
|
||||
close(g_mii_audio_record_fd);
|
||||
g_mii_audio_record_fd = -1;
|
||||
printf("audio: stop recording\n");
|
||||
} else {
|
||||
g_mii_audio_record_fd = open("audio.raw",
|
||||
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
printf("audio: start recording\n");
|
||||
}
|
||||
} else if (!strcmp(argv[1], "mute")) {
|
||||
if (argv[2] && !strcmp(argv[2], "off"))
|
||||
g_mii.speaker.muted = false;
|
||||
else if (argv[2] && !strcmp(argv[2], "on"))
|
||||
g_mii.speaker.muted = true;
|
||||
else if (!argv[2] || (argv[2] && !strcmp(argv[2], "toggle")))
|
||||
g_mii.speaker.muted = !g_mii.speaker.muted;
|
||||
printf("audio: %s\n", g_mii.speaker.muted ? "muted" : "unmuted");
|
||||
} else if (!strcmp(argv[1], "volume")) {
|
||||
if (argc < 3) {
|
||||
printf("audio: missing volume\n");
|
||||
return;
|
||||
}
|
||||
// convert a linear volume from 0 to 10 into a float from 0.0 to 1.0
|
||||
float vol = atof(argv[2]);
|
||||
if (vol < 0) vol = 0;
|
||||
else if (vol > 10) vol = 10;
|
||||
mii_speaker_volume(&g_mii.speaker, vol);
|
||||
printf("audio: volume %.3f (amp: %.4f)\n",
|
||||
vol, g_mii.speaker.vol_multiplier);
|
||||
} else {
|
||||
printf("audio: unknown command %s\n", argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
#include "mish.h"
|
||||
|
||||
MISH_CMD_NAMES(mii, "mii");
|
||||
MISH_CMD_HELP(mii,
|
||||
"mii: access internals, trace, reset, speed, etc",
|
||||
" <default> : dump current state",
|
||||
" reset : reset the cpu",
|
||||
" t|trace : toggle trace_cpu (WARNING HUGE traces!))",
|
||||
" mem : dump memory and bank map",
|
||||
" poke <addr> <val> : poke a value in memory (respect SW)",
|
||||
" peek <addr> : peek a value in memory (respect SW)",
|
||||
" speed <speed> : set speed in MHz",
|
||||
" stop : stop the cpu"
|
||||
);
|
||||
MISH_CMD_REGISTER(mii, _mii_mish_cmd);
|
||||
|
||||
MISH_CMD_NAMES(bp, "bp");
|
||||
MISH_CMD_HELP(bp,
|
||||
"mii: breakpoints. 'sticky' means the breakpoint is re-armed after hit",
|
||||
" <default> : dump state",
|
||||
" +<addr>[r|w][s] [size]: add at <addr> for read/write, sticky",
|
||||
" -<index> : disable (don't clear) breakpoint <index>"
|
||||
);
|
||||
MISH_CMD_REGISTER(bp, _mii_mish_bp);
|
||||
|
||||
MISH_CMD_NAMES(il, "il");
|
||||
MISH_CMD_HELP(il,
|
||||
"mii: disassembly",
|
||||
" <default> : list next 20 instructions.",
|
||||
" [addr]: start at address addr"
|
||||
);
|
||||
MISH_CMD_REGISTER(il, _mii_mish_il);
|
||||
|
||||
MISH_CMD_NAMES(dm, "dm","db","dw","da");
|
||||
MISH_CMD_HELP(dm,
|
||||
"mii: dump memory, byte, word, address",
|
||||
" <default>|dm <addr>: dump 64 bytes.",
|
||||
" db [<addr>]: dump one byte.",
|
||||
" dw [<addr>]: dump one word.",
|
||||
" da [<addr>]: dump one address.",
|
||||
" [addr]: start at address addr"
|
||||
);
|
||||
MISH_CMD_REGISTER(dm, _mii_mish_dm);
|
||||
|
||||
MISH_CMD_NAMES(step, "s","step","n","next","cont","h","halt");
|
||||
MISH_CMD_HELP(step,
|
||||
"mii: step instructions",
|
||||
" s|step [num]: step [num, or one] instruction.",
|
||||
" n|next : step one instruction, skip subroutines.",
|
||||
" cont : continue execution."
|
||||
);
|
||||
MISH_CMD_REGISTER(step, _mii_mish_step);
|
||||
|
||||
MISH_CMD_NAMES(text, "text");
|
||||
MISH_CMD_HELP(text,
|
||||
"mii: show text page [buggy]",
|
||||
" <default> : that's it"
|
||||
);
|
||||
MISH_CMD_REGISTER(text, _mii_mish_text);
|
||||
|
||||
MISH_CMD_NAMES(audio, "audio");
|
||||
MISH_CMD_HELP(audio,
|
||||
"audio: audio control/debug",
|
||||
" record: record/stop debug file.",
|
||||
" mute: mute/unmute audio.",
|
||||
" volume: set volume (0.0 to 1.0)."
|
||||
);
|
||||
MISH_CMD_REGISTER(audio, _mii_mish_audio);
|
46
nuklear/mii_mish_dd.c
Normal file
46
nuklear/mii_mish_dd.c
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* mii_mish_dd.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
|
||||
extern mii_t g_mii;
|
||||
|
||||
|
||||
static void
|
||||
_mii_mish_dd(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
if (!argv[1] || !strcmp(argv[1], "list")) {
|
||||
mii_dd_t *d = g_mii.dd.drive;
|
||||
printf(" ID %-16s %-20s\n", "Card", "Name");
|
||||
while (d) {
|
||||
printf("%d:%d %-16s %-20s : %s\n",
|
||||
d->slot_id, d->drive,
|
||||
d->slot->drv->desc,
|
||||
d->name, d->file ? d->file->pathname : "*empty*");
|
||||
d = d->next;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#include "mish.h"
|
||||
|
||||
MISH_CMD_NAMES(dd, "dd", "disk");
|
||||
MISH_CMD_HELP(dd,
|
||||
"mii: disk commands",
|
||||
" <default>|list: list all disk drives"
|
||||
);
|
||||
MISH_CMD_REGISTER(dd, _mii_mish_dd);
|
626
nuklear/mii_nuklear.c
Normal file
626
nuklear/mii_nuklear.c
Normal file
@ -0,0 +1,626 @@
|
||||
/*
|
||||
* mii_nuklear.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_sw.h"
|
||||
|
||||
#define NK_INCLUDE_FIXED_TYPES
|
||||
#define NK_INCLUDE_STANDARD_IO
|
||||
#define NK_INCLUDE_STANDARD_VARARGS
|
||||
#define NK_INCLUDE_DEFAULT_ALLOCATOR
|
||||
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
|
||||
#define NK_INCLUDE_FONT_BAKING
|
||||
#define NK_INCLUDE_DEFAULT_FONT
|
||||
#define NK_BUTTON_TRIGGER_ON_RELEASE
|
||||
#include "nuklear.h"
|
||||
#include "nuklear_xlib_gl3.h"
|
||||
//#include "stb_image_write.h"
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#define _NK_RGBA(_r,_g,_b,_a) {.r=_r,.g=_g,.b=_b,.a=_a}
|
||||
static const struct nk_color style[NK_COLOR_COUNT] = {
|
||||
[NK_COLOR_TEXT] = _NK_RGBA(0, 0, 0, 255),
|
||||
[NK_COLOR_WINDOW] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_HEADER] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_BORDER] = _NK_RGBA(0, 0, 0, 255),
|
||||
[NK_COLOR_BUTTON] = _NK_RGBA(185, 185, 185, 255),
|
||||
[NK_COLOR_BUTTON_HOVER] = _NK_RGBA(170, 170, 170, 255),
|
||||
[NK_COLOR_BUTTON_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
|
||||
[NK_COLOR_TOGGLE] = _NK_RGBA(150, 150, 150, 255),
|
||||
[NK_COLOR_TOGGLE_HOVER] = _NK_RGBA(120, 120, 120, 255),
|
||||
[NK_COLOR_TOGGLE_CURSOR] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_SELECT] = _NK_RGBA(190, 190, 190, 255),
|
||||
[NK_COLOR_SELECT_ACTIVE] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_SLIDER] = _NK_RGBA(190, 190, 190, 255),
|
||||
[NK_COLOR_SLIDER_CURSOR] = _NK_RGBA(80, 80, 80, 255),
|
||||
[NK_COLOR_SLIDER_CURSOR_HOVER] = _NK_RGBA(70, 70, 70, 255),
|
||||
[NK_COLOR_SLIDER_CURSOR_ACTIVE] = _NK_RGBA(60, 60, 60, 255),
|
||||
[NK_COLOR_PROPERTY] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_EDIT] = _NK_RGBA(150, 150, 150, 255),
|
||||
[NK_COLOR_EDIT_CURSOR] = _NK_RGBA(0, 0, 0, 255),
|
||||
[NK_COLOR_COMBO] = _NK_RGBA(175, 175, 175, 255),
|
||||
[NK_COLOR_CHART] = _NK_RGBA(160, 160, 160, 255),
|
||||
[NK_COLOR_CHART_COLOR] = _NK_RGBA(45, 45, 45, 255),
|
||||
[NK_COLOR_CHART_COLOR_HIGHLIGHT] = _NK_RGBA( 255, 0, 0, 255),
|
||||
[NK_COLOR_SCROLLBAR] = _NK_RGBA(180, 180, 180, 255),
|
||||
[NK_COLOR_SCROLLBAR_CURSOR] = _NK_RGBA(140, 140, 140, 255),
|
||||
[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = _NK_RGBA(150, 150, 150, 255),
|
||||
[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = _NK_RGBA(160, 160, 160, 255),
|
||||
[NK_COLOR_TAB_HEADER] = _NK_RGBA(180, 180, 180, 255),
|
||||
};
|
||||
|
||||
static GLuint screen_texture;
|
||||
static struct nk_image screen_nk;
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
typedef uint64_t mii_time_t;
|
||||
enum {
|
||||
MII_TIME_RES = 1,
|
||||
MII_TIME_SECOND = 1000000,
|
||||
MII_TIME_MS = (MII_TIME_SECOND/1000),
|
||||
};
|
||||
mii_time_t
|
||||
mii_get_time()
|
||||
{
|
||||
struct timespec tim;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &tim);
|
||||
uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / MII_TIME_RES) +
|
||||
tim.tv_nsec / (1000 * MII_TIME_RES);
|
||||
return time;
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
#include "fifo_declare.h"
|
||||
|
||||
static pthread_t mii_thread;
|
||||
static bool mii_thread_running = false;
|
||||
//static mii_trace_t mii_trace = {};
|
||||
float default_fps = 60;
|
||||
|
||||
enum {
|
||||
SIGNAL_RESET,
|
||||
SIGNAL_STOP,
|
||||
SIGNAL_STEP,
|
||||
SIGNAL_RUN,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t cmd;
|
||||
uint8_t data;
|
||||
} signal_t;
|
||||
|
||||
DECLARE_FIFO(signal_t, signal_fifo, 16);
|
||||
DEFINE_FIFO(signal_t, signal_fifo);
|
||||
|
||||
signal_fifo_t signal_fifo;
|
||||
|
||||
static void *mii_thread_func(void *arg)
|
||||
{
|
||||
mii_t *mii = (mii_t *) arg;
|
||||
mii_thread_running = true;
|
||||
__uint128_t last_cycles = mii->cycles;
|
||||
uint32_t running = 1;
|
||||
unsigned long target_fps_us = 1000000 / default_fps;
|
||||
long sleep_time = target_fps_us;
|
||||
|
||||
//mii_time_t base = mii_get_time(NULL);
|
||||
uint32_t last_frame = mii->video.frame_count;
|
||||
mii_time_t last_frame_stamp = mii_get_time();
|
||||
while (mii_thread_running) {
|
||||
signal_t sig;
|
||||
while (!signal_fifo_isempty(&signal_fifo)) {
|
||||
sig = signal_fifo_read(&signal_fifo);
|
||||
switch (sig.cmd) {
|
||||
case SIGNAL_RESET:
|
||||
mii_reset(mii, sig.data);
|
||||
break;
|
||||
case SIGNAL_STOP:
|
||||
mii_dump_run_trace(mii);
|
||||
mii_dump_trace_state(mii);
|
||||
mii->state = MII_STOPPED;
|
||||
break;
|
||||
case SIGNAL_STEP:
|
||||
mii->state = MII_STEP;
|
||||
running = 1;
|
||||
break;
|
||||
case SIGNAL_RUN:
|
||||
mii->state = MII_RUNNING;
|
||||
last_frame_stamp = mii_get_time();
|
||||
running = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mii->state != MII_STOPPED)
|
||||
mii_run(mii);
|
||||
|
||||
switch (mii->state) {
|
||||
case MII_STOPPED:
|
||||
usleep(1000);
|
||||
break;
|
||||
case MII_STEP:
|
||||
if (running) {
|
||||
running--;
|
||||
mii_dump_trace_state(mii);
|
||||
usleep(1000);
|
||||
running = 1;
|
||||
if (mii->trace.step_inst)
|
||||
mii->trace.step_inst--;
|
||||
if (mii->trace.step_inst == 0)
|
||||
mii->state = MII_STOPPED;
|
||||
}
|
||||
break;
|
||||
case MII_RUNNING:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mii->video.frame_count != last_frame) {
|
||||
sleep_time = target_fps_us;
|
||||
mii_time_t now = mii_get_time();
|
||||
if (mii->state == MII_RUNNING) {
|
||||
mii_time_t delta = now - last_frame_stamp;
|
||||
// printf("frame time %d/%d sleep time %d\n",
|
||||
// (int)delta, (int)target_fps_us,
|
||||
// (int)target_fps_us - delta);
|
||||
sleep_time = target_fps_us - delta;
|
||||
if (sleep_time < 0)
|
||||
sleep_time = 0;
|
||||
last_frame = mii->video.frame_count;
|
||||
while (last_frame_stamp <= now)
|
||||
last_frame_stamp += target_fps_us;
|
||||
|
||||
// calculate the MHz
|
||||
__uint128_t cycles = mii->cycles;
|
||||
__uint128_t delta_cycles = cycles - last_cycles;
|
||||
last_cycles = cycles;
|
||||
mii->speed_current = delta_cycles / (float)target_fps_us;
|
||||
}
|
||||
usleep(sleep_time);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern struct nk_font *nk_main_font;
|
||||
struct nk_font *nk_mono_font = NULL;
|
||||
|
||||
extern const unsigned char mii_proggy_data[];
|
||||
extern const unsigned int mii_proggy_size;
|
||||
extern const unsigned char mii_droid_data[];
|
||||
extern const unsigned int mii_droid_size;
|
||||
|
||||
void
|
||||
mii_nuklear_init(
|
||||
mii_t *mii,
|
||||
struct nk_context *ctx)
|
||||
{
|
||||
nk_style_from_table(ctx, style);
|
||||
|
||||
{
|
||||
struct nk_font_atlas *atlas;
|
||||
nk_x11_font_stash_begin(&atlas);
|
||||
struct nk_font_config cfg = nk_font_config(0);
|
||||
#if 0
|
||||
nk_rune ranges[] = {
|
||||
0x0020, 0x007E, /* Ascii */
|
||||
0x00A1, 0x00FF, /* Symbols + Umlaute */
|
||||
0
|
||||
};
|
||||
#endif
|
||||
cfg.range = nk_font_default_glyph_ranges();
|
||||
cfg.oversample_h = cfg.oversample_v = 1;
|
||||
cfg.pixel_snap = true;
|
||||
struct nk_font *nkf = nk_font_atlas_add_from_memory(atlas,
|
||||
(void*)mii_proggy_data, mii_proggy_size, 20, &cfg);
|
||||
nk_x11_font_stash_end();
|
||||
nk_mono_font = nkf;
|
||||
}
|
||||
|
||||
glGenTextures(1, &screen_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, screen_texture);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, 4,
|
||||
MII_VRAM_WIDTH,
|
||||
MII_VRAM_HEIGHT, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
mii->video.pixels);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
// printf("%s texture created %d\n", __func__, screen_texture);
|
||||
// display opengl error
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
printf("Error creating texture: %d\n", err);
|
||||
}
|
||||
screen_nk = nk_subimage_id(
|
||||
screen_texture, MII_VRAM_WIDTH, MII_VRAM_HEIGHT,
|
||||
nk_rect(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT));
|
||||
|
||||
/* start the thread */
|
||||
pthread_create(&mii_thread, NULL, mii_thread_func, mii);
|
||||
|
||||
}
|
||||
|
||||
|
||||
extern int disk2_debug;
|
||||
int show_zero_page = 0;
|
||||
|
||||
static void
|
||||
mii_nuklear_handle_input(
|
||||
mii_t *mii,
|
||||
struct nk_context *ctx)
|
||||
{
|
||||
struct nk_input *in = &ctx->input;
|
||||
if (in->keyboard.text_len) {
|
||||
// printf("INPUT %d %s\n", in->keyboard.text_len, in->keyboard.text);
|
||||
if (in->keyboard.text_len < 4) {
|
||||
mii_keypress(mii, in->keyboard.text[0]);
|
||||
} else if (in->keyboard.text_len > 1) {
|
||||
uint32_t *raw = ((uint32_t *) in->keyboard.text);
|
||||
for (int ki = 0; ki < in->keyboard.text_len / 4; ki ++) {
|
||||
uint32_t key = (raw[ki] >> 16) & 0xffff;
|
||||
uint8_t down = raw[ki] & 0xff;
|
||||
printf("KEY %04x %s\n", key, down ? "down" : "up");
|
||||
if (down) {
|
||||
if (key == 0xffc9) { // F12
|
||||
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
|
||||
signal_t sig = {
|
||||
.cmd = SIGNAL_RESET,
|
||||
.data = nk_input_is_key_down(in, NK_KEY_SHIFT)
|
||||
};
|
||||
signal_fifo_write(&signal_fifo, sig);
|
||||
printf("RESET\n");
|
||||
}
|
||||
} else if (key == 0xffc8) { // F11
|
||||
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
|
||||
signal_t sig = {
|
||||
.cmd = SIGNAL_STOP,
|
||||
};
|
||||
signal_fifo_write(&signal_fifo, sig);
|
||||
printf("STOP\n");
|
||||
}
|
||||
} else if (key == 0xffc7) { // F10
|
||||
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
|
||||
signal_t sig = {
|
||||
.cmd = SIGNAL_STEP,
|
||||
};
|
||||
signal_fifo_write(&signal_fifo, sig);
|
||||
printf("STEP\n");
|
||||
}
|
||||
} else if (key == 0xffc6) { // F9
|
||||
if (nk_input_is_key_down(in, NK_KEY_CTRL)) {
|
||||
signal_t sig = {
|
||||
.cmd = SIGNAL_RUN,
|
||||
};
|
||||
signal_fifo_write(&signal_fifo, sig);
|
||||
printf("RUN\n");
|
||||
}
|
||||
} else if (key == 0xffc2) { // F5
|
||||
mii->speed = 1.0;
|
||||
printf("Speed Normal (1MHz)\n");
|
||||
} else if (key == 0xffc3) { // F6
|
||||
mii->speed = 4;
|
||||
printf("Speed Fast (4MHz)\n");
|
||||
}
|
||||
}
|
||||
if (key == 0xffeb || key == 0xffec) { // super left/right
|
||||
key -= 0xffeb;
|
||||
mii_bank_t *bank = &mii->bank[MII_BANK_MAIN];
|
||||
uint8_t old = mii_bank_peek(bank, 0xc061 + key);
|
||||
mii_bank_poke(bank, 0xc061 + key, down ? 0x80 : 0);
|
||||
if (!!down != !!old) {
|
||||
printf("Apple %s %s\n", key ? "Open" : "Close",
|
||||
down ? "down" : "up");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
in->keyboard.text_len = 0;
|
||||
} else {
|
||||
signal_t sig = {.cmd = -1 };
|
||||
if (nk_input_is_key_pressed(in, NK_KEY_ENTER))
|
||||
sig.data = 0x0d;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_BACKSPACE))
|
||||
sig.data = 0x08;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_DEL))
|
||||
sig.data = 0x7f;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_UP))
|
||||
sig.data = 'K' - 'A' + 1;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_DOWN))
|
||||
sig.data = 'J' - 'A' + 1;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_LEFT))
|
||||
sig.data = 'H' - 'A' + 1;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_RIGHT))
|
||||
sig.data = 'U' - 'A' + 1;
|
||||
else if (nk_input_is_key_pressed(in, NK_KEY_ESCAPE))
|
||||
sig.data = 0x1b;
|
||||
if (sig.data) {
|
||||
// signal_fifo_write(&signal_fifo, sig);
|
||||
// printf("Key %d\n", sig.data);
|
||||
mii_keypress(mii, sig.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mii_nuklear(
|
||||
mii_t *mii,
|
||||
struct nk_context *ctx)
|
||||
{
|
||||
if (mii->video.frame_count != mii->video.frame_drawn) {
|
||||
mii->video.frame_drawn = mii->video.frame_count;
|
||||
// update texture with new pixels; we only need 192 lines, the others
|
||||
// are padding for the power of 2 texture
|
||||
glBindTexture(GL_TEXTURE_2D, screen_texture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
|
||||
MII_VRAM_WIDTH,
|
||||
MII_VIDEO_HEIGHT, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
mii->video.pixels);
|
||||
}
|
||||
mii_nuklear_handle_input(mii, ctx);
|
||||
int height = 720 - 8;
|
||||
int width = MII_VIDEO_WIDTH * (height / (float)MII_VIDEO_HEIGHT);
|
||||
int xpos = 1280 / 2 - width / 2;
|
||||
{
|
||||
struct nk_style *s = &ctx->style;
|
||||
nk_style_push_color(ctx, &s->window.background,
|
||||
nk_rgba(0,0,0, 255));
|
||||
nk_style_push_style_item(ctx, &s->window.fixed_background,
|
||||
nk_style_item_color(nk_rgba(0,0,0, 255)));
|
||||
if (nk_begin(ctx, "Apple //e Enhanced",
|
||||
nk_rect(xpos, 0, width + 10, height + 20),
|
||||
NK_WINDOW_NO_SCROLLBAR)) {
|
||||
nk_layout_row_static(ctx, height, width, 1);
|
||||
static int was_in = -1;
|
||||
if (nk_widget_is_hovered(ctx) && mii->mouse.enabled) {
|
||||
if (was_in != 1) {
|
||||
was_in = 1;
|
||||
ctx->input.mouse.grab = 1;
|
||||
// printf("IN\n");
|
||||
}
|
||||
struct nk_rect bounds = nk_widget_bounds(ctx);
|
||||
// normalize mouse coordinates
|
||||
double x = ctx->input.mouse.pos.x - bounds.x;
|
||||
double y = ctx->input.mouse.pos.y - bounds.y;
|
||||
// get mouse button state
|
||||
int button = ctx->input.mouse.buttons[NK_BUTTON_LEFT].down;
|
||||
// clamp coordinates inside bounds
|
||||
double vw = bounds.w;
|
||||
double vh = bounds.h;
|
||||
double mw = mii->mouse.max_x - mii->mouse.min_x;
|
||||
double mh = mii->mouse.max_y - mii->mouse.min_y;
|
||||
mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5;
|
||||
mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5;
|
||||
mii->mouse.button = button;
|
||||
// printf("Mouse is %d %d\n", (int)mii->mouse.x, (int)mii->mouse.y);
|
||||
} else {
|
||||
if (was_in == 1) {
|
||||
was_in = 0;
|
||||
ctx->input.mouse.ungrab = 1;
|
||||
// printf("OUT\n");
|
||||
}
|
||||
}
|
||||
nk_image(ctx, screen_nk);
|
||||
nk_end(ctx);
|
||||
nk_style_pop_color(ctx);
|
||||
nk_style_pop_style_item(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
struct nk_rect bounds = { .x = 0, .y = 0, .w = xpos, .h = height };
|
||||
// nk_window_get_bounds(ctx);
|
||||
bool in = nk_input_is_mouse_hovering_rect(&ctx->input, bounds);
|
||||
static bool menu_open = false;
|
||||
|
||||
if (in || menu_open) {
|
||||
struct nk_style *s = &ctx->style;
|
||||
nk_style_push_color(ctx, &s->window.background,
|
||||
nk_rgba(175, 175, 175, 255));
|
||||
nk_style_push_style_item(ctx, &s->window.fixed_background,
|
||||
nk_style_item_color(nk_rgba(175, 175, 175, 255)));
|
||||
|
||||
if (nk_begin(ctx, "Left Bar",
|
||||
nk_rect(0, 0, xpos, height + 20),
|
||||
NK_WINDOW_NO_SCROLLBAR)) {
|
||||
#if 0
|
||||
nk_menubar_begin(ctx);
|
||||
nk_layout_row_begin(ctx, NK_STATIC, 25, 2);
|
||||
nk_layout_row_push(ctx, 60);
|
||||
bool menu = false;
|
||||
if (nk_menu_begin_label(ctx, "MII", NK_TEXT_LEFT,
|
||||
nk_vec2(140, 200))) {
|
||||
static size_t prog = 40;
|
||||
static float slider = 0.5;
|
||||
static int check = nk_true;
|
||||
nk_layout_row_dynamic(ctx, 25, 1);
|
||||
if (nk_menu_item_label(ctx, "Hide", NK_TEXT_LEFT))
|
||||
;//show_menu = nk_false;
|
||||
if (nk_menu_item_label(ctx, "About", NK_TEXT_LEFT))
|
||||
;//show_app_about = nk_true;
|
||||
nk_progress(ctx, &prog, 100, NK_MODIFIABLE);
|
||||
nk_slider_float(ctx, 0.01, &slider, 1.0, 0.05);
|
||||
nk_checkbox_label(ctx, "Mute", &check);
|
||||
nk_menu_end(ctx);
|
||||
menu = true;
|
||||
}
|
||||
menu_open = menu;
|
||||
nk_menubar_end(ctx);
|
||||
nk_layout_space_end(ctx);
|
||||
#endif
|
||||
int rw = xpos - 8;
|
||||
// nk_layout_row_dynamic(ctx, 4, 1);
|
||||
// nk_spacing(ctx, 1);
|
||||
|
||||
nk_layout_row_static(ctx, 30, rw, 1);
|
||||
if (nk_button_label(ctx, "C-RESET"))
|
||||
mii_reset(mii, false);
|
||||
if (nk_button_label(ctx, "C-OA-RESET"))
|
||||
mii_reset(mii, true);
|
||||
// nk_layout_row_dynamic(ctx, 0, 1);
|
||||
nk_layout_row_begin(ctx, NK_STATIC, 30, 2);
|
||||
nk_layout_row_push(ctx, 20);
|
||||
nk_label(ctx, "V:", NK_TEXT_LEFT);
|
||||
nk_layout_row_push(ctx, rw - 20 - 4);
|
||||
const char *video_modes[] = {
|
||||
"Color",
|
||||
"Green",
|
||||
"Amber",
|
||||
};
|
||||
mii->video.color_mode = nk_combo(ctx,
|
||||
video_modes, NK_LEN(video_modes),
|
||||
mii->video.color_mode, 25, nk_vec2(80,200));
|
||||
nk_layout_space_end(ctx);
|
||||
|
||||
nk_layout_row_begin(ctx, NK_STATIC, 30, 1);
|
||||
nk_layout_row_push(ctx, rw);
|
||||
const char *speed[] = {
|
||||
"1 MHz",
|
||||
"Slow",
|
||||
"Fast",
|
||||
};
|
||||
nk_label(ctx, "Speed:", NK_TEXT_LEFT);
|
||||
nk_layout_row_push(ctx, 100);
|
||||
int spi = 0;
|
||||
spi = nk_combo(ctx,
|
||||
speed, NK_LEN(speed),
|
||||
spi, 25, nk_vec2(80,200));
|
||||
nk_layout_space_end(ctx);
|
||||
|
||||
}
|
||||
nk_end(ctx);
|
||||
nk_style_pop_color(ctx);
|
||||
nk_style_pop_style_item(ctx);
|
||||
}
|
||||
if ( 0 && nk_begin(ctx, "Controls",
|
||||
nk_rect(width, 0, 350, 10 + 192 * 3),
|
||||
NK_WINDOW_NO_SCROLLBAR)) {
|
||||
nk_layout_row_static(ctx, 30, 110, 2);
|
||||
if (nk_button_label(ctx, "C-RESET"))
|
||||
mii_reset(mii, false);
|
||||
if (nk_button_label(ctx, "C-OA-RESET"))
|
||||
mii_reset(mii, true);
|
||||
#if 0
|
||||
if (nk_button_label(ctx, "Screenshot")) {
|
||||
stbi_write_png("screen.png",
|
||||
MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT, 4, mii->video.pixels,
|
||||
MII_VRAM_WIDTH * 4);
|
||||
printf("Screenshot taken\n");
|
||||
}
|
||||
#endif
|
||||
nk_layout_row_dynamic(ctx, 30, 4);
|
||||
nk_label(ctx, "Speed:", NK_TEXT_CENTERED);
|
||||
if (nk_option_label(ctx, "Slow", mii->speed < 0.9)) mii->speed = 0.2;
|
||||
if (nk_option_label(ctx, "1 MHz", mii->speed > .95 && mii->speed < 1.05)) mii->speed = 1.0;
|
||||
if (nk_option_label(ctx, "Fast", mii->speed > 1.1 && mii->speed < 4.1)) mii->speed = 4.0;
|
||||
|
||||
nk_layout_row_dynamic(ctx, 30, 4);
|
||||
nk_label(ctx, "Video:", NK_TEXT_CENTERED);
|
||||
if (nk_option_label(ctx, "Color", mii->video.color_mode == MII_VIDEO_COLOR))
|
||||
mii->video.color_mode = MII_VIDEO_COLOR;
|
||||
if (nk_option_label(ctx, "Green", mii->video.color_mode == MII_VIDEO_GREEN))
|
||||
mii->video.color_mode = MII_VIDEO_GREEN;
|
||||
if (nk_option_label(ctx, "Amber", mii->video.color_mode == MII_VIDEO_AMBER))
|
||||
mii->video.color_mode = MII_VIDEO_AMBER;
|
||||
|
||||
#if 0
|
||||
nk_layout_row_dynamic(ctx, 20, 1);
|
||||
nk_style_set_font(ctx, &nk_mono_font->handle);
|
||||
struct nk_color save = ctx->style.window.background;
|
||||
|
||||
ctx->style.window.background = (struct nk_color)_NK_RGBA(0, 0, 0, 255);
|
||||
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
|
||||
char label[64];
|
||||
mii_dasm_t _dasm = {};
|
||||
mii_dasm_t *dasm = &_dasm;
|
||||
mii_dasm_init(dasm, mii, 0);
|
||||
dasm->mii = mii;
|
||||
// display the last few cycles up to the PC
|
||||
for (int di = 0; di < 3; di++) {
|
||||
int pci = (mii_trace.idx + MII_PC_LOG_SIZE - 3 + di) % MII_PC_LOG_SIZE;
|
||||
dasm->pc = mii_trace.log[pci];
|
||||
mii_dasm_step(dasm, label, sizeof(label));
|
||||
if (di == 2)
|
||||
label[0] = '*';
|
||||
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
|
||||
}
|
||||
// and the (potentially) next instruction here
|
||||
mii_dasm_step(dasm, label, sizeof(label));
|
||||
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
|
||||
|
||||
sprintf(label, "A:%02X X:%02X Y:%02X S:%02X",
|
||||
mii->cpu.A, mii->cpu.X, mii->cpu.Y, mii->cpu.S);
|
||||
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
|
||||
char n[] = {'C','Z','I','D','B','V','N'};
|
||||
label[0] = 0;
|
||||
sprintf(label, "%04x ", mii->cpu.PC);
|
||||
for (int i = 0; i < 7; i++)
|
||||
sprintf(label + strlen(label), "%c%d ", n[i],
|
||||
mii->cpu.P.P[i]);
|
||||
nk_label_colored(ctx, label, NK_TEXT_CENTERED, fore);
|
||||
|
||||
ctx->style.window.background = save;
|
||||
nk_style_set_font(ctx, &nk_main_font->handle);
|
||||
#endif
|
||||
nk_layout_row_static(ctx, 30, 30, 3);
|
||||
|
||||
if (nk_button_symbol(ctx, NK_SYMBOL_RECT_SOLID)) {
|
||||
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STOP});
|
||||
}
|
||||
if (nk_button_symbol(ctx, NK_SYMBOL_PLUS)) {
|
||||
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_STEP});
|
||||
}
|
||||
if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT)) {
|
||||
signal_fifo_write(&signal_fifo, (signal_t){.cmd = SIGNAL_RUN});
|
||||
}
|
||||
nk_layout_row_dynamic(ctx, 20, 3);
|
||||
{
|
||||
char label[32];
|
||||
sprintf(label, "CPU: %.1fMHz", mii->speed_current);
|
||||
nk_label(ctx, label, NK_TEXT_CENTERED);
|
||||
}
|
||||
#if 0
|
||||
nk_layout_row_dynamic(ctx, 20, 2);
|
||||
nk_checkbox_label(ctx, "Disk II Debug", &disk2_debug);
|
||||
nk_checkbox_label(ctx, "Slowmo", &mii_SLOW);
|
||||
// nk_checkbox_label(ctx, "Zero Page", &show_zero_page);
|
||||
#endif
|
||||
nk_end(ctx);
|
||||
}
|
||||
if (show_zero_page) {
|
||||
if (nk_begin(ctx, "Zero Page",
|
||||
nk_rect(0, 10 + 192 * 3, 600, 10 + 16 * 20),
|
||||
0 )) {
|
||||
nk_layout_row_dynamic(ctx, 20, 1);
|
||||
nk_style_set_font(ctx, &nk_mono_font->handle);
|
||||
struct nk_color fore = (struct nk_color)_NK_RGBA(0, 255, 0, 255);
|
||||
uint8_t *zp = mii->bank[0].mem;
|
||||
char label[128];
|
||||
for (int ri = 0; ri < (256 / 16); ri++) {
|
||||
sprintf(label, "%02x: ", ri * 16);
|
||||
for (int ci = 0; ci < 16; ci++) {
|
||||
sprintf(label + strlen(label), "%02X ",
|
||||
zp[ri * 16 + ci]);
|
||||
}
|
||||
nk_label_colored(ctx, label, NK_TEXT_LEFT, fore);
|
||||
}
|
||||
nk_style_set_font(ctx, &nk_main_font->handle);
|
||||
nk_end(ctx);
|
||||
}
|
||||
}
|
||||
}
|
22
nuklear/mii_stb_implement.c
Normal file
22
nuklear/mii_stb_implement.c
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* mii_stb.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
|
||||
#define NK_INCLUDE_FIXED_TYPES
|
||||
#define NK_INCLUDE_STANDARD_IO
|
||||
#define NK_INCLUDE_STANDARD_VARARGS
|
||||
#define NK_INCLUDE_DEFAULT_ALLOCATOR
|
||||
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
|
||||
#define NK_INCLUDE_FONT_BAKING
|
||||
#define NK_INCLUDE_DEFAULT_FONT
|
||||
#define NK_BUTTON_TRIGGER_ON_RELEASE
|
||||
#define NK_IMPLEMENTATION
|
||||
#include "nuklear.h"
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
30034
nuklear/nuklear.h
Normal file
30034
nuklear/nuklear.h
Normal file
File diff suppressed because it is too large
Load Diff
756
nuklear/nuklear_xlib_gl3.h
Normal file
756
nuklear/nuklear_xlib_gl3.h
Normal file
@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Nuklear - v1.17 - public domain
|
||||
* no warrenty implied; use at your own risk.
|
||||
* authored from 2015-2016 by Micha Mettke
|
||||
*/
|
||||
/*
|
||||
* ==============================================================
|
||||
*
|
||||
* API
|
||||
*
|
||||
* ===============================================================
|
||||
*/
|
||||
#ifndef NK_XLIB_GL3_H_
|
||||
#define NK_XLIB_GL3_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
NK_API struct nk_context* nk_x11_init(Display *dpy, Window win);
|
||||
NK_API void nk_x11_font_stash_begin(struct nk_font_atlas **atlas);
|
||||
NK_API void nk_x11_font_stash_end(void);
|
||||
NK_API int nk_x11_handle_event(XEvent *evt);
|
||||
NK_API void nk_x11_render(enum nk_anti_aliasing, int max_vertex_buffer, int max_element_buffer);
|
||||
NK_API void nk_x11_shutdown(void);
|
||||
NK_API int nk_x11_device_create(void);
|
||||
NK_API void nk_x11_device_destroy(void);
|
||||
|
||||
#endif
|
||||
/*
|
||||
* ==============================================================
|
||||
*
|
||||
* IMPLEMENTATION
|
||||
*
|
||||
* ===============================================================
|
||||
*/
|
||||
#ifdef NK_XLIB_GL3_IMPLEMENTATION
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <X11/Xlocale.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#ifndef NK_X11_DOUBLE_CLICK_LO
|
||||
#define NK_X11_DOUBLE_CLICK_LO 20
|
||||
#endif
|
||||
#ifndef NK_X11_DOUBLE_CLICK_HI
|
||||
#define NK_X11_DOUBLE_CLICK_HI 200
|
||||
#endif
|
||||
|
||||
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
|
||||
#include <GL/glxext.h>
|
||||
|
||||
/* GL_ARB_vertex_buffer_object */
|
||||
typedef void(*nkglGenBuffers)(GLsizei, GLuint*);
|
||||
typedef void(*nkglBindBuffer)(GLenum, GLuint);
|
||||
typedef void(*nkglBufferData)(GLenum, GLsizeiptr, const GLvoid*, GLenum);
|
||||
typedef void(*nkglBufferSubData)(GLenum, GLintptr, GLsizeiptr, const GLvoid*);
|
||||
typedef void*(*nkglMapBuffer)(GLenum, GLenum);
|
||||
typedef GLboolean(*nkglUnmapBuffer)(GLenum);
|
||||
typedef void(*nkglDeleteBuffers)(GLsizei, GLuint*);
|
||||
/* GL_ARB_vertex_array_object */
|
||||
typedef void (*nkglGenVertexArrays)(GLsizei, GLuint*);
|
||||
typedef void (*nkglBindVertexArray)(GLuint);
|
||||
typedef void (*nkglDeleteVertexArrays)(GLsizei, const GLuint*);
|
||||
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
|
||||
typedef void(*nkglVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid*);
|
||||
typedef void(*nkglEnableVertexAttribArray)(GLuint);
|
||||
typedef void(*nkglDisableVertexAttribArray)(GLuint);
|
||||
/* GL_ARB_framebuffer_object */
|
||||
typedef void(*nkglGenerateMipmap)(GLenum target);
|
||||
/* GLSL/OpenGL 2.0 core */
|
||||
typedef GLuint(*nkglCreateShader)(GLenum);
|
||||
typedef void(*nkglShaderSource)(GLuint, GLsizei, const GLchar**, const GLint*);
|
||||
typedef void(*nkglCompileShader)(GLuint);
|
||||
typedef void(*nkglGetShaderiv)(GLuint, GLenum, GLint*);
|
||||
typedef void(*nkglGetShaderInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
|
||||
typedef void(*nkglDeleteShader)(GLuint);
|
||||
typedef GLuint(*nkglCreateProgram)(void);
|
||||
typedef void(*nkglAttachShader)(GLuint, GLuint);
|
||||
typedef void(*nkglDetachShader)(GLuint, GLuint);
|
||||
typedef void(*nkglLinkProgram)(GLuint);
|
||||
typedef void(*nkglUseProgram)(GLuint);
|
||||
typedef void(*nkglGetProgramiv)(GLuint, GLenum, GLint*);
|
||||
typedef void(*nkglGetProgramInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*);
|
||||
typedef void(*nkglDeleteProgram)(GLuint);
|
||||
typedef GLint(*nkglGetUniformLocation)(GLuint, const GLchar*);
|
||||
typedef GLint(*nkglGetAttribLocation)(GLuint, const GLchar*);
|
||||
typedef void(*nkglUniform1i)(GLint, GLint);
|
||||
typedef void(*nkglUniform1f)(GLint, GLfloat);
|
||||
typedef void(*nkglUniformMatrix3fv)(GLint, GLsizei, GLboolean, const GLfloat*);
|
||||
typedef void(*nkglUniformMatrix4fv)(GLint, GLsizei, GLboolean, const GLfloat*);
|
||||
|
||||
static nkglGenBuffers glGenBuffers;
|
||||
static nkglBindBuffer glBindBuffer;
|
||||
static nkglBufferData glBufferData;
|
||||
static nkglBufferSubData glBufferSubData;
|
||||
static nkglMapBuffer glMapBuffer;
|
||||
static nkglUnmapBuffer glUnmapBuffer;
|
||||
static nkglDeleteBuffers glDeleteBuffers;
|
||||
static nkglGenVertexArrays glGenVertexArrays;
|
||||
static nkglBindVertexArray glBindVertexArray;
|
||||
static nkglDeleteVertexArrays glDeleteVertexArrays;
|
||||
static nkglVertexAttribPointer glVertexAttribPointer;
|
||||
static nkglEnableVertexAttribArray glEnableVertexAttribArray;
|
||||
static nkglDisableVertexAttribArray glDisableVertexAttribArray;
|
||||
static nkglGenerateMipmap glGenerateMipmap;
|
||||
static nkglCreateShader glCreateShader;
|
||||
static nkglShaderSource glShaderSource;
|
||||
static nkglCompileShader glCompileShader;
|
||||
static nkglGetShaderiv glGetShaderiv;
|
||||
static nkglGetShaderInfoLog glGetShaderInfoLog;
|
||||
static nkglDeleteShader glDeleteShader;
|
||||
static nkglCreateProgram glCreateProgram;
|
||||
static nkglAttachShader glAttachShader;
|
||||
static nkglDetachShader glDetachShader;
|
||||
static nkglLinkProgram glLinkProgram;
|
||||
static nkglUseProgram glUseProgram;
|
||||
static nkglGetProgramiv glGetProgramiv;
|
||||
static nkglGetProgramInfoLog glGetProgramInfoLog;
|
||||
static nkglDeleteProgram glDeleteProgram;
|
||||
static nkglGetUniformLocation glGetUniformLocation;
|
||||
static nkglGetAttribLocation glGetAttribLocation;
|
||||
static nkglUniform1i glUniform1i;
|
||||
static nkglUniform1f glUniform1f;
|
||||
static nkglUniformMatrix3fv glUniformMatrix3fv;
|
||||
static nkglUniformMatrix4fv glUniformMatrix4fv;
|
||||
|
||||
enum graphics_card_vendors {
|
||||
VENDOR_UNKNOWN,
|
||||
VENDOR_NVIDIA,
|
||||
VENDOR_AMD,
|
||||
VENDOR_INTEL
|
||||
};
|
||||
|
||||
struct opengl_info {
|
||||
/* info */
|
||||
const char *vendor_str;
|
||||
const char *version_str;
|
||||
const char *extensions_str;
|
||||
const char *renderer_str;
|
||||
const char *glsl_version_str;
|
||||
enum graphics_card_vendors vendor;
|
||||
/* version */
|
||||
float version;
|
||||
int major_version;
|
||||
int minor_version;
|
||||
/* extensions */
|
||||
int glsl_available;
|
||||
int vertex_buffer_obj_available;
|
||||
int vertex_array_obj_available;
|
||||
int map_buffer_range_available;
|
||||
int fragment_program_available;
|
||||
int frame_buffer_object_available;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct nk_x11_vertex {
|
||||
float position[2];
|
||||
float uv[2];
|
||||
nk_byte col[4];
|
||||
};
|
||||
|
||||
struct nk_x11_device {
|
||||
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
|
||||
struct opengl_info info;
|
||||
#endif
|
||||
struct nk_buffer cmds;
|
||||
struct nk_draw_null_texture tex_null;
|
||||
GLuint vbo, vao, ebo;
|
||||
GLuint prog;
|
||||
GLuint vert_shdr;
|
||||
GLuint frag_shdr;
|
||||
GLint attrib_pos;
|
||||
GLint attrib_uv;
|
||||
GLint attrib_col;
|
||||
GLint uniform_tex;
|
||||
GLint uniform_proj;
|
||||
GLuint font_tex;
|
||||
};
|
||||
|
||||
static struct nk_x11 {
|
||||
struct nk_x11_device ogl;
|
||||
struct nk_context ctx;
|
||||
struct nk_font_atlas atlas;
|
||||
Cursor cursor;
|
||||
Display *dpy;
|
||||
Window win;
|
||||
long last_button_click;
|
||||
} x11;
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define NK_SHADER_VERSION "#version 150\n"
|
||||
#else
|
||||
#define NK_SHADER_VERSION "#version 300 es\n"
|
||||
#endif
|
||||
|
||||
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
|
||||
#include <GL/glx.h>
|
||||
|
||||
NK_INTERN long
|
||||
nk_timestamp(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
if (gettimeofday(&tv, NULL) < 0) return 0;
|
||||
return (long)((long)tv.tv_sec * 1000 + (long)tv.tv_usec/1000);
|
||||
}
|
||||
|
||||
NK_INTERN int
|
||||
nk_x11_stricmpn(const char *a, const char *b, int len)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < len && a[i] && b[i]; ++i)
|
||||
if (a[i] != b[i]) return 1;
|
||||
if (i != len) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
NK_INTERN int
|
||||
nk_x11_check_extension(struct opengl_info *GL, const char *ext)
|
||||
{
|
||||
const char *start, *where, *term;
|
||||
where = strchr(ext, ' ');
|
||||
if (where || *ext == '\0')
|
||||
return nk_false;
|
||||
|
||||
for (start = GL->extensions_str;;) {
|
||||
where = strstr((const char*)start, ext);
|
||||
if (!where) break;
|
||||
term = where + strlen(ext);
|
||||
if (where == start || *(where - 1) == ' ') {
|
||||
if (*term == ' ' || *term == '\0')
|
||||
return nk_true;
|
||||
}
|
||||
start = term;
|
||||
}
|
||||
return nk_false;
|
||||
}
|
||||
|
||||
#define GL_EXT(name) (nk##name)nk_gl_ext(#name)
|
||||
NK_INTERN __GLXextFuncPtr
|
||||
nk_gl_ext(const char *name)
|
||||
{
|
||||
__GLXextFuncPtr func;
|
||||
func = glXGetProcAddress((const GLubyte*)name);
|
||||
if (!func) {
|
||||
fprintf(stdout, "[GL]: failed to load extension: %s", name);
|
||||
return NULL;
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
NK_INTERN int
|
||||
nk_load_opengl(struct opengl_info *gl)
|
||||
{
|
||||
int failed = nk_false;
|
||||
gl->version_str = (const char*)glGetString(GL_VERSION);
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &gl->major_version);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &gl->minor_version);
|
||||
if (gl->major_version < 2) {
|
||||
fprintf(stderr, "[GL]: Graphics card does not fulfill minimum OpenGL 2.0 support\n");
|
||||
return 0;
|
||||
}
|
||||
gl->version = (float)gl->major_version + (float)gl->minor_version * 0.1f;
|
||||
gl->renderer_str = (const char*)glGetString(GL_RENDERER);
|
||||
gl->extensions_str = (const char*)glGetString(GL_EXTENSIONS);
|
||||
gl->glsl_version_str = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
gl->vendor_str = (const char*)glGetString(GL_VENDOR);
|
||||
if (!nk_x11_stricmpn(gl->vendor_str, "ATI", 4) ||
|
||||
!nk_x11_stricmpn(gl->vendor_str, "AMD", 4))
|
||||
gl->vendor = VENDOR_AMD;
|
||||
else if (!nk_x11_stricmpn(gl->vendor_str, "NVIDIA", 6))
|
||||
gl->vendor = VENDOR_NVIDIA;
|
||||
else if (!nk_x11_stricmpn(gl->vendor_str, "Intel", 5))
|
||||
gl->vendor = VENDOR_INTEL;
|
||||
else gl->vendor = VENDOR_UNKNOWN;
|
||||
|
||||
/* Extensions */
|
||||
gl->glsl_available = (gl->version >= 2.0f);
|
||||
if (gl->glsl_available) {
|
||||
/* GLSL core in OpenGL > 2 */
|
||||
glCreateShader = GL_EXT(glCreateShader);
|
||||
glShaderSource = GL_EXT(glShaderSource);
|
||||
glCompileShader = GL_EXT(glCompileShader);
|
||||
glGetShaderiv = GL_EXT(glGetShaderiv);
|
||||
glGetShaderInfoLog = GL_EXT(glGetShaderInfoLog);
|
||||
glDeleteShader = GL_EXT(glDeleteShader);
|
||||
glCreateProgram = GL_EXT(glCreateProgram);
|
||||
glAttachShader = GL_EXT(glAttachShader);
|
||||
glDetachShader = GL_EXT(glDetachShader);
|
||||
glLinkProgram = GL_EXT(glLinkProgram);
|
||||
glUseProgram = GL_EXT(glUseProgram);
|
||||
glGetProgramiv = GL_EXT(glGetProgramiv);
|
||||
glGetProgramInfoLog = GL_EXT(glGetProgramInfoLog);
|
||||
glDeleteProgram = GL_EXT(glDeleteProgram);
|
||||
glGetUniformLocation = GL_EXT(glGetUniformLocation);
|
||||
glGetAttribLocation = GL_EXT(glGetAttribLocation);
|
||||
glUniform1i = GL_EXT(glUniform1i);
|
||||
glUniform1f = GL_EXT(glUniform1f);
|
||||
glUniformMatrix3fv = GL_EXT(glUniformMatrix3fv);
|
||||
glUniformMatrix4fv = GL_EXT(glUniformMatrix4fv);
|
||||
}
|
||||
gl->vertex_buffer_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_buffer_object");
|
||||
if (gl->vertex_buffer_obj_available) {
|
||||
/* GL_ARB_vertex_buffer_object */
|
||||
glGenBuffers = GL_EXT(glGenBuffers);
|
||||
glBindBuffer = GL_EXT(glBindBuffer);
|
||||
glBufferData = GL_EXT(glBufferData);
|
||||
glBufferSubData = GL_EXT(glBufferSubData);
|
||||
glMapBuffer = GL_EXT(glMapBuffer);
|
||||
glUnmapBuffer = GL_EXT(glUnmapBuffer);
|
||||
glDeleteBuffers = GL_EXT(glDeleteBuffers);
|
||||
}
|
||||
gl->fragment_program_available = nk_x11_check_extension(gl, "GL_ARB_fragment_program");
|
||||
if (gl->fragment_program_available) {
|
||||
/* GL_ARB_vertex_program / GL_ARB_fragment_program */
|
||||
glVertexAttribPointer = GL_EXT(glVertexAttribPointer);
|
||||
glEnableVertexAttribArray = GL_EXT(glEnableVertexAttribArray);
|
||||
glDisableVertexAttribArray = GL_EXT(glDisableVertexAttribArray);
|
||||
}
|
||||
gl->vertex_array_obj_available = nk_x11_check_extension(gl, "GL_ARB_vertex_array_object");
|
||||
if (gl->vertex_array_obj_available) {
|
||||
/* GL_ARB_vertex_array_object */
|
||||
glGenVertexArrays = GL_EXT(glGenVertexArrays);
|
||||
glBindVertexArray = GL_EXT(glBindVertexArray);
|
||||
glDeleteVertexArrays = GL_EXT(glDeleteVertexArrays);
|
||||
}
|
||||
gl->frame_buffer_object_available = nk_x11_check_extension(gl, "GL_ARB_framebuffer_object");
|
||||
if (gl->frame_buffer_object_available) {
|
||||
/* GL_ARB_framebuffer_object */
|
||||
glGenerateMipmap = GL_EXT(glGenerateMipmap);
|
||||
}
|
||||
if (!gl->vertex_buffer_obj_available) {
|
||||
fprintf(stdout, "[GL] Error: GL_ARB_vertex_buffer_object is not available!\n");
|
||||
failed = nk_true;
|
||||
}
|
||||
if (!gl->fragment_program_available) {
|
||||
fprintf(stdout, "[GL] Error: GL_ARB_fragment_program is not available!\n");
|
||||
failed = nk_true;
|
||||
}
|
||||
if (!gl->vertex_array_obj_available) {
|
||||
fprintf(stdout, "[GL] Error: GL_ARB_vertex_array_object is not available!\n");
|
||||
failed = nk_true;
|
||||
}
|
||||
if (!gl->frame_buffer_object_available) {
|
||||
fprintf(stdout, "[GL] Error: GL_ARB_framebuffer_object is not available!\n");
|
||||
failed = nk_true;
|
||||
}
|
||||
return !failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
NK_API int
|
||||
nk_x11_device_create(void)
|
||||
{
|
||||
GLint status;
|
||||
static const GLchar *vertex_shader =
|
||||
NK_SHADER_VERSION
|
||||
"uniform mat4 ProjMtx;\n"
|
||||
"in vec2 Position;\n"
|
||||
"in vec2 TexCoord;\n"
|
||||
"in vec4 Color;\n"
|
||||
"out vec2 Frag_UV;\n"
|
||||
"out vec4 Frag_Color;\n"
|
||||
"void main() {\n"
|
||||
" Frag_UV = TexCoord;\n"
|
||||
" Frag_Color = Color;\n"
|
||||
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
|
||||
"}\n";
|
||||
static const GLchar *fragment_shader =
|
||||
NK_SHADER_VERSION
|
||||
"precision mediump float;\n"
|
||||
"uniform sampler2D Texture;\n"
|
||||
"in vec2 Frag_UV;\n"
|
||||
"in vec4 Frag_Color;\n"
|
||||
"out vec4 Out_Color;\n"
|
||||
"void main(){\n"
|
||||
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
|
||||
"}\n";
|
||||
|
||||
struct nk_x11_device *dev = &x11.ogl;
|
||||
#ifdef NK_XLIB_LOAD_OPENGL_EXTENSIONS
|
||||
if (!nk_load_opengl(&dev->info)) return 0;
|
||||
#endif
|
||||
nk_buffer_init_default(&dev->cmds);
|
||||
|
||||
dev->prog = glCreateProgram();
|
||||
dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
|
||||
dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0);
|
||||
glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0);
|
||||
glCompileShader(dev->vert_shdr);
|
||||
glCompileShader(dev->frag_shdr);
|
||||
glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
|
||||
assert(status == GL_TRUE);
|
||||
glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
|
||||
assert(status == GL_TRUE);
|
||||
glAttachShader(dev->prog, dev->vert_shdr);
|
||||
glAttachShader(dev->prog, dev->frag_shdr);
|
||||
glLinkProgram(dev->prog);
|
||||
glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
|
||||
assert(status == GL_TRUE);
|
||||
|
||||
dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture");
|
||||
dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx");
|
||||
dev->attrib_pos = glGetAttribLocation(dev->prog, "Position");
|
||||
dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord");
|
||||
dev->attrib_col = glGetAttribLocation(dev->prog, "Color");
|
||||
|
||||
{
|
||||
/* buffer setup */
|
||||
GLsizei vs = sizeof(struct nk_x11_vertex);
|
||||
size_t vp = offsetof(struct nk_x11_vertex, position);
|
||||
size_t vt = offsetof(struct nk_x11_vertex, uv);
|
||||
size_t vc = offsetof(struct nk_x11_vertex, col);
|
||||
|
||||
glGenBuffers(1, &dev->vbo);
|
||||
glGenBuffers(1, &dev->ebo);
|
||||
glGenVertexArrays(1, &dev->vao);
|
||||
|
||||
glBindVertexArray(dev->vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
|
||||
|
||||
glEnableVertexAttribArray((GLuint)dev->attrib_pos);
|
||||
glEnableVertexAttribArray((GLuint)dev->attrib_uv);
|
||||
glEnableVertexAttribArray((GLuint)dev->attrib_col);
|
||||
|
||||
glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp);
|
||||
glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt);
|
||||
glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
NK_INTERN void
|
||||
nk_x11_device_upload_atlas(const void *image, int width, int height)
|
||||
{
|
||||
struct nk_x11_device *dev = &x11.ogl;
|
||||
glGenTextures(1, &dev->font_tex);
|
||||
glBindTexture(GL_TEXTURE_2D, dev->font_tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, image);
|
||||
}
|
||||
|
||||
NK_API void
|
||||
nk_x11_device_destroy(void)
|
||||
{
|
||||
struct nk_x11_device *dev = &x11.ogl;
|
||||
glDetachShader(dev->prog, dev->vert_shdr);
|
||||
glDetachShader(dev->prog, dev->frag_shdr);
|
||||
glDeleteShader(dev->vert_shdr);
|
||||
glDeleteShader(dev->frag_shdr);
|
||||
glDeleteProgram(dev->prog);
|
||||
glDeleteTextures(1, &dev->font_tex);
|
||||
glDeleteBuffers(1, &dev->vbo);
|
||||
glDeleteBuffers(1, &dev->ebo);
|
||||
nk_buffer_free(&dev->cmds);
|
||||
}
|
||||
|
||||
NK_API void
|
||||
nk_x11_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer)
|
||||
{
|
||||
int width, height;
|
||||
XWindowAttributes attr;
|
||||
struct nk_x11_device *dev = &x11.ogl;
|
||||
GLfloat ortho[4][4] = {
|
||||
{2.0f, 0.0f, 0.0f, 0.0f},
|
||||
{0.0f,-2.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f,-1.0f, 0.0f},
|
||||
{-1.0f,1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
XGetWindowAttributes(x11.dpy, x11.win, &attr);
|
||||
width = attr.width;
|
||||
height = attr.height;
|
||||
|
||||
ortho[0][0] /= (GLfloat)width;
|
||||
ortho[1][1] /= (GLfloat)height;
|
||||
|
||||
/* setup global state */
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
/* setup program */
|
||||
glUseProgram(dev->prog);
|
||||
glUniform1i(dev->uniform_tex, 0);
|
||||
glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
|
||||
glViewport(0,0,(GLsizei)width,(GLsizei)height);
|
||||
{
|
||||
/* convert from command queue into draw list and draw to screen */
|
||||
const struct nk_draw_command *cmd;
|
||||
void *vertices, *elements;
|
||||
const nk_draw_index *offset = NULL;
|
||||
struct nk_buffer vbuf, ebuf;
|
||||
|
||||
/* allocate vertex and element buffer */
|
||||
glBindVertexArray(dev->vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, NULL, GL_STREAM_DRAW);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, NULL, GL_STREAM_DRAW);
|
||||
|
||||
/* load draw vertices & elements directly into vertex + element buffer */
|
||||
vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
|
||||
elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
|
||||
{
|
||||
/* fill convert configuration */
|
||||
struct nk_convert_config config;
|
||||
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
|
||||
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, position)},
|
||||
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_x11_vertex, uv)},
|
||||
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_x11_vertex, col)},
|
||||
{NK_VERTEX_LAYOUT_END}
|
||||
};
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.vertex_layout = vertex_layout;
|
||||
config.vertex_size = sizeof(struct nk_x11_vertex);
|
||||
config.vertex_alignment = NK_ALIGNOF(struct nk_x11_vertex);
|
||||
config.tex_null = dev->tex_null;
|
||||
config.circle_segment_count = 22;
|
||||
config.curve_segment_count = 22;
|
||||
config.arc_segment_count = 22;
|
||||
config.global_alpha = 1.0f;
|
||||
config.shape_AA = AA;
|
||||
config.line_AA = AA;
|
||||
|
||||
/* setup buffers to load vertices and elements */
|
||||
nk_buffer_init_fixed(&vbuf, vertices, (size_t)max_vertex_buffer);
|
||||
nk_buffer_init_fixed(&ebuf, elements, (size_t)max_element_buffer);
|
||||
nk_convert(&x11.ctx, &dev->cmds, &vbuf, &ebuf, &config);
|
||||
}
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
/* iterate over and execute each draw command */
|
||||
nk_draw_foreach(cmd, &x11.ctx, &dev->cmds)
|
||||
{
|
||||
if (!cmd->elem_count) continue;
|
||||
// GLint unit = cmd->texture.id
|
||||
glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
|
||||
glScissor(
|
||||
(GLint)(cmd->clip_rect.x),
|
||||
(GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h))),
|
||||
(GLint)(cmd->clip_rect.w),
|
||||
(GLint)(cmd->clip_rect.h));
|
||||
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
|
||||
offset += cmd->elem_count;
|
||||
}
|
||||
nk_clear(&x11.ctx);
|
||||
nk_buffer_clear(&dev->cmds);
|
||||
}
|
||||
|
||||
/* default OpenGL state */
|
||||
glUseProgram(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
NK_API void
|
||||
nk_x11_font_stash_begin(struct nk_font_atlas **atlas)
|
||||
{
|
||||
nk_font_atlas_init_default(&x11.atlas);
|
||||
nk_font_atlas_begin(&x11.atlas);
|
||||
*atlas = &x11.atlas;
|
||||
}
|
||||
|
||||
NK_API void
|
||||
nk_x11_font_stash_end(void)
|
||||
{
|
||||
const void *image; int w, h;
|
||||
image = nk_font_atlas_bake(&x11.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
|
||||
nk_x11_device_upload_atlas(image, w, h);
|
||||
nk_font_atlas_end(&x11.atlas, nk_handle_id((int)x11.ogl.font_tex), &x11.ogl.tex_null);
|
||||
if (x11.atlas.default_font)
|
||||
nk_style_set_font(&x11.ctx, &x11.atlas.default_font->handle);
|
||||
}
|
||||
|
||||
NK_API int
|
||||
nk_x11_handle_event(XEvent *evt)
|
||||
{
|
||||
struct nk_context *ctx = &x11.ctx;
|
||||
|
||||
/* optional grabbing behavior */
|
||||
if (ctx->input.mouse.grab) {
|
||||
XDefineCursor(x11.dpy, x11.win, x11.cursor);
|
||||
ctx->input.mouse.grab = 0;
|
||||
} else if (ctx->input.mouse.ungrab) {
|
||||
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0,
|
||||
(int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
|
||||
XUndefineCursor(x11.dpy, x11.win);
|
||||
ctx->input.mouse.ungrab = 0;
|
||||
}
|
||||
|
||||
if (evt->type == KeyPress || evt->type == KeyRelease)
|
||||
{
|
||||
/* Key handler */
|
||||
int ret, down = (evt->type == KeyPress);
|
||||
KeySym *code = XGetKeyboardMapping(x11.dpy, (KeyCode)evt->xkey.keycode, 1, &ret);
|
||||
if (*code == XK_Shift_L || *code == XK_Shift_R) nk_input_key(ctx, NK_KEY_SHIFT, down);
|
||||
else if (*code == XK_Control_L || *code == XK_Control_R) nk_input_key(ctx, NK_KEY_CTRL, down);
|
||||
else if (*code == XK_Alt_L || *code == XK_Alt_R) nk_input_key(ctx, NK_KEY_ALT, down);
|
||||
else if (*code == XK_Delete) nk_input_key(ctx, NK_KEY_DEL, down);
|
||||
else if (*code == XK_Return) nk_input_key(ctx, NK_KEY_ENTER, down);
|
||||
else if (*code == XK_Tab) nk_input_key(ctx, NK_KEY_TAB, down);
|
||||
else if (*code == XK_Left) nk_input_key(ctx, NK_KEY_LEFT, down);
|
||||
else if (*code == XK_Right) nk_input_key(ctx, NK_KEY_RIGHT, down);
|
||||
else if (*code == XK_Up) nk_input_key(ctx, NK_KEY_UP, down);
|
||||
else if (*code == XK_Down) nk_input_key(ctx, NK_KEY_DOWN, down);
|
||||
else if (*code == XK_BackSpace) nk_input_key(ctx, NK_KEY_BACKSPACE, down);
|
||||
else if (*code == XK_Escape) nk_input_key(ctx, NK_KEY_ESCAPE, down);
|
||||
// else if (*code == XK_space && !down) nk_input_char(ctx, ' ');
|
||||
else if (*code == XK_Page_Up) nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
|
||||
else if (*code == XK_Page_Down) nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
|
||||
else if (*code == XK_Home) {
|
||||
nk_input_key(ctx, NK_KEY_TEXT_START, down);
|
||||
nk_input_key(ctx, NK_KEY_SCROLL_START, down);
|
||||
} else if (*code == XK_End) {
|
||||
nk_input_key(ctx, NK_KEY_TEXT_END, down);
|
||||
nk_input_key(ctx, NK_KEY_SCROLL_END, down);
|
||||
} else {
|
||||
if (*code == 'c' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_COPY, down);
|
||||
else if (*code == 'v' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_PASTE, down);
|
||||
else if (*code == 'x' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_CUT, down);
|
||||
else if (*code == 'z' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_UNDO, down);
|
||||
else if (*code == 'r' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_REDO, down);
|
||||
else if (*code == XK_Left && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
|
||||
else if (*code == XK_Right && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
|
||||
else if (*code == 'b' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down);
|
||||
else if (*code == 'e' && (evt->xkey.state & ControlMask))
|
||||
nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down);
|
||||
else {
|
||||
if (*code == 'i')
|
||||
nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down);
|
||||
else if (*code == 'r')
|
||||
nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down);
|
||||
|
||||
char buf[32];
|
||||
KeySym keysym = 0;
|
||||
if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) {
|
||||
if (down)
|
||||
nk_input_glyph(ctx, buf);
|
||||
} else {
|
||||
// printf("Unknown key %lx, pass on RAW %d\n", *code,
|
||||
// ctx->input.keyboard.text_len);
|
||||
uint32_t *d = (uint32_t *) ctx->input.keyboard.text;
|
||||
d[ctx->input.keyboard.text_len / 4] =
|
||||
(*code << 16) | !!down;
|
||||
ctx->input.keyboard.text_len += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
XFree(code);
|
||||
return 1;
|
||||
} else if (evt->type == ButtonPress || evt->type == ButtonRelease) {
|
||||
/* Button handler */
|
||||
int down = (evt->type == ButtonPress);
|
||||
const int x = evt->xbutton.x, y = evt->xbutton.y;
|
||||
if (evt->xbutton.button == Button1) {
|
||||
if (down) { /* Double-Click Button handler */
|
||||
long dt = nk_timestamp() - x11.last_button_click;
|
||||
if (dt > NK_X11_DOUBLE_CLICK_LO && dt < NK_X11_DOUBLE_CLICK_HI)
|
||||
nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_true);
|
||||
x11.last_button_click = nk_timestamp();
|
||||
} else nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, nk_false);
|
||||
nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down);
|
||||
} else if (evt->xbutton.button == Button2)
|
||||
nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down);
|
||||
else if (evt->xbutton.button == Button3)
|
||||
nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down);
|
||||
else if (evt->xbutton.button == Button4)
|
||||
nk_input_scroll(ctx, nk_vec2(0,1.0f));
|
||||
else if (evt->xbutton.button == Button5)
|
||||
nk_input_scroll(ctx, nk_vec2(0,-1.0f));
|
||||
else return 0;
|
||||
return 1;
|
||||
} else if (evt->type == MotionNotify) {
|
||||
/* Mouse motion handler */
|
||||
const int x = evt->xmotion.x, y = evt->xmotion.y;
|
||||
nk_input_motion(ctx, x, y);
|
||||
if (ctx->input.mouse.grabbed) {
|
||||
ctx->input.mouse.pos.x = ctx->input.mouse.prev.x;
|
||||
ctx->input.mouse.pos.y = ctx->input.mouse.prev.y;
|
||||
XWarpPointer(x11.dpy, None, x11.win, 0, 0, 0, 0, (int)ctx->input.mouse.pos.x, (int)ctx->input.mouse.pos.y);
|
||||
}
|
||||
return 1;
|
||||
} else if (evt->type == KeymapNotify) {
|
||||
XRefreshKeyboardMapping(&evt->xmapping);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
NK_API struct nk_context*
|
||||
nk_x11_init(Display *dpy, Window win)
|
||||
{
|
||||
if (!setlocale(LC_ALL,"")) return 0;
|
||||
if (!XSupportsLocale()) return 0;
|
||||
if (!XSetLocaleModifiers("@im=none")) return 0;
|
||||
if (!nk_x11_device_create()) return 0;
|
||||
|
||||
x11.dpy = dpy;
|
||||
x11.win = win;
|
||||
|
||||
/* create invisible cursor */
|
||||
{static XColor dummy; char data[1] = {0};
|
||||
Pixmap blank = XCreateBitmapFromData(dpy, win, data, 1, 1);
|
||||
if (blank == None) return 0;
|
||||
x11.cursor = XCreatePixmapCursor(dpy, blank, blank, &dummy, &dummy, 0, 0);
|
||||
XFreePixmap(dpy, blank);}
|
||||
|
||||
nk_init_default(&x11.ctx, 0);
|
||||
return &x11.ctx;
|
||||
}
|
||||
|
||||
NK_API void
|
||||
nk_x11_shutdown(void)
|
||||
{
|
||||
nk_font_atlas_clear(&x11.atlas);
|
||||
nk_free(&x11.ctx);
|
||||
nk_x11_device_destroy();
|
||||
XFreeCursor(x11.dpy, x11.cursor);
|
||||
memset(&x11, 0, sizeof(x11));
|
||||
}
|
||||
|
||||
#endif
|
379
src/drivers/mii_disk2.c
Normal file
379
src/drivers/mii_disk2.c
Normal file
@ -0,0 +1,379 @@
|
||||
/*
|
||||
* mii_disk2.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// Shamelesly lifted from periph/disk2.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_disk2.h"
|
||||
#include "mii_rom_disk2.h"
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
//#define DISK_DEBUG
|
||||
#ifdef DISK_DEBUG
|
||||
# define D2DBG(...) do { \
|
||||
if (c->pr_count == 1) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while(0)
|
||||
#else
|
||||
# define D2DBG(...)
|
||||
#endif
|
||||
|
||||
typedef struct mii_card_disk2_t {
|
||||
uint32_t timer;
|
||||
bool motor_on;
|
||||
bool drive_two;
|
||||
bool write_mode;
|
||||
union {
|
||||
struct {
|
||||
DiskFormatDesc disk1;
|
||||
DiskFormatDesc disk2;
|
||||
};
|
||||
DiskFormatDesc disk[2];
|
||||
};
|
||||
uint8_t data_register; // only used for write
|
||||
bool steppers[4];
|
||||
int cog1;
|
||||
int cog2;
|
||||
// int pr_count; // debug print
|
||||
} mii_card_disk2_t;
|
||||
|
||||
//static const size_t dsk_disksz = 143360;
|
||||
|
||||
int disk2_debug = 0;
|
||||
|
||||
static DiskFormatDesc *
|
||||
active_disk_obj(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->drive_two ? &c->disk2 : &c->disk1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool
|
||||
drive_spinning(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->motor_on;
|
||||
}
|
||||
|
||||
int
|
||||
active_disk(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->drive_two ? 2 : 1;
|
||||
}
|
||||
int
|
||||
eject_disk(
|
||||
mii_card_disk2_t *c,
|
||||
int drive)
|
||||
{
|
||||
if (c->motor_on && active_disk(c) == drive) {
|
||||
return -1;
|
||||
}
|
||||
init(c); // to make sure
|
||||
|
||||
if (drive == 1) {
|
||||
c->disk1.eject(&c->disk1);
|
||||
c->disk1 = disk_format_load(NULL);
|
||||
} else if (drive == 2) {
|
||||
c->disk2.eject(&c->disk2);
|
||||
c->disk2 = disk_format_load(NULL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
insert_disk(
|
||||
mii_card_disk2_t *c,
|
||||
int drive,
|
||||
const char *path)
|
||||
{
|
||||
int err = eject_disk(c, drive);
|
||||
if (err != 0) return err;
|
||||
|
||||
// Make sure we're inserted to slot 6
|
||||
// XXX should check for error/distinguish if we're already in that slot
|
||||
(void) periph_slot_reg(6, &disk2card);
|
||||
|
||||
if (err) {
|
||||
return -1; // XXX should be a distinguishable err code
|
||||
}
|
||||
if (drive == 1) {
|
||||
c->disk1 = disk_format_load(path);
|
||||
} else if (drive == 2) {
|
||||
c->disk2 = disk_format_load(path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// NOTE: cog "left" and "right" refers only to the number line,
|
||||
// and not the physical cog or track head movement.
|
||||
static bool
|
||||
cogleft(
|
||||
mii_card_disk2_t *c,
|
||||
int *cog)
|
||||
{
|
||||
int cl = ((*cog) + 3) % 4; // position to the "left" of cog
|
||||
return (!c->steppers[(*cog)] && c->steppers[cl]);
|
||||
}
|
||||
|
||||
// NOTE: cog "left" and "right" refers only to the number line,
|
||||
// and not the physical cog or track head movement.
|
||||
static bool
|
||||
cogright(
|
||||
mii_card_disk2_t *c,
|
||||
int *cog)
|
||||
{
|
||||
int cr = ((*cog) + 1) % 4; // position to the "right" of cog
|
||||
return (!c->steppers[(*cog)] && c->steppers[cr]);
|
||||
}
|
||||
|
||||
static int *
|
||||
active_cog(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->drive_two? &c->cog2 : &c->cog1;
|
||||
}
|
||||
|
||||
static void
|
||||
adjust_track(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
int *cog = active_cog(c);
|
||||
D2DBG("halftrack: ");
|
||||
if (cogleft(c, cog)) {
|
||||
*cog = ((*cog) + 3) % 4;
|
||||
if (disk->halftrack > 0) --disk->halftrack;
|
||||
D2DBG("dec to %d", disk->halftrack);
|
||||
} else if (cogright(c, cog)) {
|
||||
*cog = ((*cog) + 1) % 4;
|
||||
if (disk->halftrack < 69) ++disk->halftrack;
|
||||
D2DBG("inc to %d", disk->halftrack);
|
||||
} else {
|
||||
D2DBG("no change (%d)", disk->halftrack);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stepper_motor(
|
||||
mii_card_disk2_t *c,
|
||||
uint8_t psw)
|
||||
{
|
||||
uint8_t phase = psw >> 1;
|
||||
bool onoff = (psw & 1) != 0;
|
||||
|
||||
D2DBG("phase %d %s ", (int)phase, onoff? "on, " : "off,");
|
||||
c->steppers[phase] = onoff;
|
||||
adjust_track(c);
|
||||
}
|
||||
|
||||
static inline uint8_t encode4x4(mii_card_disk2_t *c, uint8_t orig)
|
||||
{
|
||||
return (orig | 0xAA);
|
||||
}
|
||||
|
||||
static void turn_off_motor(mii_card_disk2_t *c)
|
||||
{
|
||||
c->motor_on = false;
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
disk->spin(disk, false);
|
||||
// event_fire_disk_active(0);
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_disk2_init(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot )
|
||||
{
|
||||
mii_card_disk2_t *c = calloc(1, sizeof(*c));
|
||||
|
||||
slot->drv_priv = c;
|
||||
c->disk1 = disk_format_load(NULL);
|
||||
c->disk2 = disk_format_load(NULL);
|
||||
printf("%s loading in slot %d\n", __func__, slot->id + 1);
|
||||
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr, mii_rom_disk2, 256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_disk2_run(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot)
|
||||
{
|
||||
mii_card_disk2_t *c = slot->drv_priv;
|
||||
if (c->timer && c->timer <= mii->video.frame_count) {
|
||||
printf("%s turning off motor\n", __func__);
|
||||
c->timer = 0;
|
||||
turn_off_motor(c);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_mii_disk2_access(
|
||||
mii_t * mii, struct mii_slot_t *slot,
|
||||
uint16_t addr, uint8_t byte, bool write)
|
||||
{
|
||||
mii_card_disk2_t *c = slot->drv_priv;
|
||||
uint8_t ret = 0;
|
||||
|
||||
int psw = addr & 0x0F;
|
||||
|
||||
if (c->timer)
|
||||
c->timer = mii->video.frame_count + 60;
|
||||
uint8_t last = -1;
|
||||
if (disk2_debug && last != psw + (write? 0x10 : 0))
|
||||
printf("disk sw $%02X, PC = $%04X\n", psw, mii->cpu.PC);
|
||||
last = psw + (write? 0x10 : 0);
|
||||
if (write)
|
||||
c->data_register = byte; // ANY write sets data register
|
||||
if (psw < 8) {
|
||||
stepper_motor(c, psw);
|
||||
} else switch (psw) {
|
||||
case 0x08:
|
||||
if (c->motor_on) {
|
||||
c->timer = mii->video.frame_count + 60;
|
||||
//frame_timer(60, turn_off_motor);
|
||||
}
|
||||
break;
|
||||
case 0x09: {
|
||||
// frame_timer_cancel(turn_off_motor);
|
||||
c->timer = 0;
|
||||
c->motor_on = true;
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
disk->spin(disk, true);
|
||||
if (disk2_debug)
|
||||
printf("%s turning on motor %d\n", __func__, c->drive_two);
|
||||
// event_fire_disk_active(drive_two? 2 : 1);
|
||||
} break;
|
||||
case 0x0A:
|
||||
if (c->motor_on && c->drive_two) {
|
||||
c->disk2.spin(&c->disk2, false);
|
||||
c->disk1.spin(&c->disk1, true);
|
||||
}
|
||||
c->drive_two = false;
|
||||
if (c->motor_on) {
|
||||
// event_fire_disk_active(1);
|
||||
}
|
||||
break;
|
||||
case 0x0B:
|
||||
if (c->motor_on && !c->drive_two) {
|
||||
c->disk1.spin(&c->disk1, false);
|
||||
c->disk2.spin(&c->disk2, true);
|
||||
}
|
||||
c->drive_two = true;
|
||||
if (c->motor_on) {
|
||||
// event_fire_disk_active(2);
|
||||
}
|
||||
break;
|
||||
case 0x0C: {
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
if (!c->motor_on) {
|
||||
// do nothing
|
||||
} else if (c->write_mode) {
|
||||
// XXX ignores timing
|
||||
disk->write_byte(disk, c->data_register);
|
||||
c->data_register = 0; // "shifted out".
|
||||
} else {
|
||||
// XXX any even-numbered switch can be used
|
||||
// to read a byte. But for now we do so only
|
||||
// through the sanctioned switch for that purpose.
|
||||
ret = c->data_register = disk->read_byte(disk);
|
||||
// printf("read byte %02X\n", ret);
|
||||
}
|
||||
} break;
|
||||
case 0x0D:
|
||||
#if 0
|
||||
if (!motor_on || drive_two) {
|
||||
// do nothing
|
||||
} else if (write_mode) {
|
||||
data_register = (val == -1? 0: val);
|
||||
} else {
|
||||
// XXX should return whether disk is write-protected...
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case 0x0E:
|
||||
c->write_mode = false;
|
||||
if (disk2_debug)
|
||||
printf("%s write mode off\n", __func__);
|
||||
break;
|
||||
case 0x0F:
|
||||
c->write_mode = true;
|
||||
if (disk2_debug)
|
||||
printf("%s write mode on\n", __func__);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
D2DBG("\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_disk2_command(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot,
|
||||
uint8_t cmd,
|
||||
void * param)
|
||||
{
|
||||
mii_card_disk2_t *c = slot->drv_priv;
|
||||
switch (cmd) {
|
||||
case MII_SLOT_DRIVE_COUNT:
|
||||
if (param)
|
||||
*(int *)param = 2;
|
||||
break;
|
||||
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1:
|
||||
if (param) {
|
||||
int drive = cmd - MII_SLOT_DRIVE_LOAD;
|
||||
const char *filename = param;
|
||||
if (c->disk[drive].privdat) {
|
||||
c->disk[drive].eject(&c->disk[drive]);
|
||||
}
|
||||
printf("%s: drive %d loading %s\n", __func__, drive,
|
||||
filename);
|
||||
c->disk[drive] = disk_format_load(filename);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "disk2",
|
||||
.desc = "Apple Disk ][",
|
||||
.init = _mii_disk2_init,
|
||||
.access = _mii_disk2_access,
|
||||
.run = _mii_disk2_run,
|
||||
.command = _mii_disk2_command,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
9
src/drivers/mii_disk2.h
Normal file
9
src/drivers/mii_disk2.h
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* mii_disk2.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
89
src/drivers/mii_epromcard.c
Normal file
89
src/drivers/mii_epromcard.c
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* mii_epromcard.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* This is a driver for these eprom/flash cards from
|
||||
* Terence J. Boldt and the likes
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
|
||||
typedef struct mii_card_ee_t {
|
||||
uint8_t * file;
|
||||
uint16_t latch;
|
||||
} mii_card_ee_t;
|
||||
|
||||
int mmapfile(const char *fname, uint8_t **buf, size_t *sz, int flags);
|
||||
|
||||
static int
|
||||
_mii_ee_init(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot )
|
||||
{
|
||||
uint8_t * file;
|
||||
size_t sz;
|
||||
const char *fname = "disks/Apple IIe Diagnostic 2.1.po";
|
||||
//const char *fname = "disks/GamesWithFirmware.po";
|
||||
|
||||
if (mmapfile(fname, &file, &sz, O_RDONLY) != 0) {
|
||||
printf("Failed to load %s\n", fname);
|
||||
return -1;
|
||||
}
|
||||
mii_card_ee_t *c = calloc(1, sizeof(*c));
|
||||
|
||||
slot->drv_priv = c;
|
||||
c->file = file;
|
||||
printf("%s loading in slot %d\n", __func__, slot->id);
|
||||
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr, c->file + 0x300, 256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_mii_ee_access(
|
||||
mii_t * mii, struct mii_slot_t *slot,
|
||||
uint16_t addr, uint8_t byte, bool write)
|
||||
{
|
||||
mii_card_ee_t *c = slot->drv_priv;
|
||||
|
||||
// printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
|
||||
// mii->cpu.PC, addr, byte, write);
|
||||
int psw = addr & 0x0F;
|
||||
if (write) {
|
||||
switch (psw) {
|
||||
case 0:
|
||||
c->latch = (c->latch & 0xff00) | byte;
|
||||
break;
|
||||
case 1:
|
||||
c->latch = (c->latch & 0x00ff) | (byte << 8);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return c->file[(c->latch << 4) + psw];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "eecard",
|
||||
.desc = "EEPROM 1MB card",
|
||||
.init = _mii_ee_init,
|
||||
.access = _mii_ee_access,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
187
src/drivers/mii_mouse.c
Normal file
187
src/drivers/mii_mouse.c
Normal file
@ -0,0 +1,187 @@
|
||||
|
||||
|
||||
#include "mii.h"
|
||||
|
||||
// https://github.com/ivanizag/izapple2/blob/master/cardMouse.go
|
||||
// https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator/log/188017-entry-23-here-mousie-mousie-mousie
|
||||
// https://github.com/ct6502/apple2ts/blob/main/src/emulator/mouse.ts
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
|
||||
|
||||
enum {
|
||||
CLAMP_MIN_LO = 0x478,
|
||||
CLAMP_MIN_HI = 0x578,
|
||||
CLAMP_MAX_LO = 0x4F8,
|
||||
CLAMP_MAX_HI = 0x5F8,
|
||||
|
||||
// RAM Locations
|
||||
// (Add $Cn where n is slot to these)
|
||||
MOUSE_X_LO = 0x03B8,
|
||||
MOUSE_X_HI = 0x04B8,
|
||||
MOUSE_Y_LO = 0x0438,
|
||||
MOUSE_Y_HI = 0x0538,
|
||||
MOUSE_STATUS = 0x06B8,
|
||||
MOUSE_MODE = 0x0738,
|
||||
};
|
||||
|
||||
enum {
|
||||
mouseEnabled = 1,
|
||||
mouseIntMoveEnabled = 2,
|
||||
mouseIntButtonEnabled = 4,
|
||||
mouseIntVBlankEnabled = 8,
|
||||
};
|
||||
|
||||
typedef struct mii_card_mouse_t {
|
||||
struct mii_slot_t * slot;
|
||||
uint8_t slot_offset;
|
||||
uint8_t mode; // cached mode byte
|
||||
struct {
|
||||
uint16_t x, y;
|
||||
bool button;
|
||||
} last;
|
||||
} mii_card_mouse_t;
|
||||
|
||||
static int
|
||||
_mii_mouse_init(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot )
|
||||
{
|
||||
mii_card_mouse_t *c = calloc(1, sizeof(*c));
|
||||
c->slot = slot;
|
||||
slot->drv_priv = c;
|
||||
|
||||
printf("%s loading in slot %d\n", __func__, slot->id + 1);
|
||||
|
||||
c->slot_offset = slot->id + 1 + 0xc0;
|
||||
uint8_t data[256] = {};
|
||||
|
||||
// Identification as a mouse card
|
||||
// From Technical Note Misc #8, "Pascal 1.1 Firmware Protocol ID Bytes":
|
||||
data[0x05] = 0x38;
|
||||
data[0x07] = 0x18;
|
||||
data[0x0b] = 0x01;
|
||||
data[0x0c] = 0x20;
|
||||
// From "AppleMouse // User's Manual", Appendix B:
|
||||
//data[0x0c] = 0x20
|
||||
data[0xfb] = 0xd6;
|
||||
|
||||
// Set 8 entrypoints to sofstwitches 2 to 1f
|
||||
for (int i = 0; i < 14; i++) {
|
||||
uint8_t base = 0x60 + 0x05 * i;
|
||||
data[0x12+i] = base;
|
||||
data[base+0] = 0x8D; // STA $C0x2
|
||||
data[base+1] = 0x82 + i + ((slot->id + 1) << 4);
|
||||
data[base+2] = 0xC0;
|
||||
data[base+3] = 0x18; // CLC ;no error
|
||||
data[base+4] = 0x60; // RTS
|
||||
}
|
||||
|
||||
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr, data, 256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_mii_mouse_access(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot,
|
||||
uint16_t addr,
|
||||
uint8_t byte,
|
||||
bool write)
|
||||
{
|
||||
mii_card_mouse_t *c = slot->drv_priv;
|
||||
|
||||
int psw = addr & 0x0F;
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
switch (psw) {
|
||||
case 2: {
|
||||
if (write) {
|
||||
byte &= 0xf;
|
||||
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, byte);
|
||||
printf("%s: mouse mode %02x\n", __func__, byte);
|
||||
mii->mouse.enabled = byte & mouseEnabled;
|
||||
printf("Mouse %s\n", mii->mouse.enabled ? "enabled" : "disabled");
|
||||
printf(" Interupt: %s\n", byte & mouseIntMoveEnabled ? "enabled" : "disabled");
|
||||
printf(" Button: %s\n", byte & mouseIntButtonEnabled ? "enabled" : "disabled");
|
||||
printf(" VBlank: %s\n", byte & mouseIntVBlankEnabled ? "enabled" : "disabled");
|
||||
c->mode = byte;
|
||||
mii->video.vbl_irq = !!(byte & mouseIntVBlankEnabled);
|
||||
}
|
||||
} break;
|
||||
case 4: {// read mouse
|
||||
if (!mii->mouse.enabled)
|
||||
break;
|
||||
uint8_t status = 0;
|
||||
if (mii->mouse.button)
|
||||
status |= 1 << 7;
|
||||
if (c->last.button) {
|
||||
status |= 1 << 6;
|
||||
}
|
||||
if ((mii->mouse.x != c->last.x) || (mii->mouse.y != c->last.y)) {
|
||||
status |= 1 << 5;
|
||||
}
|
||||
mii_bank_poke(main, MOUSE_X_HI + c->slot_offset, mii->mouse.x >> 8);
|
||||
mii_bank_poke(main, MOUSE_Y_HI + c->slot_offset, mii->mouse.y >> 8);
|
||||
mii_bank_poke(main, MOUSE_X_LO + c->slot_offset, mii->mouse.x);
|
||||
mii_bank_poke(main, MOUSE_Y_LO + c->slot_offset, mii->mouse.y);
|
||||
mii_bank_poke(main, MOUSE_STATUS + c->slot_offset, status);
|
||||
// mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0xf); // already in place
|
||||
c->last.x = mii->mouse.x;
|
||||
c->last.y = mii->mouse.y;
|
||||
c->last.button = mii->mouse.button;
|
||||
} break;
|
||||
case 5: // clear mouse
|
||||
break;
|
||||
case 7: // set mouse
|
||||
if (byte == 0) {
|
||||
mii->mouse.min_x = mii_bank_peek(main, CLAMP_MIN_LO) |
|
||||
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
|
||||
mii->mouse.max_x = mii_bank_peek(main, CLAMP_MAX_LO) |
|
||||
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
|
||||
} else if (byte == 1) {
|
||||
mii->mouse.min_y = mii_bank_peek(main, CLAMP_MIN_LO) |
|
||||
(mii_bank_peek(main, CLAMP_MIN_HI) << 8);
|
||||
mii->mouse.max_y = mii_bank_peek(main, CLAMP_MAX_LO) |
|
||||
(mii_bank_peek(main, CLAMP_MAX_HI) << 8);
|
||||
}
|
||||
printf("Mouse clamp to %d,%d - %d,%d\n",
|
||||
mii->mouse.min_x, mii->mouse.min_y,
|
||||
mii->mouse.max_x, mii->mouse.max_y);
|
||||
break;
|
||||
case 8: // home mouse
|
||||
mii->mouse.x = mii->mouse.min_x;
|
||||
mii->mouse.y = mii->mouse.min_y;
|
||||
break;
|
||||
case 0xc: // init mouse
|
||||
mii->mouse.min_x = mii->mouse.min_y = 0;
|
||||
mii->mouse.max_x = mii->mouse.max_y = 1023;
|
||||
mii->mouse.enabled = 0;
|
||||
mii_bank_poke(main, MOUSE_MODE + c->slot_offset, 0x0);
|
||||
break;
|
||||
default:
|
||||
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
|
||||
mii->cpu.PC, addr, byte, write);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "mouse",
|
||||
.desc = "Mouse card",
|
||||
.init = _mii_mouse_init,
|
||||
.access = _mii_mouse_access,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
21
src/drivers/mii_mouse.h
Normal file
21
src/drivers/mii_mouse.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* mii_mouse.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// mins/max are set by the card, x,y,button are set by the UI
|
||||
typedef struct mii_mouse_t {
|
||||
bool enabled; // read only, set by driver
|
||||
uint16_t min_x, max_x,
|
||||
min_y, max_y; // set by driver when enabled
|
||||
uint16_t x, y;
|
||||
bool button;
|
||||
} mii_mouse_t;
|
177
src/drivers/mii_noslotclock.c
Normal file
177
src/drivers/mii_noslotclock.c
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* mii_noslotclock.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
// http://ctrl.pomme.reset.free.fr/index.php/hardware/no-slot-clock-ds1216e/
|
||||
// Disassembly:
|
||||
// https://gist.github.com/mgcaret/ae2860c754fd029d2640107c4fe0bffd
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "minipt.h"
|
||||
|
||||
const uint64_t nsc_pattern = 0x5CA33AC55CA33AC5ULL;
|
||||
|
||||
typedef struct mii_nsc_t {
|
||||
mii_t * mii;
|
||||
uint64_t pattern;
|
||||
void * state; // protothread state
|
||||
int bitcount;
|
||||
int mode; // read or write;
|
||||
} mii_nsc_t;
|
||||
|
||||
static void
|
||||
_mii_nsc_maketime(
|
||||
mii_nsc_t *nsc)
|
||||
{
|
||||
uint64_t ret = 0;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm t;
|
||||
localtime_r(&now, &t);
|
||||
|
||||
int year = t.tm_year % 100;
|
||||
ret = year / 10;
|
||||
ret <<= 4;
|
||||
ret += year % 10;
|
||||
ret <<= 4;
|
||||
|
||||
int month = t.tm_mon + 1;
|
||||
ret += month / 10;
|
||||
ret <<= 4;
|
||||
ret += month % 10;
|
||||
ret <<= 4;
|
||||
|
||||
int day = t.tm_mday;
|
||||
ret += day / 10;
|
||||
ret <<= 4;
|
||||
ret += day % 10;
|
||||
ret <<= 4;
|
||||
|
||||
// Bits 4 and 5 of the day ret are used to control the RST and oscillator
|
||||
// functions. These bits are shipped from the factory set to logic 1.
|
||||
ret += 0x0; //0x3, but zero on read.
|
||||
ret <<= 4;
|
||||
ret += t.tm_wday + 1; // uint64(now.Weekday()) + 1
|
||||
ret <<= 4;
|
||||
|
||||
int hour = t.tm_hour;
|
||||
ret += 0x0; // 0x8 for 24 hour mode, but zero on read.
|
||||
ret += hour / 10;
|
||||
ret <<= 4;
|
||||
ret += hour % 10;
|
||||
ret <<= 4;
|
||||
|
||||
int minute = t.tm_min;
|
||||
ret += minute / 10;
|
||||
ret <<= 4;
|
||||
ret += minute % 10;
|
||||
ret <<= 4;
|
||||
|
||||
int second = t.tm_sec;
|
||||
ret += second / 10;
|
||||
ret <<= 4;
|
||||
ret += second % 10;
|
||||
ret <<= 4;
|
||||
|
||||
int centisecond = 0;
|
||||
ret += centisecond / 10;
|
||||
ret <<= 4;
|
||||
ret += centisecond % 10;
|
||||
nsc->pattern = ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* this is a protothread, so remember no locals that will survive a yield()
|
||||
*/
|
||||
static bool
|
||||
_mii_nsc_access(
|
||||
struct mii_bank_t *bank,
|
||||
void *param,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write)
|
||||
{
|
||||
mii_nsc_t * nsc = param;
|
||||
// printf("%s PC:%04x access %s addr %04x byte %02x write %d\n",
|
||||
// __func__, nsc->mii->cpu.PC,
|
||||
// bank->name, addr, *byte, write);
|
||||
int rd = (addr & 0x4);
|
||||
int bit = addr & 1;
|
||||
bool res = false;
|
||||
pt_start(nsc->state);
|
||||
do {
|
||||
if (rd) {
|
||||
nsc->pattern = 0;
|
||||
} else {
|
||||
nsc->pattern = (nsc->pattern >> 1) | ((uint64_t)bit << 63);
|
||||
int match = nsc->pattern == nsc_pattern;
|
||||
if (match) {
|
||||
// printf("pattern %016lx %s\n", nsc->pattern, match ? "match" : "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
pt_yield(nsc->state);
|
||||
} while (1);
|
||||
nsc->bitcount = 0;
|
||||
pt_yield(nsc->state);
|
||||
nsc->mode = rd;
|
||||
// printf("%s in %s mode\n", __func__, rd ? "read" : "write");
|
||||
if (nsc->mode) {
|
||||
_mii_nsc_maketime(nsc);
|
||||
// printf("time %016lx\n", nsc->pattern);
|
||||
}
|
||||
do {
|
||||
if (nsc->mode) {
|
||||
*byte = (nsc->pattern >> nsc->bitcount) & 1;
|
||||
} else {
|
||||
nsc->pattern |= ((uint64_t)bit << nsc->bitcount);
|
||||
}
|
||||
res = true; // don't call ROM handler
|
||||
pt_yield(nsc->state);
|
||||
nsc->bitcount++;
|
||||
} while (nsc->bitcount < 64 && nsc->mode == rd);
|
||||
// printf("%s done\n", __func__);
|
||||
pt_end(nsc->state);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_nsc_probe(
|
||||
mii_t *mii,
|
||||
uint32_t flags)
|
||||
{
|
||||
printf("%s %s\n", __func__, flags & MII_INIT_NSC ? "enabled" : "disabled");
|
||||
if (!(flags & MII_INIT_NSC))
|
||||
return 0;
|
||||
mii_nsc_t * nsc = calloc(1, sizeof(*nsc));
|
||||
nsc->mii = mii;
|
||||
// This worked fine with NS.CLOCK.SYSTEM but...
|
||||
// mii_bank_install_access_cb(&mii->bank[MII_BANK_CARD_ROM],
|
||||
// _mii_nsc_access, nsc, 0xc1, 0);
|
||||
/* ... A2Desktop requires the NSC to be on the main rom, the source
|
||||
* claims it probe the slots, but in fact, it doesnt */
|
||||
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
|
||||
_mii_nsc_access, nsc, 0xc3, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "nsc",
|
||||
.desc = "No Slot Clock",
|
||||
.enable_flag = MII_INIT_NSC,
|
||||
.probe = _mii_nsc_probe,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
342
src/drivers/mii_smartport.c
Normal file
342
src/drivers/mii_smartport.c
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
* mii_smartport.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* This is shamelessly inspired from:
|
||||
* https:github.com/ct6502/apple2ts/blob/main/src/emulator/harddrivedata.ts
|
||||
* http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.1.html
|
||||
*/
|
||||
#define _GNU_SOURCE // for asprintf
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_dd.h"
|
||||
#include "mii_slot.h"
|
||||
|
||||
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
|
||||
#define INCBIN_PREFIX mii_
|
||||
#include "incbin.h"
|
||||
|
||||
INCBIN(smartport_rom, "test/asm/mii_smartport_driver.bin");
|
||||
|
||||
extern const unsigned char mii_smartport_rom_data[];
|
||||
|
||||
#define MII_SM_DRIVE_COUNT 2
|
||||
|
||||
typedef struct mii_card_sm_t {
|
||||
struct mii_card_sm_t *next;
|
||||
mii_dd_t drive[MII_SM_DRIVE_COUNT];
|
||||
struct mii_slot_t *slot;
|
||||
uint8_t * file;
|
||||
} mii_card_sm_t;
|
||||
|
||||
static void
|
||||
_mii_hd_callback(
|
||||
mii_t *mii,
|
||||
uint8_t trap)
|
||||
{
|
||||
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
|
||||
mii_card_sm_t *c = mii->slot[sid].drv_priv;
|
||||
|
||||
uint8_t command = mii_read_one(mii, 0x42);
|
||||
uint8_t unit = mii_read_one(mii, 0x43);
|
||||
uint16_t buffer = mii_read_word(mii, 0x44);
|
||||
uint16_t blk = mii_read_word(mii, 0x46);
|
||||
|
||||
unit >>= 7; // last bit is the one we want, drive 0/1
|
||||
switch (command) {
|
||||
case 0: { // get status
|
||||
if (!c->drive[unit].file) {
|
||||
mii->cpu.X = mii->cpu.Y = 0;
|
||||
mii->cpu.P.C = 1;
|
||||
} else {
|
||||
int nblocks = (c->drive[unit].file->size + 511) / 512;
|
||||
mii->cpu.X = nblocks & 0xff;
|
||||
mii->cpu.Y = nblocks >> 8;
|
||||
mii->cpu.P.C = 0;
|
||||
}
|
||||
} break;
|
||||
case 1: {// read block
|
||||
if (!c->drive[unit].file) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (blk >= c->drive[unit].file->size / 512) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].write];
|
||||
mii->cpu.P.C = mii_dd_read(
|
||||
&c->drive[unit], bank, buffer, blk, 1) != 0;
|
||||
} break;
|
||||
case 2: {// write block
|
||||
if (!c->drive[unit].file) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (blk >= c->drive[unit].file->size / 512) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
mii_bank_t * bank = &mii->bank[mii->mem[buffer >> 8].read];
|
||||
mii->cpu.P.C = mii_dd_write(
|
||||
&c->drive[unit], bank, buffer, blk, 1) != 0;
|
||||
} break;
|
||||
default: {
|
||||
printf("%s cmd %02x unit %02x buffer %04x blk %04x\n", __func__,
|
||||
command, unit, buffer, blk);
|
||||
printf("*** %s: unhandled command %02x\n", __func__, command);
|
||||
mii->cpu.P.C = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_sm_callback(
|
||||
mii_t *mii,
|
||||
uint8_t trap)
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
int sid = ((mii->cpu.PC >> 8) & 0xf) - 1;
|
||||
mii_card_sm_t *c = mii->slot[sid].drv_priv;
|
||||
|
||||
uint16_t sp = 0x100 + mii->cpu.S + 1;
|
||||
uint16_t call_addr = mii_read_word(mii, sp);
|
||||
uint8_t spCommand = mii_read_one(mii, call_addr + 1);
|
||||
uint16_t spParams = mii_read_word(mii, call_addr + 2);
|
||||
call_addr += 3;
|
||||
mii_write_word(mii, sp, call_addr);
|
||||
|
||||
uint8_t spPCount = mii_read_one(mii, spParams + 0);
|
||||
uint8_t spUnit = mii_read_one(mii, spParams + 1);
|
||||
uint16_t spBuffer = mii_read_word(mii, spParams + 2);
|
||||
|
||||
printf("%s cmd %02x params %04x pcount %d unit %02x buffer %04x\n", __func__,
|
||||
spCommand, spParams, spPCount, spUnit, spBuffer);
|
||||
switch (spCommand) {
|
||||
case 0: { // get status
|
||||
if (spPCount != 3) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
uint8_t status = mii_read_one(mii, spParams + 4);
|
||||
printf("%s: unit %d status %02x \n", __func__, spUnit, status);
|
||||
uint8_t st = 0x80 | 0x40 | 0x20;
|
||||
uint32_t bsize = 0;
|
||||
if (spUnit) spUnit--;
|
||||
if (spUnit < MII_SM_DRIVE_COUNT && c->drive[spUnit].file) {
|
||||
st |= 0x10;
|
||||
bsize = (c->drive[spUnit].file->size + 511) / 512;
|
||||
}
|
||||
if (status == 0) {
|
||||
mii->cpu.P.C = 0;
|
||||
/* Apple IIc reference says this ought to be a status byte,
|
||||
* but practice and A2Desktop says it ought to be a drive
|
||||
* count, so here goes... */
|
||||
// mii_write_one(mii, spBuffer++, st);
|
||||
mii_write_one(mii, spBuffer++, MII_SM_DRIVE_COUNT);
|
||||
mii_write_one(mii, spBuffer++, bsize);
|
||||
mii_write_one(mii, spBuffer++, bsize >> 8);
|
||||
mii_write_one(mii, spBuffer++, bsize >> 16);
|
||||
} else if (status == 3 && spUnit < MII_SM_DRIVE_COUNT) {
|
||||
mii->cpu.P.C = 0;
|
||||
mii_write_one(mii, spBuffer++, st);
|
||||
mii_write_one(mii, spBuffer++, bsize);
|
||||
mii_write_one(mii, spBuffer++, bsize >> 8);
|
||||
mii_write_one(mii, spBuffer++, bsize >> 16);
|
||||
char dname[17] = "\x8MII HD 0 ";
|
||||
dname[8] = '0' + spUnit;
|
||||
for (int i = 0; i < 17; i++)
|
||||
mii_write_one(mii, spBuffer++, dname[i]);
|
||||
mii_write_one(mii, spBuffer++, 0x02); // Profile
|
||||
mii_write_one(mii, spBuffer++, 0x00); // Profile
|
||||
mii_write_one(mii, spBuffer++, 0x01); // Version
|
||||
mii_write_one(mii, spBuffer++, 0x13);
|
||||
} else {
|
||||
printf("%s: unit %d bad status %d\n",
|
||||
__func__, spUnit, status);
|
||||
mii->cpu.P.C = 1;
|
||||
}
|
||||
} break;
|
||||
case 1: { // read
|
||||
if (spPCount != 3) {
|
||||
printf("%s: unit %d bad pcount %d\n",
|
||||
__func__, spUnit, spPCount);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (spUnit >= MII_SM_DRIVE_COUNT) {
|
||||
printf("%s: unit %d out of range\n",
|
||||
__func__, spUnit);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
uint32_t blk = mii_read_word(mii, spParams + 3) |
|
||||
(mii_read_one(mii, spParams + 4) << 16) |
|
||||
(mii_read_one(mii, spParams + 5) << 24);
|
||||
printf("%s read block %x\n", __func__, blk);
|
||||
if (!c->drive[spUnit].file) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (blk >= c->drive[spUnit].file->size / 512) {
|
||||
printf("%s: block %d out of range\n",
|
||||
__func__, blk);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].write];
|
||||
mii->cpu.P.C = mii_dd_read(
|
||||
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
|
||||
// mii->cpu.P.C = 0;
|
||||
} break;
|
||||
case 2: { // write
|
||||
if (spPCount != 3) {
|
||||
printf("%s: unit %d bad pcount %d\n",
|
||||
__func__, spUnit, spPCount);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (spUnit >= MII_SM_DRIVE_COUNT) {
|
||||
printf("%s: unit %d out of range\n",
|
||||
__func__, spUnit);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
uint32_t blk = mii_read_word(mii, spParams + 3) |
|
||||
(mii_read_one(mii, spParams + 4) << 16) |
|
||||
(mii_read_one(mii, spParams + 5) << 24);
|
||||
printf("%s write block %x\n", __func__, blk);
|
||||
if (!c->drive[spUnit].file) {
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
if (blk >= c->drive[spUnit].file->size / 512) {
|
||||
printf("%s: block %d out of range\n",
|
||||
__func__, blk);
|
||||
mii->cpu.P.C = 1;
|
||||
break;
|
||||
}
|
||||
mii_bank_t * bank = &mii->bank[mii->mem[spBuffer >> 8].read];
|
||||
mii->cpu.P.C = mii_dd_write(
|
||||
&c->drive[spUnit], bank, spBuffer, blk, 1) != 0;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_sm_init(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot )
|
||||
{
|
||||
mii_card_sm_t *c = calloc(1, sizeof(*c));
|
||||
c->slot = slot;
|
||||
slot->drv_priv = c;
|
||||
|
||||
printf("%s loading in slot %d\n", __func__, slot->id);
|
||||
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr, mii_smartport_rom_data, 256);
|
||||
|
||||
uint8_t trap_hd = mii_register_trap(mii, _mii_hd_callback);
|
||||
uint8_t trap_sm = mii_register_trap(mii, _mii_sm_callback);
|
||||
printf("%s: traps %02x %02x\n", __func__, trap_hd, trap_sm);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr + 0xd2, &trap_hd, 1);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr + 0xe2, &trap_sm, 1);
|
||||
|
||||
for (int i = 0; i < MII_SM_DRIVE_COUNT; i++) {
|
||||
mii_dd_t *dd = &c->drive[i];
|
||||
dd->slot_id = slot->id + 1;
|
||||
dd->drive = i + 1;
|
||||
dd->slot = slot;
|
||||
asprintf((char **)&dd->name, "SmartPort S:%d D:%d",
|
||||
dd->slot_id, dd->drive);
|
||||
}
|
||||
mii_dd_register_drives(&mii->dd, c->drive, MII_SM_DRIVE_COUNT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_sm_command(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot,
|
||||
uint8_t cmd,
|
||||
void * param)
|
||||
{
|
||||
mii_card_sm_t *c = slot->drv_priv;
|
||||
switch (cmd) {
|
||||
case MII_SLOT_DRIVE_COUNT:
|
||||
if (param)
|
||||
*(int *)param = MII_SM_DRIVE_COUNT;
|
||||
break;
|
||||
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + MII_SM_DRIVE_COUNT - 1:
|
||||
if (param) {
|
||||
int drive = cmd - MII_SLOT_DRIVE_LOAD;
|
||||
const char *filename = param;
|
||||
mii_dd_file_t *file = NULL;
|
||||
if (filename) {
|
||||
file = mii_dd_file_load(&mii->dd, filename, 0);
|
||||
if (!file)
|
||||
return -1;
|
||||
}
|
||||
mii_dd_drive_load(&c->drive[drive], file);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_mii_sm_access(
|
||||
mii_t * mii, struct mii_slot_t *slot,
|
||||
uint16_t addr, uint8_t byte, bool write)
|
||||
{
|
||||
#if 0
|
||||
mii_card_sm_t *c = slot->drv_priv;
|
||||
|
||||
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
|
||||
mii->cpu.PC, addr, byte, write);
|
||||
int psw = addr & 0x0F;
|
||||
if (write) {
|
||||
switch (psw) {
|
||||
case 0:
|
||||
// c->latch = (c->latch & 0xff00) | byte;
|
||||
break;
|
||||
case 1:
|
||||
// c->latch = (c->latch & 0x00ff) | (byte << 8);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// return c->file[(c->latch << 4) + psw];
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "smartport",
|
||||
.desc = "SmartPort card",
|
||||
.init = _mii_sm_init,
|
||||
.access = _mii_sm_access,
|
||||
.command = _mii_sm_command,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
79
src/drivers/mii_titan_iie.c
Normal file
79
src/drivers/mii_titan_iie.c
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* mii_titan_iie.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
|
||||
/*
|
||||
* This is a mini driver for the Titan Accelerator IIe, not very common,
|
||||
* but it's a nice card, and it's a good example of a driver that needs
|
||||
* to use a softswitch ovverride to work.
|
||||
* Also, I own one of these, and none of the other fancy ones, so this one
|
||||
* gets the love.
|
||||
*/
|
||||
static bool
|
||||
_mii_titan_access(
|
||||
struct mii_bank_t *bank,
|
||||
void *param,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write)
|
||||
{
|
||||
mii_t *mii = param;
|
||||
bool res = false;
|
||||
mii_bank_t *main = &mii->bank[MII_BANK_MAIN];
|
||||
if (write) {
|
||||
printf("titan: write %02x to %04x\n", *byte, addr);
|
||||
switch (*byte) {
|
||||
case 5:
|
||||
mii->speed = 3.58;
|
||||
mii_bank_poke(main, 0xc086, *byte);
|
||||
break;
|
||||
case 1:
|
||||
mii_bank_poke(main, 0xc086, *byte);
|
||||
mii->speed = 1;
|
||||
break;
|
||||
case 0xa: // supposed to lock it too...
|
||||
mii_bank_poke(main, 0xc086, *byte);
|
||||
mii->speed = 1;
|
||||
break;
|
||||
default:
|
||||
printf("titan: unknown speed %02x\n", *byte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_titan_probe(
|
||||
mii_t *mii,
|
||||
uint32_t flags)
|
||||
{
|
||||
printf("%s %s\n", __func__, flags & MII_INIT_TITAN ? "enabled" : "disabled");
|
||||
if (!(flags & MII_INIT_TITAN))
|
||||
return 0;
|
||||
// this override a read-only soft switch, but we only handle writes
|
||||
// so it's fine
|
||||
mii_set_sw_override(mii, 0xc086, _mii_titan_access, mii);
|
||||
mii->speed = 3.58;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static mii_slot_drv_t _driver = {
|
||||
.name = "titan",
|
||||
.desc = "Titan Accelerator IIe",
|
||||
.enable_flag = MII_INIT_TITAN,
|
||||
.probe = _mii_titan_probe,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
472
src/format/dsk.c
Normal file
472
src/format/dsk.c
Normal file
@ -0,0 +1,472 @@
|
||||
// format/dsk.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/mman.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
#define NIBBLE_SECTOR_SIZE 416
|
||||
#define NIBBLE_TRACK_SIZE 6656
|
||||
#define DSK_SECTOR_SIZE 256
|
||||
#define MAX_SECTORS 16
|
||||
#define VOLUME_NUMBER 254
|
||||
#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS)
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
struct dskprivdat {
|
||||
const char *path;
|
||||
byte *realbuf;
|
||||
byte *buf;
|
||||
const byte *secmap;
|
||||
int bytenum;
|
||||
uint64_t dirty_tracks;
|
||||
};
|
||||
static const struct dskprivdat datinit = { 0 };
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
static const size_t dsk_disksz = 143360;
|
||||
|
||||
// DOS 3.3 Physical sector order (index is physical sector,
|
||||
// value is DOS sector)
|
||||
const byte DO[] = {
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
|
||||
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
|
||||
// ProDOS Physical sector order (index is physical sector,
|
||||
// value is ProDOS sector).
|
||||
const byte PO[] = {
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
|
||||
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
|
||||
};
|
||||
|
||||
const byte TRANS62[] = {
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
};
|
||||
|
||||
static const int DETRANS62[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, 0x00, 0x01,
|
||||
-1, -1, 0x02, 0x03, -1, 0x04, 0x05, 0x06,
|
||||
-1, -1, -1, -1, -1, -1, 0x07, 0x08,
|
||||
-1, -1, -1, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
||||
-1, -1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
|
||||
-1, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, 0x1B, -1, 0x1C, 0x1D, 0x1E,
|
||||
-1, -1, -1, 0x1F, -1, -1, 0x20, 0x21,
|
||||
-1, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
-1, -1, -1, -1, -1, 0x29, 0x2A, 0x2B,
|
||||
-1, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
-1, -1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
-1, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
||||
};
|
||||
|
||||
static void realign_tracks(DiskFormatDesc *desc)
|
||||
{
|
||||
/*
|
||||
When we unpack a .dsk into nibblized form, it's
|
||||
automatically aligned neatly within our track-storage
|
||||
boundaries within the disk buffer, because that's how
|
||||
we laid 'em out.
|
||||
|
||||
When a sector is written or updated, it will
|
||||
tend to stay aligned, because the program had to
|
||||
find the existing sector to write to, to keep things
|
||||
in order.
|
||||
|
||||
However, in the event of reformatting, there are no
|
||||
guarantees about the track being aligned conveniently.
|
||||
The formatting program can start at any old position
|
||||
and just start writing, because no existing data is going
|
||||
to be preserved.
|
||||
|
||||
To deal with this, we could either:
|
||||
1) Be prepared to loop back around our track's buffer,
|
||||
mid-sector
|
||||
2) Re-align the track at our convenience, to start
|
||||
somewhere that we know for sure can't be the middle of
|
||||
sector data.
|
||||
|
||||
I've opted for option (2). It would be a bad option if
|
||||
we're reading and writing a .nib file, because we'd be
|
||||
unnecessarily altering the file's structure (in cases
|
||||
where it had NOT been reformatted), but it's perfectly
|
||||
fine when we'll be discarding the nibblized format anyway.
|
||||
|
||||
We handle this by seeking forward to the first sector-field
|
||||
start boundary (D5 AA 96) that we can find, and make that
|
||||
the new "start" of our track. It doesn't matter if it's a
|
||||
false-start that doesn't start a real sector-field, because one
|
||||
thing we know for *sure* is that first D5 can't be found
|
||||
in the middle of a legitimate header or data field.
|
||||
*/
|
||||
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
byte *buf = dat->buf;
|
||||
byte *secbuf = malloc(NIBBLE_TRACK_SIZE);
|
||||
for (int t=0; t != NUM_TRACKS; ++t) {
|
||||
byte *tstart = buf + (t * NIBBLE_TRACK_SIZE);
|
||||
byte *tend = buf + ((t + 1) * NIBBLE_TRACK_SIZE);
|
||||
byte *talign;
|
||||
for (talign = tstart; talign <= (tend - 3); ++talign) {
|
||||
if (talign[0] == 0xD5 && talign[1] == 0xAA && talign[2] == 0x96) {
|
||||
if (talign == tstart) {
|
||||
// Nothing to do, already aligned.
|
||||
} else {
|
||||
size_t rollsz = talign - tstart;
|
||||
memcpy(secbuf, tstart, rollsz);
|
||||
memmove(tstart, talign, tend-talign);
|
||||
memcpy(tend - rollsz, secbuf, rollsz);
|
||||
}
|
||||
break; // handle next track
|
||||
}
|
||||
}
|
||||
}
|
||||
free(secbuf);
|
||||
}
|
||||
|
||||
static void implodeDo(DiskFormatDesc *desc)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
|
||||
realign_tracks(desc);
|
||||
|
||||
const byte *rd = dat->buf; // nibble buf
|
||||
const byte *end = dat->buf + nib_disksz;
|
||||
|
||||
bool warned = false;
|
||||
|
||||
for (;;) {
|
||||
// Scan forward for a sector header
|
||||
const int sector_hdr_sz = 11; // counts prologue, but not epilogue
|
||||
for (;;) {
|
||||
// This is the only place we're "allowed" to end processing.
|
||||
if (rd >= (end - sector_hdr_sz)) goto done;
|
||||
if (rd[0] == 0xD5 && rd[1] == 0xAA && rd[2] == 0x96) break;
|
||||
++rd;
|
||||
}
|
||||
|
||||
header:
|
||||
rd += 3;
|
||||
int v = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int t = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int s = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int checkSum = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
|
||||
if (checkSum != (v ^ t ^ s)) {
|
||||
WARN("Sector header checksum failed, t=%d s=%d"
|
||||
" at nibblized byte %zu.\n", t, s, rd - dat->buf);
|
||||
WARN("Probable disk corruption for %s\n", dat->path);
|
||||
}
|
||||
|
||||
byte truet = (rd - dat->buf)/NIBBLE_TRACK_SIZE;
|
||||
if (t != truet) {
|
||||
WARN("Sector header lying about track number"
|
||||
" at nibblized pos %zu\n", (size_t)(rd - dat->buf));
|
||||
WARN(" (says %d but we're on track %d). Skipping sector.\n",
|
||||
(int)t, (int)truet);
|
||||
continue;
|
||||
}
|
||||
if (s >= MAX_SECTORS) {
|
||||
WARN("Sector header sec# too high (%d; max is %d).\n",
|
||||
(int)s, (int)MAX_SECTORS);
|
||||
WARN(" Skipping sector.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
const int data_field_sz = 0x15A; //counts prologue, not epilogue
|
||||
for (;;) {
|
||||
if (rd >= (end - 0x15A)) goto bail;
|
||||
if (rd[0] == 0xD5 && rd[1] == 0xAA) {
|
||||
if (rd[2] == 0x96) goto header;
|
||||
if (rd[2] == 0xAD) break;
|
||||
}
|
||||
++rd;
|
||||
}
|
||||
|
||||
rd += 3;
|
||||
|
||||
// Translate sector data field
|
||||
{
|
||||
int s_ = dat->secmap[s];
|
||||
byte *data = dat->realbuf + (t * DSK_TRACK_SIZE)
|
||||
+ (s_ * DSK_SECTOR_SIZE);
|
||||
byte data2[0x56];
|
||||
byte last = 0;
|
||||
int val;
|
||||
|
||||
if (0) {
|
||||
WARN("Translating track %d, phys sec %d (image sec %d)\n",
|
||||
t, s, s_);
|
||||
WARN(" starting at dsk pos %zu\n", (size_t)(data - dat->realbuf));
|
||||
}
|
||||
|
||||
for (int j = 0x55; j >= 0; --j) {
|
||||
val = DETRANS62[*rd - 0x80];
|
||||
if (val == -1 && !warned) {
|
||||
warned = true;
|
||||
WARN("Untranslatable nibble at (nibblized) pos %zu,"
|
||||
" disk %s.\n", (size_t)(rd - dat->buf), dat->path);
|
||||
if (rd <= end - 4) {
|
||||
WARN("%02X %02X %02X %02X [%02X] %02X %02X %02X\n",
|
||||
rd[-4], rd[-3], rd[-2], rd[-1],
|
||||
rd[0], rd[1], rd[2], rd[3]);
|
||||
}
|
||||
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
|
||||
}
|
||||
val ^= last;
|
||||
data2[j] = val;
|
||||
last = val;
|
||||
++rd;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 0x100; ++j) {
|
||||
val = DETRANS62[*rd - 0x80];
|
||||
if (val == -1 && !warned) {
|
||||
warned = true;
|
||||
WARN("Untranslatable nibble at t=%d, phys s=%d,"
|
||||
" disk %s.\n", t, s, dat->path);
|
||||
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
|
||||
}
|
||||
val ^= last;
|
||||
data[j] = val;
|
||||
last = val;
|
||||
++rd;
|
||||
}
|
||||
|
||||
int checkSum = DETRANS62[*rd++ - 0x80];
|
||||
if (checkSum != -1) checkSum ^= last;
|
||||
if (checkSum != 0) {
|
||||
WARN("Bad sector data checksum at t=%d, phys s=%d,"
|
||||
" disk %s.\n", t, s, dat->path);
|
||||
}
|
||||
|
||||
for (int k = 0, j = 0x55; k < 0x100; ++k) {
|
||||
data[k] <<= 1;
|
||||
if ((data2[j] & 0x1) != 0) {
|
||||
data[k] |= 0x01;
|
||||
}
|
||||
data2[j] >>= 1;
|
||||
|
||||
data[k] <<= 1;
|
||||
if ((data2[j] & 0x01) != 0) {
|
||||
data[k] |= 0x01;
|
||||
}
|
||||
data2[j] >>= 0x01;
|
||||
|
||||
if (--j < 0) j = 0x55;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
WARN("Error translating to dsk: ended mid-sector!\n");
|
||||
WARN("Probable disk corruption for %s\n", dat->path);
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
static void spin(DiskFormatDesc *desc, bool b)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
if (!b && dat->dirty_tracks != 0) {
|
||||
implodeDo(desc);
|
||||
// For now, sync the entire disk
|
||||
errno = 0;
|
||||
int err = msync(dat->realbuf, dsk_disksz, MS_SYNC);
|
||||
if (err < 0) {
|
||||
DIE(1,"Couldn't sync to disk file %s: %s\n",
|
||||
dat->path, strerror(errno));
|
||||
}
|
||||
dat->dirty_tracks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static byte read_byte(DiskFormatDesc *desc)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
byte val = dat->buf[pos];
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void write_byte(DiskFormatDesc *desc, byte val)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
if ((val & 0x80) == 0) {
|
||||
// D2DBG("dodged write $%02X", val);
|
||||
return; // must have high bit
|
||||
}
|
||||
dat->dirty_tracks |= 1 << (desc->halftrack/2);
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
|
||||
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
|
||||
|
||||
dat->buf[pos] = val;
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
|
||||
static void eject(DiskFormatDesc *desc)
|
||||
{
|
||||
// free dat->path and dat, and unmap disk image
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
(void) munmap(dat->buf, dsk_disksz);
|
||||
free((void*)dat->path);
|
||||
free(dat);
|
||||
}
|
||||
|
||||
// This function is derived from Scullin Steel Co.'s apple2js code
|
||||
// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140
|
||||
static void explodeSector(byte vol, byte track, byte sector,
|
||||
byte **nibSec, const byte *data)
|
||||
{
|
||||
byte *wr = *nibSec;
|
||||
unsigned int gap;
|
||||
|
||||
// Gap 1/3 (40/0x28 bytes)
|
||||
|
||||
if (sector == 0) // Gap 1
|
||||
gap = 0x80;
|
||||
else { // Gap 3
|
||||
gap = track == 0? 0x28 : 0x26;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i != gap; ++i) {
|
||||
*wr++ = 0xFF;
|
||||
}
|
||||
|
||||
// Address Field
|
||||
const byte checksum = vol ^ track ^ sector;
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96
|
||||
*wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA;
|
||||
*wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA;
|
||||
*wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA;
|
||||
*wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA;
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
|
||||
// Gap 2 (5 bytes)
|
||||
for (int i = 0; i != 5; ++i) {
|
||||
*wr++ = 0xFF;
|
||||
}
|
||||
|
||||
// Data Field
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD
|
||||
|
||||
byte *nibbles = wr;
|
||||
const unsigned ptr2 = 0;
|
||||
const unsigned ptr6 = 0x56;
|
||||
|
||||
for (int i = 0; i != 0x156; ++i) {
|
||||
nibbles[i] = 0;
|
||||
}
|
||||
|
||||
int i2 = 0x55;
|
||||
for (int i6 = 0x101; i6 >= 0; --i6) {
|
||||
byte val6 = data[i6 % 0x100];
|
||||
byte val2 = nibbles[ptr2 + i2];
|
||||
|
||||
val2 = (val2 << 1) | (val6 & 1);
|
||||
val6 >>= 1;
|
||||
val2 = (val2 << 1) | (val6 & 1);
|
||||
val6 >>= 1;
|
||||
|
||||
nibbles[ptr6 + i6] = val6;
|
||||
nibbles[ptr2 + i2] = val2;
|
||||
|
||||
if (--i2 < 0)
|
||||
i2 = 0x55;
|
||||
}
|
||||
|
||||
byte last = 0;
|
||||
for (int i = 0; i != 0x156; ++i) {
|
||||
const byte val = nibbles[i];
|
||||
nibbles[i] = TRANS62[last ^ val];
|
||||
last = val;
|
||||
}
|
||||
wr += 0x156; // advance write-pointer
|
||||
*wr++ = TRANS62[last];
|
||||
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
|
||||
// Gap 3
|
||||
*wr++ = 0xFF;
|
||||
|
||||
*nibSec = wr;
|
||||
}
|
||||
|
||||
static void explodeDsk(byte *nibbleBuf, byte *dskBuf, const byte *secmap)
|
||||
{
|
||||
for (int t = 0; t < NUM_TRACKS; ++t) {
|
||||
byte *writePtr = nibbleBuf;
|
||||
for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) {
|
||||
const byte dos_sector = secmap[phys_sector];
|
||||
const size_t off = ((MAX_SECTORS * t + dos_sector)
|
||||
* DSK_SECTOR_SIZE);
|
||||
explodeSector(VOLUME_NUMBER, t, phys_sector,
|
||||
&writePtr, &dskBuf[off]);
|
||||
}
|
||||
assert(writePtr - nibbleBuf <= NIBBLE_TRACK_SIZE);
|
||||
for (; writePtr != (nibbleBuf + NIBBLE_TRACK_SIZE); ++writePtr) {
|
||||
*writePtr = 0xFF;
|
||||
}
|
||||
nibbleBuf += NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
DiskFormatDesc dsk_insert(const char *path, byte *buf, size_t sz)
|
||||
{
|
||||
if (sz != dsk_disksz) {
|
||||
DIE(0,"Wrong disk image size for %s:\n", path);
|
||||
DIE(1," Expected %zu, got %zu.\n", dsk_disksz, sz);
|
||||
}
|
||||
|
||||
struct dskprivdat *dat = malloc(sizeof *dat);
|
||||
*dat = datinit;
|
||||
dat->realbuf = buf;
|
||||
dat->path = strdup(path);
|
||||
dat->buf = calloc(1, nib_disksz);
|
||||
|
||||
const char *ext = rindex(path, '.');
|
||||
ext = ext ? ext+1 : "";
|
||||
if (!strcasecmp(ext, "PO")) {
|
||||
INFO("Opening %s as PO.\n", dat->path);
|
||||
dat->secmap = PO;
|
||||
} else {
|
||||
INFO("Opening %s as DO.\n", dat->path);
|
||||
dat->secmap = DO;
|
||||
}
|
||||
explodeDsk(dat->buf, dat->realbuf, dat->secmap);
|
||||
|
||||
return (DiskFormatDesc){
|
||||
.privdat = dat,
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
||||
}
|
36
src/format/empty.c
Normal file
36
src/format/empty.c
Normal file
@ -0,0 +1,36 @@
|
||||
// format/empty.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
void spin(DiskFormatDesc *d, bool b)
|
||||
{
|
||||
}
|
||||
|
||||
byte read_byte(DiskFormatDesc *d)
|
||||
{
|
||||
// A real disk can never send a low byte.
|
||||
// But an empty disk must never send a legitimate byte.
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void write_byte(DiskFormatDesc *d, byte b)
|
||||
{
|
||||
}
|
||||
|
||||
void eject(DiskFormatDesc *d)
|
||||
{
|
||||
}
|
||||
|
||||
DiskFormatDesc empty_disk_desc = {
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
399
src/format/mii_dd.c
Normal file
399
src/format/mii_dd.c
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
* mii_dd.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "mii_bank.h"
|
||||
#include "mii_dd.h"
|
||||
#include "md5.h"
|
||||
|
||||
#ifndef FCC
|
||||
#define FCC(_a,_b,_c,_d) (((_a)<<24)|((_b)<<16)|((_c)<<8)|(_d))
|
||||
#endif
|
||||
|
||||
int
|
||||
mii_dd_overlay_load(
|
||||
mii_dd_t * dd );
|
||||
int
|
||||
mii_dd_overlay_prepare(
|
||||
mii_dd_t * dd );
|
||||
|
||||
|
||||
void
|
||||
mii_dd_system_init(
|
||||
mii_dd_system_t *dd )
|
||||
{
|
||||
dd->drive = NULL;
|
||||
dd->file = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
mii_dd_system_dispose(
|
||||
mii_dd_system_t *dd )
|
||||
{
|
||||
while (dd->file)
|
||||
mii_dd_file_dispose(dd, dd->file);
|
||||
dd->file = NULL;
|
||||
dd->drive = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
mii_dd_register_drives(
|
||||
mii_dd_system_t *dd,
|
||||
mii_dd_t * drives,
|
||||
uint8_t count )
|
||||
{
|
||||
for (int i = 0; i < count; i++) {
|
||||
mii_dd_t *d = &drives[i];
|
||||
d->dd = dd;
|
||||
d->next = dd->drive;
|
||||
dd->drive = d;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mii_dd_file_dispose(
|
||||
mii_dd_system_t *dd,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
// remove it from dd's file queue
|
||||
if (dd->file == file)
|
||||
dd->file = file->next;
|
||||
else {
|
||||
mii_dd_file_t *f = dd->file;
|
||||
while (f) {
|
||||
if (f->next == file) {
|
||||
f->next = file->next;
|
||||
break;
|
||||
}
|
||||
f = f->next;
|
||||
}
|
||||
}
|
||||
if (file->dd) {
|
||||
file->dd->file = NULL;
|
||||
file->dd = NULL;
|
||||
}
|
||||
if (file->fd >= 0) {
|
||||
close(file->fd);
|
||||
file->fd = -1;
|
||||
file->map = NULL;
|
||||
}
|
||||
if (file->map) {
|
||||
free(file->map);
|
||||
file->map = NULL;
|
||||
}
|
||||
if (file->pathname) {
|
||||
free(file->pathname);
|
||||
file->pathname = NULL;
|
||||
}
|
||||
free(file);
|
||||
}
|
||||
|
||||
int
|
||||
mii_dd_drive_load(
|
||||
mii_dd_t *dd,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
if (dd->file == file)
|
||||
return 0;
|
||||
if (dd->file) {
|
||||
printf("%s: %s unloading %s\n", __func__,
|
||||
dd->name,
|
||||
dd->file->pathname);
|
||||
mii_dd_file_dispose(dd->dd, dd->file);
|
||||
dd->file = NULL;
|
||||
}
|
||||
if (!file)
|
||||
return 0;
|
||||
dd->file = file;
|
||||
printf("%s: %s loading %s\n", __func__,
|
||||
dd->name, file->pathname);
|
||||
if (mii_dd_overlay_load(dd) < 0) {
|
||||
printf("%s: No overlay to load, we're fine for now\n", __func__);
|
||||
// no overlay.. what to do?
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
mii_dd_file_t *
|
||||
mii_dd_file_load(
|
||||
mii_dd_system_t *dd,
|
||||
const char *pathname,
|
||||
uint16_t flags)
|
||||
{
|
||||
if (!flags)
|
||||
flags = O_RDONLY;
|
||||
int err;
|
||||
int fd = open(pathname, flags);
|
||||
if (fd < 0) {
|
||||
perror(pathname);
|
||||
return NULL;
|
||||
}
|
||||
struct stat st;
|
||||
err = fstat(fd, &st);
|
||||
if (err < 0) {
|
||||
perror(pathname);
|
||||
goto bail;
|
||||
}
|
||||
int protect = PROT_READ;
|
||||
int mflags = MAP_PRIVATE;
|
||||
if (flags & (O_RDWR | O_WRONLY)) {
|
||||
protect |= PROT_WRITE;
|
||||
mflags = MAP_SHARED;
|
||||
}
|
||||
uint8_t *buf = mmap(NULL, st.st_size, protect, mflags, fd, 0);
|
||||
if (buf == NULL || buf == MAP_FAILED) {
|
||||
perror(pathname);
|
||||
// err = errno;
|
||||
goto bail;
|
||||
}
|
||||
mii_dd_file_t * res = calloc(1, sizeof(*res));
|
||||
res->pathname = strdup(pathname);
|
||||
res->fd = fd;
|
||||
res->map = buf;
|
||||
res->start = buf;
|
||||
res->size = st.st_size;
|
||||
res->dd = NULL;
|
||||
res->next = dd->file;
|
||||
dd->file = res;
|
||||
char *suffix = strrchr(pathname, '.');
|
||||
if (suffix && !strcasecmp(suffix, ".2mg")) {
|
||||
res->format = MII_DD_FILE_2MG;
|
||||
res->map += 64;
|
||||
}
|
||||
return res;
|
||||
bail:
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mii_dd_file_t *
|
||||
mii_dd_file_in_ram(
|
||||
mii_dd_system_t *dd,
|
||||
const char *pathname,
|
||||
uint32_t size,
|
||||
uint16_t flags)
|
||||
{
|
||||
mii_dd_file_t * res = calloc(1, sizeof(*res));
|
||||
res->pathname = strdup(pathname);
|
||||
res->fd = -1;
|
||||
res->map = calloc(1, size);
|
||||
res->start = res->map;
|
||||
res->size = size;
|
||||
res->dd = NULL;
|
||||
res->next = dd->file;
|
||||
dd->file = res;
|
||||
res->format = MII_DD_FILE_RAM;
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
mii_dd_overlay_load(
|
||||
mii_dd_t * dd )
|
||||
{
|
||||
if (dd->overlay.file)
|
||||
return 0;
|
||||
if (!dd->file)
|
||||
return -1;
|
||||
|
||||
char *filename = NULL;
|
||||
char *suffix = strrchr(dd->file->pathname, '.');
|
||||
if (suffix) {
|
||||
asprintf(&filename, "%.*s.miov", (int)(suffix - dd->file->pathname), dd->file->pathname);
|
||||
} else {
|
||||
asprintf(&filename, "%s.miov", dd->file->pathname);
|
||||
}
|
||||
int fd = open(filename, O_RDWR, 0666);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "%s: overlay %s: %s\n", __func__,
|
||||
filename, strerror(errno));
|
||||
free(filename);
|
||||
return -1;
|
||||
}
|
||||
mii_dd_file_t * file = mii_dd_file_load(dd->dd, filename, O_RDWR);
|
||||
close(fd);
|
||||
if (!file)
|
||||
return -1;
|
||||
mii_dd_overlay_header_t * h = (mii_dd_overlay_header_t *)file->start;
|
||||
|
||||
if (h->magic != FCC('M','I','O','V')) {
|
||||
fprintf(stderr, "Overlay file %s has invalid magic\n", filename);
|
||||
mii_dd_file_dispose(dd->dd, file);
|
||||
return -1;
|
||||
}
|
||||
if (h->version != 1) {
|
||||
fprintf(stderr, "Overlay file %s has invalid version\n", filename);
|
||||
mii_dd_file_dispose(dd->dd, file);
|
||||
return -1;
|
||||
}
|
||||
if (h->size != dd->file->size / 512) {
|
||||
fprintf(stderr, "Overlay file %s has invalid size\n", filename);
|
||||
mii_dd_file_dispose(dd->dd, file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MD5_CTX d5 = {};
|
||||
MD5_Init(&d5);
|
||||
MD5_Update(&d5, dd->file->start, dd->file->size);
|
||||
uint8_t md5[16];
|
||||
MD5_Final(md5, &d5);
|
||||
|
||||
if (memcmp(md5, h->src_md5, 16)) {
|
||||
fprintf(stderr, "Overlay file %s has mismatched HASH!\n", filename);
|
||||
mii_dd_file_dispose(dd->dd, file);
|
||||
return -1;
|
||||
}
|
||||
uint32_t bitmap_size = (h->size + 63) / 64;
|
||||
dd->overlay.file = file;
|
||||
dd->overlay.file->map += sizeof(*h) + bitmap_size;
|
||||
dd->overlay.header = (mii_dd_overlay_header_t *)dd->overlay.file->start;
|
||||
dd->overlay.bitmap = (uint64_t*)(dd->overlay.file->start + sizeof(*h));
|
||||
dd->overlay.blocks = dd->overlay.file->map;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_dd_overlay_prepare(
|
||||
mii_dd_t * dd )
|
||||
{
|
||||
if (dd->overlay.file)
|
||||
return 0;
|
||||
if (!dd->file)
|
||||
return -1;
|
||||
printf("%s: %s Preparing Overlay file\n", __func__, dd->name);
|
||||
uint32_t src_blocks = dd->file->size / 512;
|
||||
uint32_t bitmap_size = (src_blocks + 63) / 64;
|
||||
uint32_t blocks_size = src_blocks * 512;
|
||||
uint32_t size = sizeof(mii_dd_overlay_header_t) + bitmap_size + blocks_size;
|
||||
|
||||
char *filename = NULL;
|
||||
char *suffix = strrchr(dd->file->pathname, '.');
|
||||
if (suffix) {
|
||||
asprintf(&filename, "%.*s.miov", (int)(suffix - dd->file->pathname), dd->file->pathname);
|
||||
} else {
|
||||
asprintf(&filename, "%s.miov", dd->file->pathname);
|
||||
}
|
||||
int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "%s: Failed to create overlay file %s: %s\n", __func__,
|
||||
filename, strerror(errno));
|
||||
fprintf(stderr, "%s: Allocating a RAM one, lost on quit!\n", __func__);
|
||||
dd->overlay.file = mii_dd_file_in_ram(dd->dd, filename, size, O_RDWR);
|
||||
} else {
|
||||
ftruncate(fd, size);
|
||||
dd->overlay.file = mii_dd_file_load(dd->dd, filename, O_RDWR);
|
||||
}
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
free(filename);
|
||||
mii_dd_overlay_header_t h = {
|
||||
.magic = FCC('M','I','O','V'),
|
||||
.version = 1,
|
||||
.size = src_blocks,
|
||||
};
|
||||
// hash the whole of the file, including header
|
||||
MD5_CTX d5 = {};
|
||||
MD5_Init(&d5);
|
||||
MD5_Update(&d5, dd->file->start, dd->file->size);
|
||||
MD5_Final(h.src_md5, &d5);
|
||||
*((mii_dd_overlay_header_t *)dd->overlay.file->start) = h;
|
||||
dd->overlay.file->map += sizeof(h) + bitmap_size;
|
||||
dd->overlay.header = (mii_dd_overlay_header_t *)dd->overlay.file->start;
|
||||
dd->overlay.bitmap = (uint64_t*)(dd->overlay.file->start + sizeof(h));
|
||||
dd->overlay.blocks = dd->overlay.file->map;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_dd_read(
|
||||
mii_dd_t * dd,
|
||||
struct mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint32_t blk,
|
||||
uint16_t blockcount)
|
||||
{
|
||||
if (!dd || !dd->file || !dd->file->map)
|
||||
return -1;
|
||||
|
||||
// printf("%s: %s read %d blocks at %d\n",
|
||||
// __func__, dd->name, blockcount, blk);
|
||||
if (dd->overlay.file) {
|
||||
uint64_t *bitmap = dd->overlay.bitmap;
|
||||
for (int i = 0; i < blockcount; i++) {
|
||||
uint32_t b = blk + i;
|
||||
// printf(" overlay block %4d : %016llx\n", b,
|
||||
// (unsigned long long)be64toh(bitmap[b/64]));
|
||||
if (b >= dd->overlay.header->size)
|
||||
break;
|
||||
if (be64toh(bitmap[b / 64]) & (1ULL << (63 - (b & 63)))) {
|
||||
// printf("%s: reading %4d from overlay\n", __func__, b);
|
||||
mii_bank_write( bank,
|
||||
addr + (i * 512),
|
||||
dd->overlay.blocks + (b * 512),
|
||||
512);
|
||||
} else {
|
||||
mii_bank_write( bank,
|
||||
addr + (i * 512),
|
||||
dd->file->map + (b * 512),
|
||||
512);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mii_bank_write(
|
||||
bank,
|
||||
addr, dd->file->map + blk * 512,
|
||||
blockcount * 512);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_dd_write(
|
||||
mii_dd_t * dd,
|
||||
struct mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint32_t blk,
|
||||
uint16_t blockcount)
|
||||
{
|
||||
if (!dd || !dd->file || !dd->file->map)
|
||||
return -1;
|
||||
// printf("%s: %s write %d blocks at %d\n",
|
||||
// __func__, dd->name, blockcount, blk);
|
||||
mii_dd_overlay_prepare(dd);
|
||||
if (dd->overlay.file) {
|
||||
uint64_t *bitmap = dd->overlay.bitmap;
|
||||
for (int i = 0; i < blockcount; i++) {
|
||||
uint32_t b = blk + i;
|
||||
if (b >= dd->overlay.header->size)
|
||||
break;
|
||||
bitmap[b / 64] = htobe64(
|
||||
be64toh(bitmap[b/64]) | (1ULL << (63 - (b & 63))));
|
||||
// printf("%s: writing %d to overlay map: %016llx\n", __func__, b,
|
||||
// (unsigned long long)be64toh(bitmap[b/64]));
|
||||
}
|
||||
mii_bank_read(
|
||||
bank,
|
||||
addr, dd->overlay.blocks + blk * 512,
|
||||
blockcount * 512);
|
||||
} else {
|
||||
mii_bank_read(
|
||||
bank,
|
||||
addr, dd->file->map + blk * 512,
|
||||
blockcount * 512);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
124
src/format/mii_dd.h
Normal file
124
src/format/mii_dd.h
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* mii_dd.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct mii_dd_t;
|
||||
|
||||
enum {
|
||||
MII_DD_FILE_OVERLAY = 1,
|
||||
MII_DD_FILE_RAM,
|
||||
MII_DD_FILE_2MG = 5,
|
||||
};
|
||||
|
||||
// a disk image file (or chunck of ram, if ramdisk is used)
|
||||
typedef struct mii_dd_file_t {
|
||||
struct mii_dd_file_t *next;
|
||||
char * pathname;
|
||||
uint8_t format;
|
||||
uint8_t * start; // start of the file
|
||||
uint8_t * map; // start of the blocks
|
||||
|
||||
int fd; // if fd >= 0, map is mmaped, otherwise it's malloced
|
||||
uint32_t size;
|
||||
struct mii_dd_t * dd;
|
||||
} mii_dd_file_t;
|
||||
|
||||
/*
|
||||
* Overlays are made to provide what looks like read/write on files, but
|
||||
* without commiting any of the changes to the real primary file, instead
|
||||
* alld the changed blocks are kept into a sparse file and reloaded when
|
||||
* the 'real' block is read.
|
||||
* That way you can keep your disk images fresh and clean, while having
|
||||
* multiple version of them if you like.
|
||||
*/
|
||||
typedef union mii_dd_overlay_header_t {
|
||||
struct {
|
||||
uint32_t magic; // 'MIOV'
|
||||
uint32_t version; // 1 for now
|
||||
uint32_t flags; // unused for now
|
||||
uint32_t size; // size in blocks of original file
|
||||
uint8_t src_md5[16]; // md5 of the SOURCE disk
|
||||
};
|
||||
uint32_t raw[16];
|
||||
} mii_dd_overlay_header_t;
|
||||
|
||||
typedef struct mii_dd_overlay_t {
|
||||
mii_dd_overlay_header_t *header; // points to the file mapped in memory
|
||||
uint64_t * bitmap; // usage bitmap
|
||||
uint8_t * blocks; // raw block data
|
||||
mii_dd_file_t *file; // overlay file mapping
|
||||
} mii_dd_overlay_t;
|
||||
|
||||
struct mii_slot_t;
|
||||
struct mii_dd_system_t;
|
||||
|
||||
// a disk drive, with a slot, a drive number, and a file
|
||||
typedef struct mii_dd_t {
|
||||
struct mii_dd_t *next;
|
||||
struct mii_dd_system_t *dd;
|
||||
const char * name; // ie "Disk ][ D:2"
|
||||
uint8_t slot_id : 4, drive : 4;
|
||||
struct mii_slot_t *slot;
|
||||
unsigned int ro : 1, wp : 1, can_eject : 1;
|
||||
mii_dd_file_t * file;
|
||||
mii_dd_overlay_t overlay;
|
||||
} mii_dd_t;
|
||||
|
||||
typedef struct mii_dd_system_t {
|
||||
mii_dd_t * drive; // list of all drives on all slots
|
||||
mii_dd_file_t * file; // list of all open files (inc overlays)
|
||||
} mii_dd_system_t;
|
||||
|
||||
/*
|
||||
* register drives with the system -- these are not allocated, they are
|
||||
* statically defined in the driver code in their own structures
|
||||
*/
|
||||
void
|
||||
mii_dd_register_drives(
|
||||
mii_dd_system_t *dd,
|
||||
mii_dd_t * drives,
|
||||
uint8_t count );
|
||||
int
|
||||
mii_dd_drive_load(
|
||||
mii_dd_t *dd,
|
||||
mii_dd_file_t *file );
|
||||
|
||||
/*
|
||||
* unmap, close and dispose of 'file', clear it from the drive, if any
|
||||
*/
|
||||
void
|
||||
mii_dd_file_dispose(
|
||||
mii_dd_system_t *dd,
|
||||
mii_dd_file_t *file );
|
||||
mii_dd_file_t *
|
||||
mii_dd_file_load(
|
||||
mii_dd_system_t *dd,
|
||||
const char *filename,
|
||||
uint16_t flags);
|
||||
|
||||
struct mii_bank_t;
|
||||
// read blocks from blk into bank's address 'addr'
|
||||
int
|
||||
mii_dd_read(
|
||||
mii_dd_t * dd,
|
||||
struct mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint32_t blk,
|
||||
uint16_t blockcount);
|
||||
|
||||
int
|
||||
mii_dd_write(
|
||||
mii_dd_t * dd,
|
||||
struct mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint32_t blk,
|
||||
uint16_t blockcount);
|
||||
|
95
src/format/mii_disk_format.c
Normal file
95
src/format/mii_disk_format.c
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* mii_disk_format.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
static const size_t dsk_disksz = 143360;
|
||||
|
||||
extern DiskFormatDesc nib_insert(const char*, byte *, size_t);
|
||||
extern DiskFormatDesc dsk_insert(const char *, byte *, size_t);
|
||||
extern DiskFormatDesc empty_disk_desc;
|
||||
|
||||
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags);
|
||||
|
||||
DiskFormatDesc disk_format_load(const char *path)
|
||||
{
|
||||
if (path == NULL) {
|
||||
return empty_disk_desc;
|
||||
}
|
||||
byte *buf;
|
||||
size_t sz;
|
||||
int err = mmapfile(path, &buf, &sz, O_RDWR);
|
||||
if (buf == NULL) {
|
||||
DIE(1,"Couldn't load/mmap disk %s: %s\n",
|
||||
path, strerror(err));
|
||||
}
|
||||
printf("%s loaded %s dz = %d\n", __func__, path, (int)sz);
|
||||
if (sz == nib_disksz) {
|
||||
return nib_insert(path, buf, sz);
|
||||
} else if (sz == dsk_disksz) {
|
||||
return dsk_insert(path, buf, sz);
|
||||
} else {
|
||||
DIE(2,"Unrecognized disk format for %s.\n", path);
|
||||
}
|
||||
}
|
||||
|
||||
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags)
|
||||
{
|
||||
int err;
|
||||
int fd;
|
||||
|
||||
*buf = NULL;
|
||||
|
||||
errno = 0;
|
||||
fd = open(fname, flags);
|
||||
if (fd < 0) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
errno = 0;
|
||||
err = fstat(fd, &st);
|
||||
if (err < 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
int protect = PROT_READ;
|
||||
int mflags = MAP_PRIVATE;
|
||||
if (flags & O_RDWR || flags & O_WRONLY) {
|
||||
protect |= PROT_WRITE;
|
||||
mflags = MAP_SHARED;
|
||||
}
|
||||
*buf = mmap(NULL, st.st_size, protect, mflags, fd, 0);
|
||||
if (*buf == NULL) {
|
||||
err = errno;
|
||||
goto bail;
|
||||
}
|
||||
close(fd); // safe to close now.
|
||||
|
||||
*sz = st.st_size;
|
||||
return 0;
|
||||
bail:
|
||||
close(fd);
|
||||
return err;
|
||||
}
|
35
src/format/mii_disk_format.h
Normal file
35
src/format/mii_disk_format.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* mii_disk_format.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/********** FORMATS **********/
|
||||
#define NUM_TRACKS 35
|
||||
#define SECTOR_SIZE 256
|
||||
typedef struct DiskFormatDesc DiskFormatDesc;
|
||||
typedef struct DiskFormatDesc {
|
||||
void *privdat;
|
||||
bool writeprot;
|
||||
unsigned int halftrack;
|
||||
void (*spin)(DiskFormatDesc *, bool);
|
||||
uint8_t (*read_byte)(DiskFormatDesc *);
|
||||
void (*write_byte)(DiskFormatDesc *, uint8_t);
|
||||
void (*eject)(DiskFormatDesc *);
|
||||
} DiskFormatDesc;
|
||||
|
||||
DiskFormatDesc disk_format_load(const char *path);
|
||||
|
||||
#define WARN(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define INFO(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DIE(code, ...) do { \
|
||||
WARN(__VA_ARGS__); \
|
||||
if (code) exit(code); \
|
||||
} while(0)
|
100
src/format/nib.c
Normal file
100
src/format/nib.c
Normal file
@ -0,0 +1,100 @@
|
||||
// format/nib.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
#define NIBBLE_TRACK_SIZE 6656
|
||||
#define NIBBLE_SECTOR_SIZE 416
|
||||
#define MAX_SECTORS 16
|
||||
|
||||
struct nibprivdat {
|
||||
const char *path;
|
||||
byte *buf;
|
||||
int bytenum;
|
||||
uint64_t dirty_tracks;
|
||||
};
|
||||
static const struct nibprivdat datinit = { 0 };
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
|
||||
static void spin(DiskFormatDesc *desc, bool b)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
if (!b && dat->dirty_tracks != 0) {
|
||||
// For now, sync the entire disk
|
||||
errno = 0;
|
||||
int err = msync(dat->buf, nib_disksz, MS_SYNC);
|
||||
if (err < 0) {
|
||||
DIE(1,"Couldn't sync to disk file %s: %s\n",
|
||||
dat->path, strerror(errno));
|
||||
}
|
||||
dat->dirty_tracks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static byte read_byte(DiskFormatDesc *desc)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
byte val = dat->buf[pos];
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void write_byte(DiskFormatDesc *desc, byte val)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
if ((val & 0x80) == 0) {
|
||||
// D2DBG("dodged write $%02X", val);
|
||||
return; // must have high bit
|
||||
}
|
||||
dat->dirty_tracks |= 1 << (desc->halftrack/2);
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
|
||||
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
|
||||
|
||||
dat->buf[pos] = val;
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
|
||||
static void eject(DiskFormatDesc *desc)
|
||||
{
|
||||
// free dat->path and dat, and unmap disk image
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
(void) munmap(dat->buf, nib_disksz);
|
||||
free((void*)dat->path);
|
||||
free(dat);
|
||||
}
|
||||
|
||||
DiskFormatDesc nib_insert(const char *path, byte *buf, size_t sz)
|
||||
{
|
||||
if (sz != nib_disksz) {
|
||||
DIE(0,"Wrong disk image size for %s:\n", path);
|
||||
DIE(1," Expected %zu, got %zu.\n", nib_disksz, sz);
|
||||
}
|
||||
|
||||
struct nibprivdat *dat = malloc(sizeof *dat);
|
||||
*dat = datinit;
|
||||
dat->buf = buf;
|
||||
dat->path = strdup(path);
|
||||
|
||||
return (DiskFormatDesc){
|
||||
.privdat = dat,
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
||||
}
|
714
src/mii.c
Normal file
714
src/mii.c
Normal file
@ -0,0 +1,714 @@
|
||||
/*
|
||||
* mii.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii_rom_iiee.h"
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_video.h"
|
||||
#include "mii_sw.h"
|
||||
#include "mii_65c02.h"
|
||||
#include "minipt.h"
|
||||
|
||||
|
||||
mii_slot_drv_t * mii_slot_drv_list = NULL;
|
||||
|
||||
static const mii_bank_t _mii_banks_init[MII_BANK_COUNT] = {
|
||||
[MII_BANK_MAIN] = {
|
||||
.name = "MAIN",
|
||||
.base = 0x0000,
|
||||
.size = 0xd0, // 208 pages, 48KB
|
||||
},
|
||||
[MII_BANK_BSR] = {
|
||||
.name = "BSR",
|
||||
.base = 0xd000,
|
||||
.size = 64,
|
||||
},
|
||||
[MII_BANK_BSR_P2] = {
|
||||
.name = "BSR P2",
|
||||
.base = 0xd000,
|
||||
.size = 16,
|
||||
},
|
||||
[MII_BANK_AUX] = {
|
||||
.name = "AUX",
|
||||
.base = 0x0000,
|
||||
.size = 0xd0, // 208 pages, 48KB
|
||||
},
|
||||
[MII_BANK_AUX_BSR] = {
|
||||
.name = "AUX BSR",
|
||||
.base = 0xd000,
|
||||
.size = 64,
|
||||
},
|
||||
[MII_BANK_AUX_BSR_P2] = {
|
||||
.name = "AUX BSR P2",
|
||||
.base = 0xd000,
|
||||
.size = 16,
|
||||
},
|
||||
[MII_BANK_ROM] = {
|
||||
.name = "ROM",
|
||||
.base = 0xc000,
|
||||
.size = 0x40, // 64 pages, 16KB
|
||||
.ro = 1,
|
||||
},
|
||||
[MII_BANK_CARD_ROM] = {
|
||||
.name = "CARD ROM",
|
||||
.base = 0xc100,
|
||||
.size = 15,
|
||||
.ro = 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#include "mii_65c02_ops.h"
|
||||
#include "mii_65c02_disasm.h"
|
||||
|
||||
void
|
||||
mii_dump_trace_state(
|
||||
mii_t *mii)
|
||||
{
|
||||
mii_cpu_t * cpu = &mii->cpu;
|
||||
mii_cpu_state_t s = mii->cpu_state;
|
||||
printf("PC:%04X A:%02X X:%02X Y:%02X S:%02x #%d %c AD:%04X D:%02x %c ",
|
||||
cpu->PC, cpu->A, cpu->X, cpu->Y, cpu->S, cpu->cycle,
|
||||
s.sync ? 'I' : ' ', s.addr, s.data, s.w ? 'W' : 'R');
|
||||
// display the S flags
|
||||
static const char *s_flags = "CZIDBRVN";
|
||||
for (int i = 0; i < 8; i++)
|
||||
printf("%c", cpu->P.P[7-i] ? s_flags[7-i] : tolower(s_flags[7-i]));
|
||||
if (s.sync) {
|
||||
uint8_t op[16];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
mii_mem_access(mii, mii->cpu.PC + i, op + i, false, false);
|
||||
}
|
||||
mii_op_t d = mii_cpu_op[op[0]];
|
||||
printf(" ");
|
||||
char dis[32];
|
||||
mii_cpu_disasm_one(op, cpu->PC, dis, sizeof(dis),
|
||||
MII_DUMP_DIS_DUMP_HEX);
|
||||
printf(": %s", dis);
|
||||
if (d.desc.branch) {
|
||||
if (cpu->P.P[d.desc.s_bit] == d.desc.s_bit_value)
|
||||
printf(" ; taken");
|
||||
}
|
||||
printf("\n");
|
||||
} else
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void
|
||||
mii_dump_run_trace(
|
||||
mii_t *mii)
|
||||
{
|
||||
// walk all the previous PC values in mii->trace, and display a line
|
||||
// of disassebly for all of them
|
||||
for (int li = 0; li < MII_PC_LOG_SIZE; li++) {
|
||||
int idx = (mii->trace.idx + li) & (MII_PC_LOG_SIZE - 1);
|
||||
uint16_t pc = mii->trace.log[idx];
|
||||
uint8_t op[16];
|
||||
for (int i = 0; i < 4; i++)
|
||||
mii_mem_access(mii, pc + i, op + i, false, false);
|
||||
// mii_op_t d = mii_cpu_op[op[0]];
|
||||
char dis[64];
|
||||
mii_cpu_disasm_one(op, pc, dis, sizeof(dis),
|
||||
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
|
||||
printf("%s\n", dis);
|
||||
}
|
||||
}
|
||||
|
||||
#define _SAME 0xf
|
||||
|
||||
static inline void
|
||||
mii_page_set(
|
||||
mii_t * mii,
|
||||
uint8_t read,
|
||||
uint8_t write,
|
||||
uint8_t bank,
|
||||
uint8_t end )
|
||||
{
|
||||
for (int i = bank; i <= end; i++) {
|
||||
if (read != _SAME)
|
||||
mii->mem[i].read = read;
|
||||
if (write != _SAME)
|
||||
mii->mem[i].write = write;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
mii_sw(
|
||||
mii_t *mii,
|
||||
uint16_t sw)
|
||||
{
|
||||
return mii_bank_peek(&mii->bank[MII_BANK_MAIN], sw);
|
||||
}
|
||||
|
||||
static void
|
||||
mii_page_table_update(
|
||||
mii_t *mii)
|
||||
{
|
||||
if (!mii->mem_dirty)
|
||||
return;
|
||||
mii->mem_dirty = 0;
|
||||
int altzp = mii_sw(mii, SWALTPZ);
|
||||
int page2 = mii_sw(mii, SWPAGE2);
|
||||
int store80 = mii_sw(mii, SW80STORE);
|
||||
int hires = mii_sw(mii, SWHIRES);
|
||||
int ramrd = mii_sw(mii, SWRAMRD);
|
||||
int ramwrt = mii_sw(mii, SWRAMWRT);
|
||||
int intcxrom = mii_sw(mii, SWINTCXROM);
|
||||
int slotc3rom = mii_sw(mii, SWSLOTC3ROM);
|
||||
|
||||
if (mii->trace_cpu)
|
||||
printf("%04x: page table update altzp:%02x page2:%02x store80:%02x hires:%02x ramrd:%02x ramwrt:%02x intcxrom:%02x slotc3rom:%02x\n",
|
||||
mii->cpu.PC,
|
||||
altzp, page2, store80, hires, ramrd, ramwrt, intcxrom, slotc3rom);
|
||||
// clean slate
|
||||
mii_page_set(mii, MII_BANK_MAIN, MII_BANK_MAIN, 0x00, 0xc0);
|
||||
mii_page_set(mii, MII_BANK_ROM, MII_BANK_ROM, 0xc1, 0xff);
|
||||
if (altzp)
|
||||
mii_page_set(mii, MII_BANK_AUX, MII_BANK_AUX, 0x00, 0x01);
|
||||
mii_page_set(mii,
|
||||
ramrd ? MII_BANK_AUX : MII_BANK_MAIN,
|
||||
ramwrt ? MII_BANK_AUX : MII_BANK_MAIN, 0x02, 0xbf);
|
||||
if (store80) {
|
||||
mii_page_set(mii,
|
||||
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
|
||||
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x04, 0x07);
|
||||
if (hires)
|
||||
mii_page_set(mii,
|
||||
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
|
||||
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x20, 0x3f);
|
||||
}
|
||||
if (!intcxrom)
|
||||
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc1, 0xc7);
|
||||
mii_page_set(mii,
|
||||
slotc3rom ? MII_BANK_CARD_ROM : MII_BANK_ROM, _SAME, 0xc3, 0xc3);
|
||||
mii_page_set(mii,
|
||||
mii->bsr_mode.read ?
|
||||
altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR :
|
||||
MII_BANK_ROM,
|
||||
mii->bsr_mode.write ?
|
||||
altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR :
|
||||
MII_BANK_ROM,
|
||||
0xd0, 0xff);
|
||||
// BSR P2
|
||||
mii_page_set(mii,
|
||||
mii->bsr_mode.read ?
|
||||
(altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR) +
|
||||
mii->bsr_mode.page2 : MII_BANK_ROM,
|
||||
mii->bsr_mode.write ?
|
||||
(altzp ? MII_BANK_AUX_BSR : MII_BANK_BSR) +
|
||||
mii->bsr_mode.page2 : MII_BANK_ROM,
|
||||
0xd0, 0xdf);
|
||||
}
|
||||
|
||||
void
|
||||
mii_set_sw_override(
|
||||
mii_t *mii,
|
||||
uint16_t sw_addr,
|
||||
mii_bank_access_cb cb,
|
||||
void *param)
|
||||
{
|
||||
if (!mii->soft_switches_override)
|
||||
mii->soft_switches_override = calloc(256,
|
||||
sizeof(*mii->soft_switches_override));
|
||||
sw_addr &= 0xff;
|
||||
mii->soft_switches_override[sw_addr].cb = cb;
|
||||
mii->soft_switches_override[sw_addr].param = param;
|
||||
}
|
||||
|
||||
static bool
|
||||
mii_access_soft_switches(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write)
|
||||
{
|
||||
if (!(addr >= 0xc000 && addr <= 0xc0ff) || addr == 0xcfff)
|
||||
return false;
|
||||
bool res = false;
|
||||
uint8_t on = 0;
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
|
||||
/*
|
||||
* This allows driver (titan accelerator etc) to have their own
|
||||
* soft switches, and override/supplement any default ones.
|
||||
*/
|
||||
if (mii->soft_switches_override && mii->soft_switches_override[addr & 0xff].cb) {
|
||||
res = mii->soft_switches_override[addr & 0xff].cb(
|
||||
main, mii->soft_switches_override[addr & 0xff].param,
|
||||
addr, byte, write);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
switch (addr) {
|
||||
case 0xc090 ... 0xc0ff: {
|
||||
res = true;
|
||||
int slot = ((addr >> 4) & 7) - 1;
|
||||
#if 0
|
||||
printf("SLOT %d addr %04x write %d %02x drv %s\n",
|
||||
slot, addr, write, *byte,
|
||||
mii->slot[slot].drv ? mii->slot[slot].drv->name : "none");
|
||||
#endif
|
||||
if (mii->slot[slot].drv) {
|
||||
on = mii->slot[slot].drv->access(mii,
|
||||
&mii->slot[slot], addr, *byte, write);
|
||||
if (!write)
|
||||
*byte = on;
|
||||
}
|
||||
} break;
|
||||
case 0xc080 ... 0xc08f: {
|
||||
res = true;
|
||||
uint8_t mode = addr & 0x0f;
|
||||
static const int write_modes[4] = { 0, 1, 0, 1, };
|
||||
static const int read_modes[4] = { 1, 0, 0, 1, };
|
||||
uint8_t rd = read_modes[mode & 3];
|
||||
uint8_t wr = write_modes[mode & 3];
|
||||
mii->bsr_mode.write = wr;
|
||||
mii->bsr_mode.read = rd;
|
||||
mii->bsr_mode.page2 = mode & 0x08 ? 0 : 1;
|
||||
mii->mem_dirty = 1;
|
||||
if (mii->trace_cpu)
|
||||
printf("%04x: BSR mode addr %04x:%02x read:%s write:%s %s altzp:%02x\n",
|
||||
mii->cpu.PC, addr,
|
||||
mode,
|
||||
rd ? "BSR" : "ROM",
|
||||
wr ? "BSR" : "ROM",
|
||||
mii->bsr_mode.page2 ? "page2" : "page1",
|
||||
mii_sw(mii, SWALTPZ));
|
||||
} break;
|
||||
case 0xcfff:
|
||||
res = true;
|
||||
mii->mem_dirty = 1;
|
||||
printf("%s TODO reset SLOT roms\n", __func__);
|
||||
break;
|
||||
case SWPAGE2OFF:
|
||||
case SWPAGE2ON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWPAGE2, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWHIRESOFF:
|
||||
case SWHIRESON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWHIRES, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
// printf("HIRES %s\n", (addr & 1) ? "ON" : "OFF");
|
||||
break;
|
||||
case SWSPEAKER:
|
||||
res = true;
|
||||
mii_speaker_click(&mii->speaker);
|
||||
break;
|
||||
case 0xc068:
|
||||
res = true;
|
||||
// IIgs register, read by prodos tho
|
||||
break;
|
||||
}
|
||||
if (res && !mii->mem_dirty)
|
||||
return res;
|
||||
if (write) {
|
||||
switch (addr) {
|
||||
case SW80STOREOFF:
|
||||
case SW80STOREON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SW80STORE, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWRAMRDOFF:
|
||||
case SWRAMRDON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWRAMRD, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWRAMWRTOFF:
|
||||
case SWRAMWRTON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWRAMWRT, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWALTPZOFF:
|
||||
case SWALTPZON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWALTPZ, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWINTCXROMOFF:
|
||||
case SWINTCXROMON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWINTCXROM, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
case SWSLOTC3ROMOFF:
|
||||
case SWSLOTC3ROMON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWSLOTC3ROM, (addr & 1) << 7);
|
||||
mii->mem_dirty = 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (addr) {
|
||||
case SWBSRBANK2:
|
||||
*byte = mii->bsr_mode.page2 ? 0x80 : 0;
|
||||
res = true;
|
||||
break;
|
||||
case SWBSRREADRAM:
|
||||
*byte = mii->bsr_mode.read ? 0x80 : 0;
|
||||
res = true;
|
||||
break;
|
||||
case SWRAMRD:
|
||||
case SWRAMWRT:
|
||||
case SW80STORE:
|
||||
case SWINTCXROM:
|
||||
case SWALTPZ:
|
||||
case SWSLOTC3ROM:
|
||||
res = true;
|
||||
*byte = mii_bank_peek(main, addr);
|
||||
break;
|
||||
case 0xc020: // toggle TAPE output ?!?!
|
||||
case 0xc064: // Analog Input 0 (paddle 0)
|
||||
case 0xc065: // Analog Input 1 (paddle 1)
|
||||
case 0xc079: // Analog Input Reset
|
||||
res = true;
|
||||
break;
|
||||
case 0xc068:
|
||||
res = true;
|
||||
// IIgs register, read by prodos tho
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!res) {
|
||||
// printf("%s addr %04x write %d %02x\n", __func__, addr, write, *byte);
|
||||
// mii->state = MII_STOPPED;
|
||||
}
|
||||
mii_page_table_update(mii);
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool
|
||||
mii_access_keyboard(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write)
|
||||
{
|
||||
bool res = false;
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
switch (addr) {
|
||||
case SWKBD:
|
||||
if (!write) {
|
||||
/* If fifo not empty, peek at the next key to process, it already
|
||||
* has the 0x80 bit on -- otherwise, return 0 */
|
||||
res = true;
|
||||
#if 1
|
||||
*byte = mii_bank_peek(main, SWKBD);
|
||||
#else
|
||||
if (mii_key_fifo_isempty(&mii->keys))
|
||||
*byte = 0;
|
||||
else
|
||||
*byte = mii_key_fifo_read_at(&mii->keys, 0).key;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case SWAKD:
|
||||
res = true;
|
||||
#if 1
|
||||
{
|
||||
uint8_t r = mii_bank_peek(main, SWAKD);
|
||||
if (!write)
|
||||
*byte = r;
|
||||
r &= 0x7f;
|
||||
mii_bank_poke(main, SWAKD, r);
|
||||
mii_bank_poke(main, SWKBD, r);
|
||||
}
|
||||
#else
|
||||
// if (write) {
|
||||
/* clear latch, and replace it immediately with the new key
|
||||
* if there's one in the FIFO */
|
||||
if (!mii_key_fifo_isempty(&mii->keys)) {
|
||||
mii_key_t k = mii_key_fifo_read(&mii->keys);
|
||||
mii_bank_poke(main, SWAKD, k.key);
|
||||
} else
|
||||
mii_bank_poke(main, SWAKD,
|
||||
mii_bank_peek(main, SWAKD) & ~0x80);
|
||||
// } else
|
||||
if (!write)
|
||||
*byte = mii_bank_peek(main, SWAKD);
|
||||
#endif
|
||||
break;
|
||||
case 0xc061 ... 0xc063: // Push Button 0, 1, 2 (Apple Keys)
|
||||
res = true;
|
||||
if (!write)
|
||||
*byte = mii_bank_peek(main, addr);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
mii_keypress(
|
||||
mii_t *mii,
|
||||
uint8_t key)
|
||||
{
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
key |= 0x80;
|
||||
mii_bank_poke(main, SWAKD, key);
|
||||
mii_bank_poke(main, SWKBD, key);
|
||||
#if 0
|
||||
mii_key_t k = {
|
||||
.key = key | 0x80,
|
||||
};
|
||||
if (!mii_key_fifo_isfull(&mii->keys))
|
||||
mii_key_fifo_write(&mii->keys, k);
|
||||
else {
|
||||
printf("%s key fifo full\n", __func__);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mii_init(
|
||||
mii_t *mii )
|
||||
{
|
||||
memset(mii, 0, sizeof(*mii));
|
||||
mii->speed = 1.0;
|
||||
for (int i = 0; i < MII_BANK_COUNT; i++)
|
||||
mii->bank[i] = _mii_banks_init[i];
|
||||
mii->bank[MII_BANK_ROM].mem = (uint8_t*)&iie_enhanced_rom_bin[0];
|
||||
mii->cpu.trap = MII_TRAP;
|
||||
mii_reset(mii, true);
|
||||
mii_speaker_init(mii, &mii->speaker);
|
||||
mii->cpu_state = mii_cpu_init(&mii->cpu);
|
||||
for (int i = 0; i < 7; i++)
|
||||
mii->slot[i].id = i;
|
||||
}
|
||||
|
||||
void
|
||||
mii_prepare(
|
||||
mii_t *mii,
|
||||
uint32_t flags )
|
||||
{
|
||||
printf("%s driver table\n", __func__);
|
||||
mii_slot_drv_t * drv = mii_slot_drv_list;
|
||||
while (drv) {
|
||||
printf("%s driver: %s\n", __func__, drv->name);
|
||||
if (drv->probe && drv->probe(mii, flags)) {
|
||||
printf("%s %s probe done\n", __func__, drv->name);
|
||||
}
|
||||
drv = drv->next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mii_reset(
|
||||
mii_t *mii,
|
||||
bool cold)
|
||||
{
|
||||
// printf("%s cold %d\n", __func__, cold);
|
||||
mii->cpu_state.reset = 1;
|
||||
mii->bsr_mode.write = 1;
|
||||
mii->bsr_mode.read = 0;
|
||||
mii->bsr_mode.page2 = 1;
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
mii_bank_poke(main, SWSLOTC3ROM, 0);
|
||||
mii_bank_poke(main, SWRAMRD, 0);
|
||||
mii_bank_poke(main, SWRAMWRT, 0);
|
||||
mii_bank_poke(main, SWALTPZ, 0);
|
||||
mii_bank_poke(main, SW80STORE, 0);
|
||||
mii_bank_poke(main, SW80COL, 0);
|
||||
mii->mem_dirty = 1;
|
||||
if (cold) {
|
||||
/* these HAS to be reset in that state somehow */
|
||||
mii_bank_poke(main, SWINTCXROM, 0);
|
||||
uint8_t z[2] = {0x55,0x55};
|
||||
mii_bank_write(main, 0x3f2, z, 2);
|
||||
}
|
||||
mii->mem_dirty = 1;
|
||||
mii_page_table_update(mii);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (mii->slot[i].drv && mii->slot[i].drv->reset)
|
||||
mii->slot[i].drv->reset(mii, &mii->slot[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mii_mem_access(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t * d,
|
||||
bool wr,
|
||||
bool do_sw)
|
||||
{
|
||||
if (!do_sw && addr >= 0xc000 && addr <= 0xc0ff)
|
||||
return;
|
||||
uint8_t done =
|
||||
mii_access_keyboard(mii, addr, d, wr) ||
|
||||
mii_access_video(mii, addr, d, wr) ||
|
||||
mii_access_soft_switches(mii, addr, d, wr);
|
||||
if (!done) {
|
||||
uint8_t page = addr >> 8;
|
||||
if (wr) {
|
||||
uint8_t m = mii->mem[page].write;
|
||||
mii_bank_t * b = &mii->bank[m];
|
||||
if (b->ro) {
|
||||
// printf("%s write to RO bank %s %04x:%02x\n",
|
||||
// __func__, b->name, addr, *d);
|
||||
} else
|
||||
mii_bank_write(b, addr, d, 1);
|
||||
} else {
|
||||
uint8_t m = mii->mem[page].read;
|
||||
mii_bank_t * b = &mii->bank[m];
|
||||
*d = mii_bank_peek(b, addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_handle_trap(
|
||||
mii_t *mii)
|
||||
{
|
||||
// printf("%s TRAP hit PC: %04x\n", __func__, mii->cpu.PC);
|
||||
mii->cpu_state.sync = 1;
|
||||
mii->cpu_state.trap = 0;
|
||||
mii->cpu.state = NULL;
|
||||
uint8_t trap = mii_read_one(mii, mii->cpu.PC);
|
||||
mii->cpu.PC += 1;
|
||||
// printf("%s TRAP %02x return PC %04x\n", __func__, trap, mii->cpu.PC);
|
||||
if (mii->trap.map & (1 << trap)) {
|
||||
if (mii->trap.trap[trap].cb)
|
||||
mii->trap.trap[trap].cb(mii, trap);
|
||||
} else {
|
||||
printf("%s TRAP %02x not handled\n", __func__, trap);
|
||||
mii->state = MII_STOPPED;
|
||||
}
|
||||
// mii->state = MII_STOPPED;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
mii_register_trap(
|
||||
mii_t *mii,
|
||||
mii_trap_handler_cb cb)
|
||||
{
|
||||
if (mii->trap.map == 0xffff) {
|
||||
printf("%s no more traps!!\n", __func__);
|
||||
return 0xff;
|
||||
}
|
||||
for (int i = 0; i < (int)sizeof(mii->trap.map) * 8; i++) {
|
||||
if (!(mii->trap.map & (1 << i))) {
|
||||
mii->trap.map |= 1 << i;
|
||||
mii->trap.trap[i].cb = cb;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void
|
||||
mii_run(
|
||||
mii_t *mii)
|
||||
{
|
||||
/* this runs all cycles for one instruction */
|
||||
do {
|
||||
if (mii->trace_cpu)
|
||||
mii_dump_trace_state(mii);
|
||||
mii->cpu_state = mii_cpu_run(&mii->cpu, mii->cpu_state);
|
||||
mii_video_run(mii);
|
||||
mii_speaker_run(&mii->speaker);
|
||||
// extract 16-bit address from pin mask
|
||||
const uint16_t addr = mii->cpu_state.addr;
|
||||
const uint8_t data = mii->cpu_state.data;
|
||||
int wr = mii->cpu_state.w;
|
||||
uint8_t d = data;
|
||||
if (mii->debug.bp_map) {
|
||||
for (int i = 0; i < (int)sizeof(mii->debug.bp_map) * 8; i++) {
|
||||
if (!(mii->debug.bp_map & (1 << i)))
|
||||
continue;
|
||||
if (addr >= mii->debug.bp[i].addr &&
|
||||
addr < mii->debug.bp[i].addr + mii->debug.bp[i].size) {
|
||||
if (((mii->debug.bp[i].kind & MII_BP_R) && !wr) ||
|
||||
((mii->debug.bp[i].kind & MII_BP_W) && wr)) {
|
||||
|
||||
if (1 || !mii->debug.bp[i].silent) {
|
||||
printf("BREAKPOINT %d at %04x PC:%04x\n",
|
||||
i, addr, mii->cpu.PC);
|
||||
mii_dump_run_trace(mii);
|
||||
mii_dump_trace_state(mii);
|
||||
mii->state = MII_STOPPED;
|
||||
}
|
||||
}
|
||||
if (!(mii->debug.bp[i].kind & MII_BP_STICKY))
|
||||
mii->debug.bp_map &= ~(1 << i);
|
||||
mii->debug.bp[i].kind |= MII_BP_HIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
mii_mem_access(mii, addr, &d, wr, true);
|
||||
if (!wr)
|
||||
mii->cpu_state.data = d;
|
||||
if (mii->cpu_state.trap) {
|
||||
_mii_handle_trap(mii);
|
||||
}
|
||||
} while (!(mii->cpu_state.sync));
|
||||
mii->cycles += mii->cpu.cycle;
|
||||
// log PC for the running disassembler display
|
||||
mii->trace.log[mii->trace.idx] = mii->cpu.PC;
|
||||
mii->trace.idx = (mii->trace.idx + 1) & (MII_PC_LOG_SIZE - 1);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (mii->slot[i].drv && mii->slot[i].drv->run)
|
||||
mii->slot[i].drv->run(mii, &mii->slot[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//! Read one byte from and addres, using the current memory mapping
|
||||
uint8_t
|
||||
mii_read_one(
|
||||
mii_t *mii,
|
||||
uint16_t addr)
|
||||
{
|
||||
uint8_t d = 0;
|
||||
mii_mem_access(mii, addr, &d, 0, false);
|
||||
return d;
|
||||
}
|
||||
//! Read a word from addr, using current memory mapping (little endian)
|
||||
uint16_t
|
||||
mii_read_word(
|
||||
mii_t *mii,
|
||||
uint16_t addr)
|
||||
{
|
||||
uint8_t d = 0;
|
||||
uint16_t res = 0;
|
||||
mii_mem_access(mii, addr, &d, 0, false);
|
||||
res = d;
|
||||
mii_mem_access(mii, addr + 1, &d, 0, false);
|
||||
res |= d << 8;
|
||||
return res;
|
||||
}
|
||||
/* same accessors, for write
|
||||
*/
|
||||
void
|
||||
mii_write_one(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t d)
|
||||
{
|
||||
mii_mem_access(mii, addr, &d, 1, false);
|
||||
}
|
||||
void
|
||||
mii_write_word(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint16_t w)
|
||||
{
|
||||
uint8_t d = w;
|
||||
mii_mem_access(mii, addr, &d, 1, false);
|
||||
d = w >> 8;
|
||||
mii_mem_access(mii, addr + 1, &d, 1, false);
|
||||
}
|
274
src/mii.h
Normal file
274
src/mii.h
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* mii.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "mii_65c02.h"
|
||||
#include "fifo_declare.h"
|
||||
#include "mii_dd.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_slot.h"
|
||||
#include "mii_video.h"
|
||||
#include "mii_speaker.h"
|
||||
#include "mii_mouse.h"
|
||||
|
||||
enum {
|
||||
MII_BANK_MAIN = 0, // main 48K address space
|
||||
MII_BANK_BSR, // 0xd000 - 0xffff bank switched RAM 16KB
|
||||
MII_BANK_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB
|
||||
|
||||
MII_BANK_AUX, // aux 48K address space (80 cols card)
|
||||
MII_BANK_AUX_BSR, // 0xd000 - 0xffff bank switched RAM aux 16KB
|
||||
MII_BANK_AUX_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB (aux bank)
|
||||
|
||||
MII_BANK_ROM, // 0xc000 - 0xffff 16K ROM
|
||||
MII_BANK_CARD_ROM, // 0xc100 - 0xcfff Card ROM access
|
||||
MII_BANK_COUNT,
|
||||
};
|
||||
|
||||
|
||||
typedef void (*mii_trap_handler_cb)(
|
||||
mii_t * mii,
|
||||
uint8_t trap);
|
||||
typedef struct mii_trap_t {
|
||||
uint16_t map;
|
||||
struct {
|
||||
mii_trap_handler_cb cb;
|
||||
} trap[16];
|
||||
} mii_trap_t;
|
||||
|
||||
// state of the emulator
|
||||
enum {
|
||||
MII_RUNNING = 0, // default
|
||||
MII_STOPPED,
|
||||
MII_STEP,
|
||||
};
|
||||
|
||||
enum {
|
||||
MII_BP_PC = (1 << 0), // breakpoint on PC
|
||||
MII_BP_W = (1 << 1), // breakpoint on write
|
||||
MII_BP_R = (1 << 2), // breakpoint on read
|
||||
MII_BP_HIT = (1 << 3), // breakpoint was hit
|
||||
MII_BP_SILENT = (1 << 4), // don't dump state (used for the 'next' command)
|
||||
MII_BP_STICKY = (1 << 7), // breakpoint is sticky (rearms itself)
|
||||
};
|
||||
|
||||
#define MII_PC_LOG_SIZE 16
|
||||
|
||||
/*
|
||||
* this keeps track of the last few PC values, for the debugger
|
||||
*/
|
||||
typedef struct mii_trace_t {
|
||||
uint16_t log[MII_PC_LOG_SIZE];
|
||||
uint8_t idx;
|
||||
// when in MII_STEP, do not fall back to MII_STOPPED until these
|
||||
// run out
|
||||
uint32_t step_inst;
|
||||
} mii_trace_t;
|
||||
|
||||
/*
|
||||
* principal emulator state, for a faceless emulation
|
||||
*/
|
||||
typedef struct mii_t {
|
||||
unsigned int state;
|
||||
__uint128_t cycles;
|
||||
/* this is the video frame/VBL rate vs 60hz, default to 1.0 */
|
||||
float speed;
|
||||
float speed_current; // calculated speed
|
||||
mii_cpu_t cpu;
|
||||
mii_cpu_state_t cpu_state;
|
||||
/*
|
||||
* bank index for each memory page number, this is recalculated
|
||||
* everytime a soft switch is triggered
|
||||
*/
|
||||
struct {
|
||||
uint8_t read : 4, write : 4;
|
||||
} mem[256];
|
||||
int mem_dirty; // recalculate mem[] on next access
|
||||
struct {
|
||||
int write, read, page2;
|
||||
} bsr_mode;
|
||||
|
||||
mii_trace_t trace;
|
||||
int trace_cpu;
|
||||
mii_trap_t trap;
|
||||
/*
|
||||
* Used for debugging only
|
||||
*/
|
||||
struct {
|
||||
uint16_t bp_map;
|
||||
struct {
|
||||
uint32_t kind : 8,
|
||||
addr : 16,
|
||||
size : 8,
|
||||
silent : 1;
|
||||
} bp[16];
|
||||
} debug;
|
||||
mii_bank_t bank[MII_BANK_COUNT];
|
||||
// the page c000 can have individual callbacks to override/supplement
|
||||
// existing default behaviour. This is currently used for the titan
|
||||
// accelerator 'card'
|
||||
mii_bank_access_t * soft_switches_override;
|
||||
mii_slot_t slot[7];
|
||||
mii_video_t video;
|
||||
mii_speaker_t speaker;
|
||||
mii_mouse_t mouse;
|
||||
mii_dd_system_t dd;
|
||||
} mii_t;
|
||||
|
||||
enum {
|
||||
MII_INIT_NSC = (1 << 0), // Install no slot clock
|
||||
MII_INIT_TITAN = (1 << 1), // Install Titan 'card'
|
||||
MII_INIT_DEFAULT = MII_INIT_NSC,
|
||||
};
|
||||
|
||||
/*
|
||||
* Call this first, to initialize the emulator state
|
||||
* This doesn't initializes any driver.
|
||||
*/
|
||||
void
|
||||
mii_init(
|
||||
mii_t *mii );
|
||||
|
||||
/*
|
||||
* Call this to prepare the emulator, instantiate and install drivers
|
||||
* etc. Presumably after you have used mii_argv_parse or loaded a config
|
||||
* file to set up the drivers.
|
||||
* flags is a combination of MII_INIT_*
|
||||
*/
|
||||
void
|
||||
mii_prepare(
|
||||
mii_t *mii,
|
||||
uint32_t flags );
|
||||
|
||||
/*
|
||||
* Parses arguments until in finds one that isn't for mii, and returns
|
||||
* the index of that argument in *index.
|
||||
* Return value is 0 if there's an argument that wasn't handled or 1
|
||||
* if all the arguments were parsed (and *index == argc)
|
||||
* mii parameter is the state AFTER mii_init() has been called.
|
||||
*
|
||||
* ioFlags is a combination of MII_INIT_*, it will be updated with the
|
||||
* flags found in the arguments. Pass this to mii_prepare()
|
||||
*/
|
||||
int
|
||||
mii_argv_parse(
|
||||
mii_t *mii,
|
||||
int argc,
|
||||
const char *argv[],
|
||||
int *index,
|
||||
uint32_t *ioFlags );
|
||||
/*
|
||||
* Locate driver_name, and attempt to register it with slot_id slot.
|
||||
* Returns 0 on success, -1 on failure
|
||||
*/
|
||||
int
|
||||
mii_slot_drv_register(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id,
|
||||
const char *driver_name);
|
||||
/* returns the driver registered for slot slot_id (or NULL) */
|
||||
mii_slot_drv_t *
|
||||
mii_slot_drv_get(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id);
|
||||
|
||||
/*
|
||||
* Reset the emulator, cold reset if cold is true, otherwise warm reset
|
||||
*/
|
||||
void
|
||||
mii_reset(
|
||||
mii_t *mii,
|
||||
bool cold);
|
||||
/* Execute one instruction, respecting breakpoints, runs the video code,
|
||||
* Check for traps and other associated debug stuff.
|
||||
*/
|
||||
void
|
||||
mii_run(
|
||||
mii_t *mii);
|
||||
// this one is thread safe, there's a FIFO behind it.
|
||||
void
|
||||
mii_keypress(
|
||||
mii_t *mii,
|
||||
uint8_t key);
|
||||
|
||||
/* read a byte as the processor would (ex softwitches!), this will
|
||||
* respect the status of slot ROMs, language card, 80 columns etc */
|
||||
uint8_t
|
||||
mii_read_one(
|
||||
mii_t *mii,
|
||||
uint16_t addr);
|
||||
/* write a byte as the processor would (ex softwitches!), this will
|
||||
* respect the status of slot ROMs, language card, 80 columns etc */
|
||||
void
|
||||
mii_write_one(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t d);
|
||||
/* read a word as the processor would (ex softwitches!), this will
|
||||
* respect the status of slot ROMs, language card, 80 columns etc */
|
||||
uint16_t
|
||||
mii_read_word(
|
||||
mii_t *mii,
|
||||
uint16_t addr);
|
||||
/* write a word as the processor would (ex softwitches!), this will
|
||||
* respect the status of slot ROMs, language card, 80 columns etc */
|
||||
void
|
||||
mii_write_word(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint16_t w);
|
||||
|
||||
/* lower level call to access memory -- this one can trigger softswitches
|
||||
* if specified. Otherwise behaves as the previous ones, one byte at a time
|
||||
*/
|
||||
void
|
||||
mii_mem_access(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write,
|
||||
bool do_sw);
|
||||
/* register a callback to call when a specific soft switches is hit,
|
||||
* this allows overriding/supplementing/tracing access to sw.
|
||||
*/
|
||||
void
|
||||
mii_set_sw_override(
|
||||
mii_t *mii,
|
||||
uint16_t sw_addr,
|
||||
mii_bank_access_cb cb,
|
||||
void *param);
|
||||
|
||||
void
|
||||
mii_dump_trace_state(
|
||||
mii_t *mii);
|
||||
void
|
||||
mii_dump_run_trace(
|
||||
mii_t *mii);
|
||||
|
||||
extern mii_slot_drv_t * mii_slot_drv_list;
|
||||
|
||||
#define MI_DRIVER_REGISTER(_mii_driver)\
|
||||
__attribute__((constructor,unused)) \
|
||||
static void _mii_register_##_mii_driver() { \
|
||||
_mii_driver.next = mii_slot_drv_list; \
|
||||
mii_slot_drv_list = &_mii_driver; \
|
||||
}
|
||||
|
||||
#define MII_TRAP 0xdbfb
|
||||
/*
|
||||
* Request a trap ID for the given callback. Calling code is responsible
|
||||
* for setting up the trap using the 2 magic NOPs in sequence. See mii_smartport.c
|
||||
* for an example.
|
||||
*/
|
||||
uint8_t
|
||||
mii_register_trap(
|
||||
mii_t *mii,
|
||||
mii_trap_handler_cb cb);
|
581
src/mii_65c02.c
Normal file
581
src/mii_65c02.c
Normal file
@ -0,0 +1,581 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "minipt.h"
|
||||
#include "mii_65c02.h"
|
||||
#define MII_CPU_65C02_IMPL
|
||||
#include "mii_65c02_ops.h"
|
||||
|
||||
mii_cpu_state_t
|
||||
mii_cpu_init(
|
||||
mii_cpu_t *cpu )
|
||||
{
|
||||
mii_cpu_state_t s = {
|
||||
.addr = 0,
|
||||
.reset = 1,
|
||||
};
|
||||
cpu->state = NULL;
|
||||
return s;
|
||||
}
|
||||
|
||||
#define _FETCH(_val) { \
|
||||
s.addr = _val; s.w = 0; cpu->cycle++; \
|
||||
pt_yield(cpu->state); \
|
||||
} while (0);
|
||||
#define _STORE(_addr, _val) { \
|
||||
s.addr = _addr; s.data = _val; s.w = 1; cpu->cycle++; \
|
||||
pt_yield(cpu->state); \
|
||||
} while (0);
|
||||
|
||||
#define _SET_P(_byte) { \
|
||||
for (int _pi = 0; _pi < 8; _pi++) \
|
||||
cpu->P.P[_pi] = _pi == B_B || _pi == B_X || \
|
||||
((_byte) & (1 << _pi)); \
|
||||
}
|
||||
#define _NZC(_val) { \
|
||||
uint16_t v = (_val); \
|
||||
cpu->P.N = !!(v & 0x80); \
|
||||
cpu->P.Z = (v & 0xff) == 0; \
|
||||
cpu->P.C = !!(v & 0xff00); \
|
||||
} while (0)
|
||||
#define _NZ(_val) { \
|
||||
uint16_t v = (_val); \
|
||||
cpu->P.N = !!(v & 0x80); \
|
||||
cpu->P.Z = (v & 0xff) == 0; \
|
||||
} while (0)
|
||||
#define _C(_val) { \
|
||||
cpu->P.C = !!(_val); \
|
||||
} while (0)
|
||||
|
||||
mii_cpu_state_t
|
||||
mii_cpu_run(
|
||||
mii_cpu_t *cpu,
|
||||
mii_cpu_state_t s)
|
||||
{
|
||||
mii_op_desc_t d = mii_cpu_op[cpu->IR].desc;
|
||||
pt_start(cpu->state);
|
||||
next_instruction:
|
||||
if (s.reset) {
|
||||
s.reset = 0;
|
||||
_FETCH(0xfffc); cpu->_P = s.data;
|
||||
_FETCH(0xfffd); cpu->_P |= s.data << 8;
|
||||
cpu->PC = cpu->_P;
|
||||
cpu->S = 0xFF;
|
||||
_SET_P(0);
|
||||
}
|
||||
if (s.irq) {
|
||||
s.irq = 0;
|
||||
cpu->P.P[B_B] = cpu->IRQ == 2;
|
||||
cpu->IRQ = 1;
|
||||
cpu->_D = cpu->PC + 1;
|
||||
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
|
||||
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
|
||||
uint8_t p = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
p |= (i == B_X || cpu->P.P[i]) << i;
|
||||
_STORE(0x0100 | cpu->S--, p);
|
||||
cpu->P.P[B_I] = 1;
|
||||
}
|
||||
if (cpu->IRQ) {
|
||||
cpu->IRQ = 0;
|
||||
_FETCH(0xfffe); cpu->_P = s.data;
|
||||
_FETCH(0xffff); cpu->_P |= s.data << 8;
|
||||
cpu->PC = cpu->_P;
|
||||
}
|
||||
s.sync = 1;
|
||||
// we dont' reset the cycle here, that way calling code has a way of knowing
|
||||
// how many cycles were used by the previous instruction
|
||||
_FETCH(cpu->PC);
|
||||
s.sync = 0;
|
||||
cpu->cycle = 0;
|
||||
cpu->PC++;
|
||||
cpu->IR = s.data;
|
||||
d = mii_cpu_op[cpu->IR].desc;
|
||||
cpu->ir_log = (cpu->ir_log << 8) | cpu->IR;
|
||||
s.trap = cpu->trap && (cpu->ir_log & 0xffff) == cpu->trap;
|
||||
if (s.trap)
|
||||
cpu->ir_log = 0;
|
||||
switch (d.mode) {
|
||||
case IMM:
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
break;
|
||||
case BRANCH: // BEQ/BNE etc
|
||||
case ZP_REL: // $(xx)
|
||||
_FETCH(cpu->PC++); cpu->_P = s.data;
|
||||
break;
|
||||
case ZP_X: // $xx,X
|
||||
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->X) & 0xff;
|
||||
break;
|
||||
case ZP_Y: // $xx,Y
|
||||
_FETCH(cpu->PC++); cpu->_P = (s.data + cpu->Y) & 0xff;
|
||||
break;
|
||||
case ABS: { // $xxxx
|
||||
_FETCH(cpu->PC++); cpu->_P = s.data;
|
||||
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
|
||||
} break;
|
||||
case ABS_X: { // $xxxx,X
|
||||
_FETCH(cpu->PC++); cpu->_P = s.data;
|
||||
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
|
||||
cpu->_P += cpu->X;
|
||||
if ((cpu->_P & 0xff00) != (s.data << 8))
|
||||
cpu->cycle++;
|
||||
} break;
|
||||
case ABS_Y: { // $xxxx,Y
|
||||
_FETCH(cpu->PC++); cpu->_P = s.data;
|
||||
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
|
||||
cpu->_P += cpu->Y;
|
||||
if ((cpu->_P & 0xff00) != (s.data << 8))
|
||||
cpu->cycle++;
|
||||
} break;
|
||||
case IND_X: { // ($xx,X)
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
cpu->_D += cpu->X;
|
||||
_FETCH(cpu->_D & 0xff); cpu->_P = s.data;
|
||||
cpu->_D++;
|
||||
_FETCH(cpu->_D & 0xff); cpu->_P |= s.data << 8;
|
||||
} break;
|
||||
case IND_Y: { // ($xx),Y
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
_FETCH(cpu->_D); cpu->_P = s.data;
|
||||
_FETCH((cpu->_D + 1) & 0xff);
|
||||
cpu->_P |= s.data << 8;
|
||||
cpu->_P += cpu->Y;
|
||||
} break;
|
||||
case IND: { // ($xxxx)
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
|
||||
_FETCH(cpu->_D); cpu->_P = s.data;
|
||||
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
|
||||
} break;
|
||||
case IND_Z: { // ($xx)
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
_FETCH(cpu->_D); cpu->_P = s.data;
|
||||
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
|
||||
} break;
|
||||
case IND_AX: { // ($xxxx,X)
|
||||
_FETCH(cpu->PC++); cpu->_D = s.data;
|
||||
_FETCH(cpu->PC++); cpu->_D |= s.data << 8;
|
||||
cpu->_D += cpu->X;
|
||||
if ((cpu->_D & 0xff00) != (s.data << 8))
|
||||
cpu->cycle++;
|
||||
_FETCH(cpu->_D); cpu->_P = s.data;
|
||||
_FETCH(cpu->_D + 1); cpu->_P |= s.data << 8;
|
||||
} break;
|
||||
}
|
||||
if (d.r) {
|
||||
_FETCH(cpu->_P);
|
||||
cpu->_D = s.data;
|
||||
}
|
||||
switch (cpu->IR) {
|
||||
case 0x69: case 0x65: case 0x75: case 0x6D: case 0x7D:
|
||||
case 0x79: case 0x61: case 0x71: case 0x72:
|
||||
{ // ADC
|
||||
// Handle adding in BCD with bit D
|
||||
if (cpu->P.D) {
|
||||
uint8_t lo = (cpu->A & 0x0f) + (cpu->_D & 0x0f) + !!cpu->P.C;
|
||||
if (lo > 9) lo += 6;
|
||||
uint8_t hi = (cpu->A >> 4) + (cpu->_D >> 4) + (lo > 0x0f);
|
||||
cpu->P.Z = ((uint8_t)(cpu->A + cpu->_D + cpu->P.C)) == 0;
|
||||
// that is 6502 behaviour
|
||||
// cpu->P.N = !!(hi & 0xf8);
|
||||
cpu->P.V = !!((!((cpu->A ^ cpu->_D) & 0x80) &&
|
||||
((cpu->A ^ (hi << 4))) & 0x80));
|
||||
if (hi > 9) hi += 6;
|
||||
cpu->P.C = hi > 15;
|
||||
cpu->A = (hi << 4) | (lo & 0x0f);
|
||||
// THAT is 65c02 behaviour
|
||||
cpu->P.N = !!(cpu->A & 0x80);
|
||||
} else {
|
||||
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
|
||||
cpu->P.V = cpu->P.C = 0;
|
||||
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
|
||||
cpu->P.V = 1;
|
||||
_NZC(sum);
|
||||
cpu->A = sum;
|
||||
}
|
||||
} break;
|
||||
case 0x29: case 0x25: case 0x35: case 0x2D: case 0x3D:
|
||||
case 0x39: case 0x21: case 0x31: case 0x32:
|
||||
{ // AND
|
||||
cpu->A &= cpu->_D;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x0A:
|
||||
{ // ASL
|
||||
cpu->P.C = !!(cpu->A & 0x80);
|
||||
cpu->A <<= 1;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x06: case 0x16: case 0x0E: case 0x1E:
|
||||
{ // ASL
|
||||
cpu->P.C = !!(cpu->_D & 0x80);
|
||||
cpu->_D <<= 1;
|
||||
_NZ(cpu->_D);
|
||||
} break;
|
||||
case 0x0f: case 0x1f: case 0x2f: case 0x3f: case 0x4f:
|
||||
case 0x5f: case 0x6f: case 0x7f: case 0x8f: case 0x9f:
|
||||
case 0xaf: case 0xbf: case 0xcf: case 0xdf: case 0xef:
|
||||
case 0xff:
|
||||
{ // BBR/BBS
|
||||
_FETCH(cpu->PC++); // relative branch
|
||||
if (((cpu->_D >> d.s_bit) & 1) == d.s_bit_value) {
|
||||
cpu->_P = cpu->PC + (int8_t)cpu->_P;
|
||||
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
|
||||
cpu->cycle++;
|
||||
cpu->PC = cpu->_P;
|
||||
cpu->cycle++;
|
||||
}
|
||||
} break;
|
||||
case 0x90: case 0xB0: case 0xF0: case 0x30:
|
||||
case 0xD0: case 0x10: case 0x50: case 0x70:
|
||||
{ // BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS
|
||||
if (d.s_bit_value == cpu->P.P[d.s_bit]) {
|
||||
cpu->_P = cpu->PC + (int8_t)cpu->_P;
|
||||
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
|
||||
cpu->cycle++;
|
||||
cpu->PC = cpu->_P;
|
||||
cpu->cycle++;
|
||||
}
|
||||
} break;
|
||||
case 0x80:
|
||||
{ // BRA
|
||||
cpu->PC = cpu->PC + (int8_t)cpu->_P; cpu->cycle++;
|
||||
} break;
|
||||
case 0x89:
|
||||
{ // BIT immediate -- does not change N & V!
|
||||
cpu->P.Z = !(cpu->A & cpu->_D);
|
||||
} break;
|
||||
case 0x24: case 0x2C: case 0x34: case 0x3C:
|
||||
{ // BIT
|
||||
cpu->P.Z = !(cpu->A & cpu->_D);
|
||||
cpu->P.N = !!(cpu->_D & 0x80);
|
||||
cpu->P.V = !!(cpu->_D & 0x40);
|
||||
} break;
|
||||
case 0x00:
|
||||
{ // BRK
|
||||
s.irq = 1;
|
||||
cpu->IRQ = 2; // IRQ interrupt
|
||||
// cpu->P.P[B_D] = 0; // 65c02 clears this
|
||||
} break;
|
||||
case 0x18: case 0xD8: case 0x58: case 0xB8:
|
||||
{ // CLC, CLD, CLI, CLV
|
||||
cpu->P.P[d.s_bit] = 0;
|
||||
} break;
|
||||
case 0xC9: case 0xC5: case 0xD5: case 0xCD: case 0xDD:
|
||||
case 0xD9: case 0xC1: case 0xD1: case 0xD2:
|
||||
{ // CMP
|
||||
cpu->P.C = !!(cpu->A >= cpu->_D);
|
||||
uint8_t d = cpu->A - cpu->_D;
|
||||
_NZ(d);
|
||||
} break;
|
||||
case 0xE0: case 0xE4: case 0xEC:
|
||||
{ // CPX
|
||||
cpu->P.C = !!(cpu->X >= cpu->_D);
|
||||
uint8_t d = cpu->X - cpu->_D;
|
||||
_NZ(d);
|
||||
} break;
|
||||
case 0xC0: case 0xC4: case 0xCC:
|
||||
{ // CPY
|
||||
cpu->P.C = !!(cpu->Y >= cpu->_D);
|
||||
uint8_t d = cpu->Y - cpu->_D;
|
||||
_NZ(d);
|
||||
} break;
|
||||
case 0x3A:
|
||||
{ // DEC
|
||||
_NZ(--cpu->A);
|
||||
} break;
|
||||
case 0xC6: case 0xD6: case 0xCE: case 0xDE:
|
||||
{ // DEC
|
||||
_NZ(--cpu->_D);
|
||||
} break;
|
||||
case 0xCA:
|
||||
{ // DEX
|
||||
_NZ(--cpu->X);
|
||||
} break;
|
||||
case 0x88:
|
||||
{ // DEY
|
||||
_NZ(--cpu->Y);
|
||||
} break;
|
||||
case 0x49: case 0x45: case 0x55: case 0x4D: case 0x5D:
|
||||
case 0x59: case 0x41: case 0x51: case 0x52:
|
||||
{ // EOR
|
||||
cpu->A ^= cpu->_D;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x1A:
|
||||
{ // INC
|
||||
_NZ(++cpu->A);
|
||||
} break;
|
||||
case 0xE6: case 0xF6: case 0xEE: case 0xFE:
|
||||
{ // INC
|
||||
_NZ(++cpu->_D);
|
||||
} break;
|
||||
case 0xE8:
|
||||
{ // INX
|
||||
_NZ(++cpu->X);
|
||||
} break;
|
||||
case 0xC8:
|
||||
{ // INY
|
||||
_NZ(++cpu->Y);
|
||||
} break;
|
||||
case 0x4C: case 0x6C: case 0x7C:
|
||||
{ // JMP
|
||||
cpu->PC = cpu->_P;
|
||||
} break;
|
||||
case 0x20:
|
||||
{ // JSR
|
||||
cpu->_D = cpu->PC - 1;
|
||||
_STORE(0x0100 | cpu->S--, cpu->_D >> 8);
|
||||
_STORE(0x0100 | cpu->S--, cpu->_D & 0xff);
|
||||
cpu->PC = cpu->_P;
|
||||
} break;
|
||||
case 0xA9: case 0xA5: case 0xB5: case 0xAD: case 0xBD:
|
||||
case 0xB9: case 0xA1: case 0xB1: case 0xB2:
|
||||
{ // LDA
|
||||
cpu->A = cpu->_D;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0xA2: case 0xA6: case 0xB6: case 0xAE: case 0xBE:
|
||||
{ // LDX
|
||||
cpu->X = cpu->_D;
|
||||
_NZ(cpu->X);
|
||||
} break;
|
||||
case 0xA0: case 0xA4: case 0xB4: case 0xAC: case 0xBC:
|
||||
{ // LDY
|
||||
cpu->Y = cpu->_D;
|
||||
_NZ(cpu->Y);
|
||||
} break;
|
||||
case 0x4A:
|
||||
{ // LSR
|
||||
cpu->P.C = !!(cpu->A & 0x01);
|
||||
cpu->A >>= 1;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x46: case 0x56: case 0x4E: case 0x5E:
|
||||
{ // LSR
|
||||
cpu->P.C = !!(cpu->_D & 0x01);
|
||||
cpu->_D >>= 1;
|
||||
_NZ(cpu->_D);
|
||||
} break;
|
||||
case 0xEA:
|
||||
{ // NOP
|
||||
} break;
|
||||
case 0x09: case 0x05: case 0x15: case 0x0D: case 0x1D:
|
||||
case 0x19: case 0x01: case 0x11: case 0x12:
|
||||
{ // ORA
|
||||
cpu->A |= cpu->_D;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x48:
|
||||
{ // PHA
|
||||
_STORE(0x0100 | cpu->S--, cpu->A); cpu->cycle++;
|
||||
} break;
|
||||
case 0x08:
|
||||
{ // PHP
|
||||
uint8_t p = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
p |= (i == B_X || cpu->P.P[i]) << i;
|
||||
p |= (1 << B_B);
|
||||
_STORE(0x0100 | cpu->S--, p); cpu->cycle++;
|
||||
} break;
|
||||
case 0xDA:
|
||||
{ // PHX
|
||||
_STORE(0x0100 | cpu->S--, cpu->X);cpu->cycle++;
|
||||
} break;
|
||||
case 0x5A:
|
||||
{ // PHY
|
||||
_STORE(0x0100 | cpu->S--, cpu->Y);cpu->cycle++;
|
||||
} break;
|
||||
case 0x68:
|
||||
{ // PLA
|
||||
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
|
||||
cpu->A = s.data;cpu->cycle++;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x28:
|
||||
{ // PLP
|
||||
_FETCH(0x0100 | ++cpu->S);cpu->cycle++;
|
||||
_SET_P(s.data);cpu->cycle++;
|
||||
} break;
|
||||
case 0xFA:
|
||||
{ // PLX
|
||||
_FETCH(0x0100 | ++cpu->S);
|
||||
cpu->X = s.data;
|
||||
_NZ(cpu->X);
|
||||
} break;
|
||||
case 0x7A:
|
||||
{ // PLY
|
||||
_FETCH(0x0100 | ++cpu->S);
|
||||
cpu->Y = s.data;
|
||||
_NZ(cpu->Y);
|
||||
} break;
|
||||
case 0x2A:
|
||||
{ // ROL
|
||||
uint8_t c = cpu->P.C;
|
||||
cpu->P.C = !!(cpu->A & 0x80);
|
||||
cpu->A <<= 1;
|
||||
cpu->A |= c;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x26: case 0x36: case 0x2E: case 0x3E:
|
||||
{ // ROL
|
||||
uint8_t c = cpu->P.C;
|
||||
cpu->P.C = !!(cpu->_D & 0x80);
|
||||
cpu->_D <<= 1;
|
||||
cpu->_D |= c;
|
||||
_NZ(cpu->_D);
|
||||
} break;
|
||||
case 0x6A:
|
||||
{ // ROR
|
||||
uint8_t c = cpu->P.C;
|
||||
cpu->P.C = !!(cpu->A & 0x01);
|
||||
cpu->A >>= 1;
|
||||
cpu->A |= c << 7;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x66: case 0x76: case 0x6E: case 0x7E:
|
||||
{ // ROR
|
||||
uint8_t c = cpu->P.C;
|
||||
cpu->P.C = !!(cpu->_D & 0x01);
|
||||
cpu->_D >>= 1;
|
||||
cpu->_D |= c << 7;
|
||||
_NZ(cpu->_D);
|
||||
} break;
|
||||
case 0x40:
|
||||
{ // RTI
|
||||
_FETCH(0x0100 | ((++cpu->S) & 0xff));
|
||||
for (int i = 0; i < 8; i++)
|
||||
cpu->P.P[i] = i == B_B || (s.data & (1 << i));
|
||||
cpu->P._R = 1;
|
||||
_FETCH(0x0100 | ((++cpu->S) & 0xff));
|
||||
cpu->_P = s.data;
|
||||
_FETCH(0x0100 | ((++cpu->S) & 0xff));
|
||||
cpu->_P = (s.data << 8) | (cpu->_P );
|
||||
cpu->PC = cpu->_P;
|
||||
} break;
|
||||
case 0x60:
|
||||
{ // RTS
|
||||
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
|
||||
cpu->_P = s.data;
|
||||
_FETCH(0x0100 | ((++cpu->S) & 0xff));cpu->cycle++;
|
||||
cpu->_P |= s.data << 8;
|
||||
cpu->PC = cpu->_P + 1; cpu->cycle++;
|
||||
} break;
|
||||
case 0xE9: case 0xE5: case 0xF5: case 0xED: case 0xFD:
|
||||
case 0xF9: case 0xE1: case 0xF1: case 0xF2:
|
||||
{ // SBC
|
||||
// Handle subbing in BCD with bit D
|
||||
if (cpu->P.D) {
|
||||
uint8_t lo = (cpu->A & 0x0f) - (cpu->_D & 0x0f) - !cpu->P.C;
|
||||
if (lo & 0x10) lo -= 6;
|
||||
uint8_t hi = (cpu->A >> 4) - (cpu->_D >> 4) - (lo & 0x10);
|
||||
if (hi & 0x10) hi -= 6;
|
||||
cpu->P.Z = ((uint8_t)(cpu->A - cpu->_D - !cpu->P.C)) == 0;
|
||||
cpu->P.N = !!(hi & 0x8);
|
||||
cpu->P.V = !!(((cpu->A ^ cpu->_D) &
|
||||
((cpu->A ^ (hi << 4))) & 0x80));
|
||||
cpu->P.C = !(hi & 0x10);
|
||||
cpu->A = (hi << 4) | (lo & 0x0f);
|
||||
} else {
|
||||
cpu->_D = (~cpu->_D) & 0xff;
|
||||
uint16_t sum = cpu->A + cpu->_D + !!cpu->P.C;
|
||||
cpu->P.V = cpu->P.C = 0;
|
||||
if (~(cpu->A ^ cpu->_D) & (cpu->A ^ sum) & 0x80)
|
||||
cpu->P.V = 1;
|
||||
_NZC(sum);
|
||||
cpu->A = sum;
|
||||
}
|
||||
} break;
|
||||
case 0x38: case 0xF8: case 0x78:
|
||||
{ // SEC, SED, SEI
|
||||
cpu->P.P[d.s_bit] = 1;
|
||||
} break;
|
||||
case 0x85: case 0x95: case 0x8D: case 0x9D:
|
||||
case 0x99: case 0x81: case 0x91: case 0x92:
|
||||
{ // STA
|
||||
cpu->_D = cpu->A;
|
||||
} break;
|
||||
case 0x86: case 0x96: case 0x8E:
|
||||
{ // STX
|
||||
cpu->_D = cpu->X;
|
||||
} break;
|
||||
case 0x84: case 0x94: case 0x8C:
|
||||
{ // STY
|
||||
cpu->_D = cpu->Y;
|
||||
} break;
|
||||
case 0x64: case 0x74: case 0x9C: case 0x9E:
|
||||
{ // STZ
|
||||
cpu->_D = 0;
|
||||
} break;
|
||||
case 0x14: case 0x1c:
|
||||
{ // TRB
|
||||
cpu->P.Z = !(cpu->A & cpu->_D);
|
||||
cpu->_D &= ~cpu->A;
|
||||
} break;
|
||||
case 0x04: case 0x0c:
|
||||
{ // TSB
|
||||
cpu->P.Z = !(cpu->A & cpu->_D);
|
||||
cpu->_D |= cpu->A;
|
||||
} break;
|
||||
case 0xAA:
|
||||
{ // TAX
|
||||
cpu->X = cpu->A;
|
||||
_NZ(cpu->X);
|
||||
} break;
|
||||
case 0xA8:
|
||||
{ // TAY
|
||||
cpu->Y = cpu->A;
|
||||
_NZ(cpu->Y);
|
||||
} break;
|
||||
case 0xBA:
|
||||
{ // TSX
|
||||
cpu->X = cpu->S;
|
||||
_NZ(cpu->X);
|
||||
} break;
|
||||
case 0x8A:
|
||||
{ // TXA
|
||||
cpu->A = cpu->X;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x9A:
|
||||
{ // TXS
|
||||
cpu->S = cpu->X;
|
||||
} break;
|
||||
case 0x98:
|
||||
{ // TYA
|
||||
cpu->A = cpu->Y;
|
||||
_NZ(cpu->A);
|
||||
} break;
|
||||
case 0x07: case 0x17: case 0x27: case 0x37: case 0x47:
|
||||
case 0x57: case 0x67: case 0x77: case 0x87: case 0x97:
|
||||
case 0xA7: case 0xB7: case 0xC7: case 0xD7: case 0xE7:
|
||||
case 0xF7:
|
||||
{ // RMB/SMB
|
||||
cpu->_D = (cpu->_D & ~(1 << d.s_bit)) | (d.s_bit_value << d.s_bit);
|
||||
} break;
|
||||
/* Apparently these NOPs use 3 bytes, according to the tests */
|
||||
case 0x5c: case 0xdc: case 0xfc:
|
||||
_FETCH(cpu->PC++);
|
||||
/* Apparently these NOPs use 2 bytes, according to the tests */
|
||||
case 0x02: case 0x22: case 0x42: case 0x62: case 0x82:
|
||||
case 0xC2: case 0xE2: case 0x44: case 0x54: case 0xD4:
|
||||
case 0xF4:
|
||||
_FETCH(cpu->PC++); // consume that byte
|
||||
break;
|
||||
case 0xdb: case 0xfb:
|
||||
// trap NOPs
|
||||
break;
|
||||
default:
|
||||
printf("%04x %02x UNKNOWN INSTRUCTION\n", cpu->PC, cpu->IR);
|
||||
// exit(1);
|
||||
break;
|
||||
}
|
||||
if (d.w) {
|
||||
_STORE(cpu->_P, cpu->_D);
|
||||
}
|
||||
goto next_instruction;
|
||||
pt_end(cpu->state);
|
||||
return s;
|
||||
}
|
88
src/mii_65c02.h
Normal file
88
src/mii_65c02.h
Normal file
@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* State structure used to 'talk' to the CPU emulator.
|
||||
* It works like this:
|
||||
* mii_cpu_state_t s = { .reset = 1 };
|
||||
* do {
|
||||
* s = mii_cpu_run(cpu, s);
|
||||
* if (s.w)
|
||||
* write_memory(s.addr, s.data);
|
||||
* else
|
||||
* s.data = read_memory(s.addr);
|
||||
* } while (1);
|
||||
* s.sync will be 1 when we are fetching a new instruction
|
||||
* s.reset will be 1 when the CPU is in reset (fetching vector)
|
||||
* will turn to zero when done
|
||||
* You check then the cpu->cycle to know what cycle you're in the current
|
||||
* instruction.
|
||||
*
|
||||
* If you want to 'jump' to a new PC, you need to
|
||||
* s.addr = new_pc;
|
||||
* s.sync = 1;
|
||||
* this will stop the current instruction and start fetching at new_pc
|
||||
*
|
||||
*/
|
||||
typedef union mii_cpu_state_t {
|
||||
struct {
|
||||
uint32_t addr : 16,
|
||||
data : 8,
|
||||
w : 1,
|
||||
sync : 1,
|
||||
reset : 1,
|
||||
irq : 1,
|
||||
nmi : 1,
|
||||
trap : 1;
|
||||
};
|
||||
uint32_t raw;
|
||||
} mii_cpu_state_t;
|
||||
|
||||
/* CPU state machine */
|
||||
typedef struct mii_cpu_t {
|
||||
uint8_t A, X, Y, S;
|
||||
/* internal 16bit registers for fetch etc
|
||||
* _D is the 'data' register, used for 16bit operations
|
||||
* _P is the 'pointer' register, used for 16bit addressing
|
||||
* This is not set hard in stone, but typically addressing modes that
|
||||
* are 'indirect' and load from memory will set _P and read in _D
|
||||
* so the opcode doesn't have to handle s.data at all */
|
||||
uint16_t _D, _P;
|
||||
/* My experience with simavr shows that maintaining a 8 bits bitfield
|
||||
* for a status register is a lot slower than having discrete flags
|
||||
* and 'constructing' the matching 8 biots register when needed */
|
||||
union {
|
||||
struct {
|
||||
uint8_t C, Z, I, D, B, _R, V, N;
|
||||
};
|
||||
uint8_t P[8];
|
||||
} P;
|
||||
uint16_t PC;
|
||||
uint8_t IR;
|
||||
uint8_t IRQ; // IRQ (0) or NMI (1) or BRK (2)
|
||||
uint8_t cycle; // for current instruction
|
||||
/* State of the CPU state machine */
|
||||
void * state;
|
||||
|
||||
/* sequence of instruction that will trigger a trap flag.
|
||||
* this is used to trigger 'call backs' to the main code
|
||||
* typically use a pair of NOPs sequence that is unlikely to exist in
|
||||
* real code. */
|
||||
uint16_t trap;
|
||||
// last 4 instructions, as a shift register, used for traps or debug
|
||||
uint32_t ir_log;
|
||||
|
||||
/* Debug only; the callback is called every cycles, with the current
|
||||
* state of the cpu. */
|
||||
uint8_t * ram; // DEBUG
|
||||
} mii_cpu_t;
|
||||
|
||||
mii_cpu_state_t
|
||||
mii_cpu_init(
|
||||
mii_cpu_t *cpu );
|
||||
|
||||
mii_cpu_state_t
|
||||
mii_cpu_run(
|
||||
mii_cpu_t *cpu,
|
||||
mii_cpu_state_t s);
|
503
src/mii_65c02_asm.c
Normal file
503
src/mii_65c02_asm.c
Normal file
@ -0,0 +1,503 @@
|
||||
/*
|
||||
* mii_65c02_asm.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii_65c02.h"
|
||||
#include "mii_65c02_ops.h"
|
||||
#include "mii_65c02_asm.h"
|
||||
|
||||
static char *
|
||||
_mii_extract_token(
|
||||
char *position,
|
||||
char *dst,
|
||||
int dst_len)
|
||||
{
|
||||
if (!position)
|
||||
return NULL;
|
||||
while (*position == ' ' || *position == '\t')
|
||||
position++;
|
||||
char *kw = strsep(&position, " \t");
|
||||
if (kw)
|
||||
strncpy(dst, kw, dst_len);
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
_mii_extract_name(
|
||||
char *src,
|
||||
char *dst,
|
||||
int dst_len)
|
||||
{
|
||||
char *end = src;
|
||||
while (isalnum(*end) || *end == '_')
|
||||
*dst++ = *end++;
|
||||
*dst = 0;
|
||||
return end;
|
||||
}
|
||||
|
||||
static char *
|
||||
_mii_extract_value_or_name(
|
||||
mii_cpu_asm_line_t *l,
|
||||
char *src)
|
||||
{
|
||||
static const char * const hex = "0123456789abcdef";
|
||||
char *end = src;
|
||||
l->op_value = 0;
|
||||
if (*end == '<') {
|
||||
l->op_low = 1;
|
||||
end++;
|
||||
} else if (*end == '>') {
|
||||
l->op_high = 1;
|
||||
end++;
|
||||
}
|
||||
if (*end == '$') {
|
||||
end++;
|
||||
while (isxdigit(*end))
|
||||
l->op_value = (l->op_value << 4) +
|
||||
(strchr(hex, tolower(*end++)) - hex);
|
||||
} else if (isdigit(*end)) {
|
||||
while (isdigit(*end))
|
||||
l->op_value = (l->op_value << 4) +
|
||||
(strchr(hex, tolower(*end++)) - hex);
|
||||
} else {
|
||||
end = _mii_extract_name(end, l->op_name, sizeof(l->op_name));
|
||||
l->label_resolved = 0;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_resolve_symbol(
|
||||
mii_cpu_asm_program_t *p,
|
||||
mii_cpu_asm_line_t *l,
|
||||
int optional)
|
||||
{
|
||||
if (!l->op_name[0] || l->label_resolved)
|
||||
return 1;
|
||||
mii_cpu_asm_line_t *l2 = p->sym;
|
||||
while (l2) {
|
||||
if (!strcasecmp(l->op_name, l2->label)) {
|
||||
l->op_value = l2->op_value;
|
||||
l->label_resolved = 1;
|
||||
goto found;
|
||||
}
|
||||
l2 = l2->sym_next;
|
||||
}
|
||||
if (!optional)
|
||||
printf("%s symbol %s not found\n", __func__, l->op_name);
|
||||
return 0;
|
||||
found:
|
||||
if (l->op_low)
|
||||
l->op_value &= 0xff;
|
||||
else if (l->op_high)
|
||||
l->op_value >>= 8;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
mii_cpu_asm_load(
|
||||
mii_cpu_asm_program_t *p,
|
||||
const char *prog)
|
||||
{
|
||||
const char *current = prog;
|
||||
int line_count = 0;
|
||||
do {
|
||||
const char *lend = strchr(current, '\n');
|
||||
int len = lend ? (int)(lend - current) : (int)strlen(current);
|
||||
|
||||
mii_cpu_asm_line_t *l = calloc(1, sizeof(mii_cpu_asm_line_t) + len + 1);
|
||||
l->line_index = line_count++;
|
||||
sprintf(l->line, "%.*s", len, current);
|
||||
if (p->prog_tail) {
|
||||
p->prog_tail->next = l;
|
||||
p->prog_tail = l;
|
||||
} else {
|
||||
p->prog = p->prog_tail = l;
|
||||
}
|
||||
char *dup = strdup(l->line);
|
||||
char * comment = strrchr(dup, ';');
|
||||
if (comment)
|
||||
*comment = 0;
|
||||
if (!*dup) {
|
||||
goto next_line;
|
||||
}
|
||||
char * position = dup;
|
||||
if (position[0] != ' ' && position[0] != '\t') {
|
||||
char *start = position;
|
||||
char *kw = strsep(&position, " \t=");
|
||||
if (kw) {
|
||||
// strip spaces
|
||||
char *ke = kw + strlen(kw);
|
||||
while (ke > start && (ke[-1] <= ' ' || ke[-1] == ':'))
|
||||
*--ke = 0;
|
||||
strncpy(l->label, kw, sizeof(l->label)-1);
|
||||
}
|
||||
}
|
||||
position = _mii_extract_token(position, l->mnemonic, sizeof(l->mnemonic));
|
||||
|
||||
if (!strcmp(l->mnemonic, ".db") || !strcmp(l->mnemonic, "byte")) {
|
||||
/* remaining of the line is comma separated hex values OR symbols
|
||||
* that can be resolved immediately */
|
||||
char *kw = position;
|
||||
while (*kw == ' ' || *kw == '\t') kw++;
|
||||
char *cur = kw;
|
||||
while ((cur = strsep(&kw, ",")) != NULL) {
|
||||
while (*cur == ' ' || *cur == '\t') cur++;
|
||||
char *ke = cur + strlen(cur);
|
||||
while (ke > cur && (ke[-1] <= ' ' || ke[-1] == ':'))
|
||||
*--ke = 0;
|
||||
if (!*cur)
|
||||
break;
|
||||
_mii_extract_value_or_name(l, cur);
|
||||
if (_mii_resolve_symbol(p, l, 0) == 0)
|
||||
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
|
||||
else
|
||||
l->opcodes[l->opcode_count++] = l->op_value;
|
||||
}
|
||||
goto next_line;
|
||||
}
|
||||
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
|
||||
if (l->operand[0] == '.' || l->operand[0] == '=') {
|
||||
// a directive that has been not aligned on the first line, re-adjust
|
||||
snprintf(l->label, sizeof(l->label), "%s", l->mnemonic);
|
||||
snprintf(l->mnemonic, sizeof(l->mnemonic), "%s", l->operand);
|
||||
position = _mii_extract_token(position, l->operand, sizeof(l->operand));
|
||||
}
|
||||
if (l->mnemonic[0] == '=' ||
|
||||
!strcasecmp(l->mnemonic, "equ") ||
|
||||
!strcasecmp(l->mnemonic, ".equ")) {
|
||||
l->symbol = 1;
|
||||
if (p->sym_tail)
|
||||
p->sym_tail->sym_next = l;
|
||||
else
|
||||
p->sym = l;
|
||||
_mii_extract_value_or_name(l, l->operand);
|
||||
if (_mii_resolve_symbol(p, l, 0) == 0)
|
||||
printf("ERROR -- sorry code symbols don't work, just EQUs\n");
|
||||
}
|
||||
// printf(">L:%s M:%s O:%s\n", l->label, l->mnemonic, l->operand);
|
||||
next_line:
|
||||
free(dup);
|
||||
current = lend ? lend+1 : NULL;
|
||||
} while (current);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
mii_cpu_opcode_has_mode(
|
||||
const char *mnemonic,
|
||||
int mode)
|
||||
{
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (!strncasecmp(mnemonic, mii_cpu_op[i].name, 4)) {
|
||||
if (mode == mii_cpu_op[i].desc.mode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
_mii_cpu_asm_parse_operand(
|
||||
mii_cpu_asm_program_t *p,
|
||||
mii_cpu_asm_line_t *l)
|
||||
{
|
||||
l->mode = IMPLIED;
|
||||
if (!l->operand[0])
|
||||
return 0;
|
||||
|
||||
char sep = 0; // (parenthesis)
|
||||
char *src = l->operand;
|
||||
switch (*src) {
|
||||
case '#':
|
||||
l->mode = IMM;
|
||||
src = _mii_extract_value_or_name(l, src + 1);
|
||||
break;
|
||||
case '$':
|
||||
l->mode = ABS;
|
||||
src = _mii_extract_value_or_name(l, src);
|
||||
break;
|
||||
case '(':
|
||||
l->mode = IND;
|
||||
sep = ')';
|
||||
src = _mii_extract_value_or_name(l, src + 1);
|
||||
break;
|
||||
default: {
|
||||
l->mode = ABS;
|
||||
src = _mii_extract_value_or_name(l, src);
|
||||
_mii_resolve_symbol(p, l, 1);
|
||||
} break;
|
||||
}
|
||||
while (*src == ' ' || *src == '\t') src++;
|
||||
switch (*src) {
|
||||
case ',':
|
||||
src++;
|
||||
while (*src == ' ' || *src == '\t') src++;
|
||||
switch (tolower(*src)) {
|
||||
case 'x':
|
||||
l->mode = ABS_X;
|
||||
if (sep) {
|
||||
// special case for JMP ($xxxx,x)
|
||||
if (!strcasecmp(l->mnemonic, "JMP"))
|
||||
l->mode = IND_AX;
|
||||
else
|
||||
l->mode = IND_X;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'y':
|
||||
l->mode = ABS_Y;
|
||||
break;
|
||||
default:
|
||||
l->mode = ABS;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
src++;
|
||||
switch (*src) {
|
||||
case ',':
|
||||
src++;
|
||||
while (*src == ' ' || *src == '\t') src++;
|
||||
switch (tolower(*src)) {
|
||||
case 'x':
|
||||
l->mode = IND_X;
|
||||
break;
|
||||
default:
|
||||
l->mode = IND_Y;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
l->mode = IND;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (l->mode) {
|
||||
case ABS: {
|
||||
if (l->op_value < 0x100) {
|
||||
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_REL);
|
||||
if (newo != -1) {
|
||||
l->mode = ZP_REL;
|
||||
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||||
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case ABS_X: {
|
||||
if (l->op_value < 0x100) {
|
||||
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_X);
|
||||
if (newo != -1) {
|
||||
l->mode = ZP_X;
|
||||
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||||
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case ABS_Y: {
|
||||
if (l->op_value < 0x100) {
|
||||
int newo = mii_cpu_opcode_has_mode(l->mnemonic, ZP_Y);
|
||||
if (newo != -1) {
|
||||
l->mode = ZP_Y;
|
||||
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||||
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case IND: if (1) {
|
||||
if (l->op_value < 0x100) {
|
||||
// printf("Testing if IND opcode %s can be IND_Z operand %s (%04x)\n",
|
||||
// l->mnemonic, l->operand, l->op_value);
|
||||
int newo = mii_cpu_opcode_has_mode(l->mnemonic, IND_Z);
|
||||
if (newo != -1) {
|
||||
// printf("YES replace %02x with %02x\n",
|
||||
// l->opcodes[0], newo);
|
||||
l->mode = IND_Z;
|
||||
l->opcodes[0] = mii_cpu_op[newo].desc.op;
|
||||
l->opcode_count = mii_cpu_op[newo].desc.pc;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_cpu_asm_assemble(
|
||||
mii_cpu_asm_program_t *p )
|
||||
{
|
||||
mii_cpu_asm_line_t *l = p->prog;
|
||||
int error = 0;
|
||||
|
||||
// fix symbols
|
||||
l = p->sym;
|
||||
while (l) {
|
||||
_mii_extract_value_or_name(l, l->operand);
|
||||
// printf("SYM %s = %04x\n", l->label, l->op_value);
|
||||
l = l->sym_next;
|
||||
}
|
||||
l = p->prog;
|
||||
while (l) {
|
||||
if (!l->mnemonic[0] || l->symbol) {
|
||||
l = l->next;
|
||||
continue;
|
||||
}
|
||||
l->mode = IMPLIED;
|
||||
_mii_cpu_asm_parse_operand(p, l);
|
||||
if (!strcasecmp(l->mnemonic, ".org")) {
|
||||
if (p->verbose)
|
||||
printf("%s origin set to $%04x\n", __func__, l->op_value);
|
||||
if (l->mode == ABS) {
|
||||
if (!p->org)
|
||||
p->org = l->op_value;
|
||||
l->addr_set = 1;
|
||||
l->addr = l->op_value;
|
||||
}
|
||||
l = l->next;
|
||||
continue;
|
||||
} else if (!strcasecmp(l->mnemonic, ".verbose")) {
|
||||
p->verbose = 1;
|
||||
l = l->next;
|
||||
continue;
|
||||
} else if (l->mnemonic[0] == '.') {
|
||||
l = l->next;
|
||||
continue;
|
||||
}
|
||||
int found = -1;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (!strncasecmp(l->mnemonic, mii_cpu_op[i].name, 4)) {
|
||||
if (mii_cpu_op[i].desc.branch) {
|
||||
l->mode = mii_cpu_op[i].desc.mode;
|
||||
found = i;
|
||||
break;
|
||||
} else if (l->mode == mii_cpu_op[i].desc.mode) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == -1) {
|
||||
printf("ERROR: %d: %s %s %s\n",
|
||||
l->line_index, l->label, l->mnemonic, l->operand);
|
||||
printf(" Missing opcode for %s %d\n", l->mnemonic, l->mode);
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
// printf("FOUND %02x for '%s' '%s' '%s' name:'%s'\n",
|
||||
// found, l->label, l->mnemonic, l->operand, l->op_name);
|
||||
l->opcodes[0] = mii_cpu_op[found].desc.op;
|
||||
l->opcode_count = mii_cpu_op[found].desc.pc;
|
||||
l = l->next;
|
||||
}
|
||||
if (error)
|
||||
return error;
|
||||
uint16_t pc = p->org;
|
||||
/*
|
||||
* We (should) know all instruction size by now, so lets sets
|
||||
* the addresses for all the lines
|
||||
*/
|
||||
l = p->prog;
|
||||
while (l) {
|
||||
if (l->addr_set)
|
||||
pc = l->addr;
|
||||
else
|
||||
l->addr = pc;
|
||||
if (l->mnemonic[0] != '.') {
|
||||
// this sets the ones we know about, and clears the ones we dont
|
||||
for (int i = 1; i < l->opcode_count; i++)
|
||||
l->opcodes[i] = l->op_value >> (8 * (i - 1));
|
||||
}
|
||||
pc += l->opcode_count;
|
||||
l = l->next;
|
||||
}
|
||||
/*
|
||||
* Now resolve all the operand labels
|
||||
*/
|
||||
l = p->prog;
|
||||
while (l) {
|
||||
if (l->op_name[0] && !l->label_resolved) {
|
||||
mii_cpu_asm_line_t *l2 = p->prog;
|
||||
while (l2) {
|
||||
int32_t value = 0;
|
||||
if (!strcasecmp(l->op_name, l2->label)) {
|
||||
value = l2->op_value;
|
||||
if (!l2->symbol)
|
||||
value = l2->addr;
|
||||
else
|
||||
value = l2->op_value;
|
||||
l->op_value = value;
|
||||
l->label_resolved = 1;
|
||||
if (mii_cpu_op[l->opcodes[0]].desc.branch)
|
||||
l->op_value = l2->addr - l->addr - 2;
|
||||
else if (l->op_low)
|
||||
l->op_value &= 0xff;
|
||||
else if (l->op_high)
|
||||
l->op_value >>= 8;
|
||||
for (int i = 1; i < l->opcode_count; i++)
|
||||
l->opcodes[i] = l->op_value >> (8 * (i - 1));
|
||||
break;
|
||||
}
|
||||
l2 = l2->next;
|
||||
}
|
||||
if (!l->label_resolved) {
|
||||
printf("ERROR: Missing label %d: %s %s %s\n",
|
||||
l->line_index, l->label, l->mnemonic, l->operand);
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
l = l->next;
|
||||
}
|
||||
if (error)
|
||||
return error;
|
||||
p->output_len = p->prog_tail->addr + p->prog_tail->opcode_count - p->org;
|
||||
if (p->verbose)
|
||||
printf("%s program at $%04x size %d bytes\n", __func__,
|
||||
p->org, p->output_len);
|
||||
p->output = calloc(1, p->output_len);
|
||||
l = p->prog;
|
||||
while (l) {
|
||||
for (int i = 0; i < l->opcode_count; i++)
|
||||
p->output[l->addr - p->org + i] = l->opcodes[i];
|
||||
l = l->next;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
int
|
||||
mii_cpu_asm(
|
||||
mii_cpu_asm_program_t *p,
|
||||
const char *prog)
|
||||
{
|
||||
mii_cpu_asm_load(p, prog);
|
||||
|
||||
return mii_cpu_asm_assemble(p);
|
||||
}
|
||||
|
||||
void
|
||||
mii_cpu_asm_free(
|
||||
mii_cpu_asm_program_t *p)
|
||||
{
|
||||
mii_cpu_asm_line_t *l = p->prog;
|
||||
while (l) {
|
||||
mii_cpu_asm_line_t *next = l->next;
|
||||
free(l);
|
||||
l = next;
|
||||
}
|
||||
if (p->output)
|
||||
free(p->output);
|
||||
memset(p, 0, sizeof(*p));
|
||||
}
|
47
src/mii_65c02_asm.h
Normal file
47
src/mii_65c02_asm.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef struct mii_cpu_asm_line_t {
|
||||
struct mii_cpu_asm_line_t *next;
|
||||
struct mii_cpu_asm_line_t *sym_next;
|
||||
uint16_t line_index;
|
||||
uint8_t symbol : 1,
|
||||
label_resolved : 1,
|
||||
addr_set : 1, op_low : 1, op_high : 1;
|
||||
uint16_t addr; // address of the instruction (if resolved)
|
||||
uint8_t mode; // mode of the instruction
|
||||
uint8_t opcode_count; // number of bytes for the opcode
|
||||
uint8_t opcodes[32]; // or .byte statements
|
||||
|
||||
char label[64];
|
||||
char mnemonic[64];
|
||||
char operand[64];
|
||||
|
||||
char op_name[64];
|
||||
int op_value;
|
||||
|
||||
char line[]; // actual line read ends up here, untouched
|
||||
} mii_cpu_asm_line_t;
|
||||
|
||||
typedef struct mii_cpu_asm_program_t {
|
||||
uint8_t verbose;
|
||||
uint16_t org; // origin, can be set before, or with .org
|
||||
mii_cpu_asm_line_t *sym;
|
||||
mii_cpu_asm_line_t *sym_tail;
|
||||
|
||||
mii_cpu_asm_line_t *prog;
|
||||
mii_cpu_asm_line_t *prog_tail;
|
||||
|
||||
uint8_t * output;
|
||||
uint16_t output_len;
|
||||
} mii_cpu_asm_program_t;
|
||||
|
||||
int
|
||||
mii_cpu_asm(
|
||||
mii_cpu_asm_program_t *p,
|
||||
const char *prog);
|
||||
void
|
||||
mii_cpu_asm_free(
|
||||
mii_cpu_asm_program_t *p);
|
124
src/mii_65c02_disasm.c
Normal file
124
src/mii_65c02_disasm.c
Normal file
@ -0,0 +1,124 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "mii_65c02.h"
|
||||
#include "mii_65c02_ops.h"
|
||||
#include "mii_65c02_disasm.h"
|
||||
|
||||
int
|
||||
mii_cpu_disasm_one(
|
||||
const uint8_t *prog,
|
||||
uint16_t addr,
|
||||
char *out,
|
||||
size_t out_len,
|
||||
uint16_t flags)
|
||||
{
|
||||
uint16_t pc = addr;
|
||||
int i = 0;
|
||||
|
||||
uint8_t op = prog[i];
|
||||
mii_op_desc_t d = mii_cpu_op[op].desc;
|
||||
if (!d.pc)
|
||||
d.pc = 1;
|
||||
*out = 0;
|
||||
int len = out_len;
|
||||
if (flags & MII_DUMP_DIS_PC)
|
||||
len -= snprintf(out + strlen(out), len, "%04X: ", pc + i);
|
||||
if (flags & MII_DUMP_DIS_DUMP_HEX) {
|
||||
char hex[32] = {0};
|
||||
for (int oi = 0; oi < d.pc; oi++)
|
||||
sprintf(hex + (oi * 3), "%02X ", prog[i + oi]);
|
||||
len -= snprintf(out + strlen(out), len, "%-9s ", hex);
|
||||
}
|
||||
len -= snprintf(out + strlen(out), len, "%.4s ",
|
||||
mii_cpu_op[op].name[0] ?
|
||||
mii_cpu_op[op].name : "???");
|
||||
i++;
|
||||
switch (d.mode) {
|
||||
case IMM:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"#$%02X", prog[i++]);
|
||||
break;
|
||||
case BRANCH:
|
||||
case ZP_REL:
|
||||
if ((op & 0xf) == 0xf) {
|
||||
uint16_t base = pc + i + 1;
|
||||
uint16_t dest = base + (int8_t)prog[i++];
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"%d,$%04X", d.s_bit, dest);
|
||||
} else if (d.branch) {
|
||||
uint16_t base = pc + i + 1;
|
||||
uint16_t dest = base + (int8_t)prog[i++];
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%04X", dest);
|
||||
} else
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X", prog[i++]);
|
||||
break;
|
||||
case ZP_X:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X,X", prog[i++]);
|
||||
break;
|
||||
case ZP_Y:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X,Y", prog[i++]);
|
||||
break;
|
||||
case ABS:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X%02X", prog[i + 1], prog[i]);
|
||||
i += 2;
|
||||
break;
|
||||
case ABS_X:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X%02X,X", prog[i + 1], prog[i]);
|
||||
i += 2;
|
||||
break;
|
||||
case ABS_Y:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"$%02X%02X,Y", prog[i + 1], prog[i]);
|
||||
i += 2;
|
||||
break;
|
||||
case IND_X:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"($%02X,X)", prog[i++]);
|
||||
break;
|
||||
case IND_AX:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"($%02X%02X,X)", prog[i + 1], prog[i]);
|
||||
i += 2;
|
||||
break;
|
||||
case IND_Y:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"($%02X),Y", prog[i++]);
|
||||
break;
|
||||
case IND_Z:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"($%02X)", prog[i++]);
|
||||
break;
|
||||
case IND:
|
||||
len -= snprintf(out + strlen(out), len,
|
||||
"($%02X%02X)", prog[i + 1], prog[i]);
|
||||
i += 2;
|
||||
break;
|
||||
case IMPLIED:
|
||||
break;
|
||||
}
|
||||
return d.pc;
|
||||
}
|
||||
|
||||
void
|
||||
mii_cpu_disasm(
|
||||
const uint8_t *prog,
|
||||
uint16_t addr,
|
||||
uint16_t len)
|
||||
{
|
||||
uint16_t pc = addr;
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
char out[256] = {0};
|
||||
i += mii_cpu_disasm_one(prog + i, pc + i, out, sizeof(out),
|
||||
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
|
||||
printf("%s\n", out);
|
||||
}
|
||||
}
|
24
src/mii_65c02_disasm.h
Normal file
24
src/mii_65c02_disasm.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
enum {
|
||||
MII_DUMP_DIS_PC = (1 << 0),
|
||||
MII_DUMP_DIS_DUMP_HEX = (1 << 1),
|
||||
};
|
||||
|
||||
|
||||
int
|
||||
mii_cpu_disasm_one(
|
||||
const uint8_t *prog,
|
||||
uint16_t addr,
|
||||
char *out,
|
||||
size_t out_len,
|
||||
uint16_t flags);
|
||||
|
||||
void
|
||||
mii_cpu_disasm(
|
||||
const uint8_t *prog,
|
||||
uint16_t addr,
|
||||
uint16_t len);
|
308
src/mii_65c02_ops.h
Normal file
308
src/mii_65c02_ops.h
Normal file
@ -0,0 +1,308 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct mii_op_desc_t {
|
||||
uint32_t op : 8,
|
||||
mode : 4,
|
||||
pc : 2,
|
||||
branch : 1, // relative branch
|
||||
ch_pc : 1, // change PC
|
||||
s_bit : 3,
|
||||
s_bit_value : 1,
|
||||
r : 1,
|
||||
w : 1;
|
||||
} mii_op_desc_t;
|
||||
|
||||
typedef struct mii_op_t {
|
||||
char name[4];
|
||||
mii_op_desc_t desc;
|
||||
} mii_op_t;
|
||||
|
||||
enum {
|
||||
B_C = 0,
|
||||
B_Z,
|
||||
B_I,
|
||||
B_D,
|
||||
B_B,
|
||||
B_X,
|
||||
B_V,
|
||||
B_N,
|
||||
};
|
||||
|
||||
enum {
|
||||
IMPLIED, // BRK
|
||||
IMM, // LDA #$01
|
||||
ZP_REL, // LDA $C0 or BCC $FF
|
||||
ZP_X, // LDA $C0,X
|
||||
ZP_Y, // LDX $C0,Y
|
||||
ABS, // LDA $1234
|
||||
ABS_X, // LDA $1234,X
|
||||
ABS_Y, // LDA $1234,Y
|
||||
IND_X, // LDA ($FF,X)
|
||||
IND_AX, // JMP ($1234,X)
|
||||
IND_Y, // LDA ($FF),Y
|
||||
IND, // JMP ($1234)
|
||||
IND_Z, // LDA ($C0)
|
||||
BRANCH, // BEQ/BNE etc
|
||||
};
|
||||
|
||||
#define PCODE_(_name, _mode, _op, _pc, _r, _w, _br, _sb, _sv, _cpc) \
|
||||
[_op] = { \
|
||||
.name = #_name, \
|
||||
.desc = { \
|
||||
.op = _op, .mode = _mode, .pc = _pc, .r = _r, .w = _w, \
|
||||
.branch = _br, .s_bit = _sb, .s_bit_value = _sv, .ch_pc = _cpc } \
|
||||
},
|
||||
|
||||
#ifndef MII_CPU_65C02_IMPL
|
||||
extern const mii_op_t mii_cpu_op[256];
|
||||
|
||||
#else // MII_CPU_65C02_IMPL
|
||||
|
||||
#define PCODE_R_(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 1, 0, 0, 0, 0, 0)
|
||||
#define PCODE__W(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 0, 1, 0, 0, 0, 0)
|
||||
|
||||
#define PCODE___(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
#define PCODE_RW(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 1, 1, 0, 0, 0, 0)
|
||||
#define PCODE__W(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 0, 1, 0, 0, 0, 0)
|
||||
|
||||
#define PCODE_BR(_name, _op, _pc, _sb, _sv) \
|
||||
PCODE_(_name, BRANCH, _op, _pc, 0, 0, 1, B_##_sb, _sv, 1)
|
||||
#define PCODE_PC(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 1, 0, 0, 0, 0, 1)
|
||||
#define PCODE_RT(_name, _mode, _op, _pc) \
|
||||
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, 0, 0, 1)
|
||||
|
||||
#define PCODE_SB(_name, _mode, _op, _pc, _sb, _sv) \
|
||||
PCODE_(_name, _mode, _op, _pc, 0, 0, 0, B_##_sb, _sv, 0)
|
||||
// for RMBx SMBx
|
||||
#define PCODE_MB(_name, _op, _sb, _sv) \
|
||||
PCODE_(_name, ZP_REL, _op, 2, 1, 1, 0, _sb, _sv, 0)
|
||||
// for BBRx BBSx
|
||||
#define PCODE_BB(_name, _op, _sb, _sv) \
|
||||
PCODE_(_name, ZP_REL, _op, 3, 1, 0, 1, _sb, _sv, 0)
|
||||
|
||||
const mii_op_t mii_cpu_op[256] = {
|
||||
PCODE___(ADC, IMM, 0x69, 2)
|
||||
PCODE_R_(ADC, ZP_REL, 0x65, 2)
|
||||
PCODE_R_(ADC, ZP_X, 0x75, 2)
|
||||
PCODE_R_(ADC, ABS, 0x6D, 3)
|
||||
PCODE_R_(ADC, ABS_X, 0x7D, 3)
|
||||
PCODE_R_(ADC, ABS_Y, 0x79, 3)
|
||||
PCODE_R_(ADC, IND_X, 0x61, 2)
|
||||
PCODE_R_(ADC, IND_Y, 0x71, 2)
|
||||
PCODE_R_(ADC, IND_Z, 0x72, 2)
|
||||
PCODE___(AND, IMM, 0x29, 2)
|
||||
PCODE_R_(AND, ZP_REL, 0x25, 2)
|
||||
PCODE_R_(AND, ZP_X, 0x35, 2)
|
||||
PCODE_R_(AND, ABS, 0x2D, 3)
|
||||
PCODE_R_(AND, ABS_X, 0x3D, 3)
|
||||
PCODE_R_(AND, ABS_Y, 0x39, 3)
|
||||
PCODE_R_(AND, IND_X, 0x21, 2)
|
||||
PCODE_R_(AND, IND_Y, 0x31, 2)
|
||||
PCODE_R_(AND, IND_Z, 0x32, 2)
|
||||
PCODE___(ASL, IMPLIED, 0x0A, 1)
|
||||
PCODE_RW(ASL, ZP_REL, 0x06, 2)
|
||||
PCODE_RW(ASL, ZP_X, 0x16, 2)
|
||||
PCODE_RW(ASL, ABS, 0x0E, 3)
|
||||
PCODE_RW(ASL, ABS_X, 0x1E, 3)
|
||||
PCODE_BB(BBR0, 0x0F, 0, 0)
|
||||
PCODE_BB(BBR1, 0x1F, 1, 0)
|
||||
PCODE_BB(BBR2, 0x2F, 2, 0)
|
||||
PCODE_BB(BBR3, 0x3F, 3, 0)
|
||||
PCODE_BB(BBR4, 0x4F, 4, 0)
|
||||
PCODE_BB(BBR5, 0x5F, 5, 0)
|
||||
PCODE_BB(BBR6, 0x6F, 6, 0)
|
||||
PCODE_BB(BBR7, 0x7F, 7, 0)
|
||||
PCODE_BB(BBS0, 0x8F, 0, 1)
|
||||
PCODE_BB(BBS1, 0x9F, 1, 1)
|
||||
PCODE_BB(BBS2, 0xAF, 2, 1)
|
||||
PCODE_BB(BBS3, 0xBF, 3, 1)
|
||||
PCODE_BB(BBS4, 0xCF, 4, 1)
|
||||
PCODE_BB(BBS5, 0xDF, 5, 1)
|
||||
PCODE_BB(BBS6, 0xEF, 6, 1)
|
||||
PCODE_BB(BBS7, 0xFF, 7, 1)
|
||||
PCODE_BR(BCC, 0x90, 2, C, 0)
|
||||
PCODE_BR(BCS, 0xB0, 2, C, 1)
|
||||
PCODE_BR(BEQ, 0xF0, 2, Z, 1)
|
||||
PCODE_BR(BMI, 0x30, 2, N, 1)
|
||||
PCODE_BR(BNE, 0xD0, 2, Z, 0)
|
||||
PCODE_BR(BPL, 0x10, 2, N, 0)
|
||||
PCODE_BR(BVC, 0x50, 2, V, 0)
|
||||
PCODE_BR(BVS, 0x70, 2, V, 1)
|
||||
PCODE_BR(BRA, 0x80, 2, X, 1)
|
||||
PCODE_R_(BIT, ZP_REL, 0x24, 2)
|
||||
PCODE_R_(BIT, ABS, 0x2C, 3)
|
||||
PCODE___(BIT, IMM, 0x89, 2)
|
||||
PCODE_R_(BIT, ZP_X, 0x34, 2)
|
||||
PCODE_R_(BIT, ABS_X, 0x3C, 3)
|
||||
PCODE_RT(BRK, IMPLIED, 0x00, 1)
|
||||
PCODE_SB(CLC, IMPLIED, 0x18, 1, C, 0)
|
||||
PCODE_SB(CLD, IMPLIED, 0xD8, 1, D, 0)
|
||||
PCODE_SB(CLI, IMPLIED, 0x58, 1, I, 0)
|
||||
PCODE_SB(CLV, IMPLIED, 0xB8, 1, V, 0)
|
||||
PCODE___(CMP, IMM, 0xC9, 2)
|
||||
PCODE_R_(CMP, ZP_REL, 0xC5, 2)
|
||||
PCODE_R_(CMP, ZP_X, 0xD5, 2)
|
||||
PCODE_R_(CMP, ABS_X, 0xDD, 3)
|
||||
PCODE_R_(CMP, ABS, 0xCD, 3)
|
||||
PCODE_R_(CMP, ABS_Y, 0xD9, 3)
|
||||
PCODE_R_(CMP, IND_X, 0xC1, 2)
|
||||
PCODE_R_(CMP, IND_Y, 0xD1, 2)
|
||||
PCODE_R_(CMP, IND_Z, 0xD2, 2)
|
||||
PCODE___(CPX, IMM, 0xE0, 2)
|
||||
PCODE_R_(CPX, ZP_REL, 0xE4, 2)
|
||||
PCODE_R_(CPX, ABS, 0xEC, 3)
|
||||
PCODE___(CPY, IMM, 0xC0, 2)
|
||||
PCODE_R_(CPY, ZP_REL, 0xC4, 2)
|
||||
PCODE_R_(CPY, ABS, 0xCC, 3)
|
||||
PCODE___(DEC, IMPLIED, 0x3A, 1)
|
||||
PCODE_RW(DEC, ZP_REL, 0xC6, 2)
|
||||
PCODE_RW(DEC, ZP_X, 0xD6, 2)
|
||||
PCODE_RW(DEC, ABS, 0xCE, 3)
|
||||
PCODE_RW(DEC, ABS_X, 0xDE, 3)
|
||||
PCODE___(DEX, IMPLIED, 0xCA, 1)
|
||||
PCODE___(DEY, IMPLIED, 0x88, 1)
|
||||
PCODE___(EOR, IMM, 0x49, 2)
|
||||
PCODE_R_(EOR, ZP_REL, 0x45, 2)
|
||||
PCODE_R_(EOR, ZP_X, 0x55, 2)
|
||||
PCODE_R_(EOR, ABS, 0x4D, 3)
|
||||
PCODE_R_(EOR, ABS_X, 0x5D, 3)
|
||||
PCODE_R_(EOR, ABS_Y, 0x59, 3)
|
||||
PCODE_R_(EOR, IND_X, 0x41, 2)
|
||||
PCODE_R_(EOR, IND_Y, 0x51, 2)
|
||||
PCODE_R_(EOR, IND_Z, 0x52, 2)
|
||||
PCODE___(INC, IMPLIED, 0x1A, 1)
|
||||
PCODE_RW(INC, ZP_REL, 0xE6, 2)
|
||||
PCODE_RW(INC, ZP_X, 0xF6, 2)
|
||||
PCODE_RW(INC, ABS, 0xEE, 3)
|
||||
PCODE_RW(INC, ABS_X, 0xFE, 3)
|
||||
PCODE___(INX, IMPLIED, 0xE8, 1)
|
||||
PCODE___(INY, IMPLIED, 0xC8, 1)
|
||||
PCODE_PC(JMP, ABS, 0x4C, 3)
|
||||
PCODE_PC(JMP, IND, 0x6C, 3)
|
||||
PCODE_PC(JMP, IND_AX, 0x7C, 3)
|
||||
PCODE_PC(JSR, ABS, 0x20, 3)
|
||||
PCODE___(LDA, IMM, 0xA9, 2)
|
||||
PCODE_R_(LDA, ZP_REL, 0xA5, 2)
|
||||
PCODE_R_(LDA, ZP_X, 0xB5, 2)
|
||||
PCODE_R_(LDA, ABS, 0xAD, 3)
|
||||
PCODE_R_(LDA, ABS_X, 0xBD, 3)
|
||||
PCODE_R_(LDA, ABS_Y, 0xB9, 3)
|
||||
PCODE_R_(LDA, IND_X, 0xA1, 2)
|
||||
PCODE_R_(LDA, IND_Y, 0xB1, 2)
|
||||
PCODE_R_(LDA, IND_Z, 0xB2, 2)
|
||||
PCODE___(LDX, IMM, 0xA2, 2)
|
||||
PCODE_R_(LDX, ZP_REL, 0xA6, 2)
|
||||
PCODE_R_(LDX, ZP_Y, 0xB6, 2)
|
||||
PCODE_R_(LDX, ABS, 0xAE, 3)
|
||||
PCODE_R_(LDX, ABS_Y, 0xBE, 3)
|
||||
PCODE___(LDY, IMM, 0xA0, 2)
|
||||
PCODE_R_(LDY, ZP_REL, 0xA4, 2)
|
||||
PCODE_R_(LDY, ZP_X, 0xB4, 2)
|
||||
PCODE_R_(LDY, ABS, 0xAC, 3)
|
||||
PCODE_R_(LDY, ABS_X, 0xBC, 3)
|
||||
PCODE___(LSR, IMPLIED, 0x4A, 1)
|
||||
PCODE_RW(LSR, ZP_REL, 0x46, 2)
|
||||
PCODE_RW(LSR, ZP_X, 0x56, 2)
|
||||
PCODE_RW(LSR, ABS, 0x4E, 3)
|
||||
PCODE_RW(LSR, ABS_X, 0x5E, 3)
|
||||
PCODE___(NOP, IMPLIED, 0xEA, 1)
|
||||
PCODE___(ORA, IMM, 0x09, 2)
|
||||
PCODE_R_(ORA, ZP_REL, 0x05, 2)
|
||||
PCODE_R_(ORA, ZP_X, 0x15, 2)
|
||||
PCODE_R_(ORA, ABS, 0x0D, 3)
|
||||
PCODE_R_(ORA, ABS_X, 0x1D, 3)
|
||||
PCODE_R_(ORA, ABS_Y, 0x19, 3)
|
||||
PCODE_R_(ORA, IND_X, 0x01, 2)
|
||||
PCODE_R_(ORA, IND_Y, 0x11, 2)
|
||||
PCODE_R_(ORA, IND_Z, 0x12, 2)
|
||||
PCODE___(PHA, IMPLIED, 0x48, 1)
|
||||
PCODE___(PHP, IMPLIED, 0x08, 1)
|
||||
PCODE___(PHX, IMPLIED, 0xDA, 1)
|
||||
PCODE___(PHY, IMPLIED, 0x5A, 1)
|
||||
PCODE___(PLA, IMPLIED, 0x68, 1)
|
||||
PCODE___(PLP, IMPLIED, 0x28, 1)
|
||||
PCODE___(PLX, IMPLIED, 0xFA, 1)
|
||||
PCODE___(PLY, IMPLIED, 0x7A, 1)
|
||||
// these fill the opcode name without terminating zero
|
||||
PCODE_MB(RMB0, 0x07, 0, 0)
|
||||
PCODE_MB(RMB1, 0x17, 1, 0)
|
||||
PCODE_MB(RMB2, 0x27, 2, 0)
|
||||
PCODE_MB(RMB3, 0x37, 3, 0)
|
||||
PCODE_MB(RMB4, 0x47, 4, 0)
|
||||
PCODE_MB(RMB5, 0x57, 5, 0)
|
||||
PCODE_MB(RMB6, 0x67, 6, 0)
|
||||
PCODE_MB(RMB7, 0x77, 7, 0)
|
||||
PCODE___(ROL, IMPLIED, 0x2A, 1)
|
||||
PCODE_RW(ROL, ZP_REL, 0x26, 2)
|
||||
PCODE_RW(ROL, ZP_X, 0x36, 2)
|
||||
PCODE_RW(ROL, ABS, 0x2E, 3)
|
||||
PCODE_RW(ROL, ABS_X, 0x3E, 3)
|
||||
PCODE___(ROR, IMPLIED, 0x6A, 1)
|
||||
PCODE_RW(ROR, ZP_REL, 0x66, 2)
|
||||
PCODE_RW(ROR, ZP_X, 0x76, 2)
|
||||
PCODE_RW(ROR, ABS, 0x6E, 3)
|
||||
PCODE_RW(ROR, ABS_X, 0x7E, 3)
|
||||
PCODE_RT(RTI, IMPLIED, 0x40, 1)
|
||||
PCODE_RT(RTS, IMPLIED, 0x60, 1)
|
||||
PCODE___(SBC, IMM, 0xE9, 2)
|
||||
PCODE_R_(SBC, ZP_REL, 0xE5, 2)
|
||||
PCODE_R_(SBC, ZP_X, 0xF5, 2)
|
||||
PCODE_R_(SBC, ABS, 0xED, 3)
|
||||
PCODE_R_(SBC, ABS_X, 0xFD, 3)
|
||||
PCODE_R_(SBC, ABS_Y, 0xF9, 3)
|
||||
PCODE_R_(SBC, IND_X, 0xE1, 2)
|
||||
PCODE_R_(SBC, IND_Y, 0xF1, 2)
|
||||
PCODE_R_(SBC, IND_Z, 0xF2, 2)
|
||||
PCODE_SB(SEC, IMPLIED, 0x38, 1, C, 1)
|
||||
PCODE_SB(SED, IMPLIED, 0xF8, 1, D, 1)
|
||||
PCODE_SB(SEI, IMPLIED, 0x78, 1, I, 1)
|
||||
// these fill the opcode name without terminating zero
|
||||
PCODE_MB(SMB0, 0x87, 0, 1)
|
||||
PCODE_MB(SMB1, 0x97, 1, 1)
|
||||
PCODE_MB(SMB2, 0xa7, 2, 1)
|
||||
PCODE_MB(SMB3, 0xb7, 3, 1)
|
||||
PCODE_MB(SMB4, 0xc7, 4, 1)
|
||||
PCODE_MB(SMB5, 0xd7, 5, 1)
|
||||
PCODE_MB(SMB6, 0xe7, 6, 1)
|
||||
PCODE_MB(SMB7, 0xf7, 7, 1)
|
||||
PCODE__W(STA, ZP_REL, 0x85, 2)
|
||||
PCODE__W(STA, ZP_X, 0x95, 2)
|
||||
PCODE__W(STA, ABS, 0x8D, 3)
|
||||
PCODE__W(STA, ABS_X, 0x9D, 3)
|
||||
PCODE__W(STA, ABS_Y, 0x99, 3)
|
||||
PCODE__W(STA, IND_X, 0x81, 2)
|
||||
PCODE__W(STA, IND_Y, 0x91, 2)
|
||||
PCODE__W(STA, IND_Z, 0x92, 2)
|
||||
PCODE__W(STX, ZP_REL, 0x86, 2)
|
||||
PCODE__W(STX, ZP_Y, 0x96, 2)
|
||||
PCODE__W(STX, ABS, 0x8E, 3)
|
||||
PCODE__W(STY, ZP_REL, 0x84, 2)
|
||||
PCODE__W(STY, ZP_X, 0x94, 2)
|
||||
PCODE__W(STY, ABS, 0x8C, 3)
|
||||
PCODE__W(STZ, ZP_REL, 0x64, 2)
|
||||
PCODE__W(STZ, ZP_X, 0x74, 2)
|
||||
PCODE__W(STZ, ABS, 0x9C, 3)
|
||||
PCODE__W(STZ, ABS_X, 0x9E, 3)
|
||||
PCODE___(TAX, IMPLIED, 0xAA, 1)
|
||||
PCODE___(TAY, IMPLIED, 0xA8, 1)
|
||||
PCODE_RW(TRB, ZP_REL, 0x14, 2)
|
||||
PCODE_RW(TRB, ABS, 0x1c, 3)
|
||||
PCODE___(TSX, IMPLIED, 0xBA, 1)
|
||||
PCODE_RW(TSB, ZP_REL, 0x04, 2)
|
||||
PCODE_RW(TSB, ABS, 0x0c, 3)
|
||||
PCODE___(TXA, IMPLIED, 0x8A, 1)
|
||||
PCODE___(TXS, IMPLIED, 0x9A, 1)
|
||||
PCODE___(TYA, IMPLIED, 0x98, 1)
|
||||
};
|
||||
|
||||
|
||||
#endif // MII_CPU_65C02_IMPL
|
199
src/mii_argv.c
Normal file
199
src/mii_argv.c
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* mii_argv.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
|
||||
extern mii_slot_drv_t * mii_slot_drv_list;
|
||||
|
||||
static void
|
||||
_mii_usage(
|
||||
const char *progname)
|
||||
{
|
||||
printf("Usage: %s [options]\n", progname);
|
||||
printf("Options:\n");
|
||||
printf(" -h, --help\tThis help\n");
|
||||
printf(" -v, --verbose\tVerbose output\n");
|
||||
printf(" -m, --mute\tMute the speaker\n");
|
||||
printf(" -vol, --volume <volume>\tSet speaker volume (0.0 to 10.0)\n");
|
||||
printf(" -speed, --speed <speed>\tSet the CPU speed in MHz\n");
|
||||
printf(" -s, --slot <slot>:<driver>\tSpecify a slot and driver\n");
|
||||
printf("\t\tSlot id is 1..7\n");
|
||||
printf(" -d, --drive <slot>:<drive>:<filename>\tLoad a drive\n");
|
||||
printf("\t\tSlot id is 1..7, drive is 1..2\n");
|
||||
printf("\t\tAlternate syntax: <slot>:<drive> <filename>\n");
|
||||
printf(" -L, --list-drivers\tList available drivers, exit\n");
|
||||
printf(" -def, --default\tUse a set of default cards:\n");
|
||||
printf("\t\tSlot 4: mouse\n");
|
||||
printf("\t\tSlot 6: disk2\n");
|
||||
printf("\t\tSlot 7: smartport\n");
|
||||
printf(" -nsc[=0|1]\tEnable/Disable No Slot Clock:\n");
|
||||
printf("\t\t0: disable\n");
|
||||
printf("\t\t1: enable [Enabled by default]\n");
|
||||
printf(" -titan[=0|1]\tEnable/Disable Titan Accelerator IIe:\n");
|
||||
printf("\t\t0: disable [default]\n");
|
||||
printf("\t\t1: enable [Start at 3.58MHz]\n");
|
||||
}
|
||||
|
||||
int
|
||||
mii_argv_parse(
|
||||
mii_t *mii,
|
||||
int argc,
|
||||
const char *argv[],
|
||||
int *index,
|
||||
uint32_t *ioFlags)
|
||||
{
|
||||
if (*index == 0)
|
||||
*index += 1;
|
||||
for (int i = *index; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
|
||||
_mii_usage(argv[0]);
|
||||
exit(0);
|
||||
} else if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
|
||||
// mii->verbose++;
|
||||
// continue;
|
||||
} else if ((!strcmp(arg, "-s") || !strcmp(arg, "--slot")) && i < argc-1) {
|
||||
// for mat for slot is 1..8:<name> where name is the driver name
|
||||
const char *p = argv[++i];
|
||||
int slot = 0;
|
||||
char *drv = NULL;
|
||||
if (sscanf(p, "%d:%ms", &slot, &drv) != 2) {
|
||||
printf("mii: invalid slot specification %s\n", p);
|
||||
return 1;
|
||||
}
|
||||
if (slot < 1 || slot > 8) {
|
||||
printf("mii: invalid slot number %d\n", slot);
|
||||
return 1;
|
||||
}
|
||||
if (drv == NULL) {
|
||||
printf("mii: missing driver name for slot %d\n", slot);
|
||||
return 1;
|
||||
}
|
||||
if (mii_slot_drv_register(mii, slot, drv) < 0) {
|
||||
printf("mii: failed to register driver %s for slot %d\n", drv, slot);
|
||||
return 1;
|
||||
}
|
||||
} else if ((!strcmp(arg, "-d") || !strcmp(arg, "--drive")) && i < argc-1) {
|
||||
// drive takes 2 following arguments, the <slot>:<drive> and a filename
|
||||
const char *p = argv[++i];
|
||||
int slot = 0;
|
||||
int drive = 0;
|
||||
const char *filename = NULL;
|
||||
int got = sscanf(p, "%d:%d:%ms", &slot, &drive, &filename);
|
||||
if (got == 2) {
|
||||
if (i < argc-1) {
|
||||
filename = argv[++i];
|
||||
got = 3;
|
||||
} else {
|
||||
printf("mii: missing filename for drive %d:%d\n", slot, drive);
|
||||
return 1;
|
||||
}
|
||||
} else if (got != 3) {
|
||||
printf("mii: invalid drive specification %s\n", p);
|
||||
return 1;
|
||||
}
|
||||
if (slot < 1 || slot > 8) {
|
||||
printf("mii: invalid slot number %d\n", slot);
|
||||
return 1;
|
||||
}
|
||||
if (drive < 1 || drive > 2) {
|
||||
printf("mii: invalid drive number %d\n", drive);
|
||||
return 1;
|
||||
}
|
||||
if (filename == NULL) {
|
||||
printf("mii: missing filename for drive %d:%d\n", slot, drive);
|
||||
return 1;
|
||||
}
|
||||
mii_slot_command(mii, slot,
|
||||
MII_SLOT_DRIVE_LOAD + drive - 1,
|
||||
(void*)filename);
|
||||
} else if (!strcmp(arg, "-def") || !strcmp(arg, "--default")) {
|
||||
mii_slot_drv_register(mii, 4, "mouse");
|
||||
mii_slot_drv_register(mii, 6, "disk2");
|
||||
mii_slot_drv_register(mii, 7, "smartport");
|
||||
} else if (!strcmp(arg, "-L") || !strcmp(arg, "--list-drivers")) {
|
||||
mii_slot_drv_t * drv = mii_slot_drv_list;
|
||||
printf("mii: available drivers:\n");
|
||||
while (drv) {
|
||||
printf("%10.10s - %s\n", drv->name, drv->desc);
|
||||
drv = drv->next;
|
||||
}
|
||||
exit(0);
|
||||
} else if (!strcmp(arg, "-m") || !strcmp(arg, "--mute")) {
|
||||
mii->speaker.muted = true;
|
||||
} else if (!strcmp(arg, "-vol") || !strcmp(arg, "--volume")) {
|
||||
if (i < argc-1) {
|
||||
float vol = atof(argv[++i]);
|
||||
if (vol < 0) vol = 0;
|
||||
else if (vol > 10) vol = 10;
|
||||
mii_speaker_volume(&mii->speaker, vol);
|
||||
} else {
|
||||
printf("mii: missing volume value\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (!strcmp(arg, "-speed") || !strcmp(arg, "--speed")) {
|
||||
if (i < argc-1) {
|
||||
mii->speed = atof(argv[++i]);
|
||||
if (mii->speed < 0.0)
|
||||
mii->speed = 1.0;
|
||||
} else {
|
||||
printf("mii: missing speed value\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (argv[i][0] == '-') {
|
||||
char dup[128];
|
||||
snprintf(dup, sizeof(dup), "%s", argv[i] + 1);
|
||||
char *equal = dup;
|
||||
char *name = strsep(&equal, "=");
|
||||
int enable = 1;
|
||||
if (equal && *equal) {
|
||||
if (!strcmp(equal, "0"))
|
||||
enable = 0;
|
||||
else if (!strcmp(equal, "1"))
|
||||
enable = 1;
|
||||
else {
|
||||
printf("mii: invalid flag %s\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// printf("%s lookup driver %s to %s\n", __func__,
|
||||
// name, enable ? "enable" : "disable");
|
||||
mii_slot_drv_t * drv = mii_slot_drv_list;
|
||||
int done = 0;
|
||||
while (drv) {
|
||||
printf("%10.10s - %s\n", drv->name, drv->desc);
|
||||
if (drv->enable_flag) {
|
||||
if (!strcmp(name, drv->name)) {
|
||||
*ioFlags = (*ioFlags & ~drv->enable_flag) |
|
||||
(enable ? drv->enable_flag : 0);
|
||||
printf("mii: %s %s\n", name, enable ? "enabled" : "disabled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
drv = drv->next;
|
||||
}
|
||||
if (!done && equal) {
|
||||
printf("mii: no driver found %s\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
printf("mii: unknown argument %s\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
*index = argc;
|
||||
return 1;
|
||||
}
|
105
src/mii_bank.c
Normal file
105
src/mii_bank.c
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* mii_bank.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
|
||||
|
||||
void
|
||||
mii_bank_write(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
const uint8_t *data,
|
||||
uint16_t len)
|
||||
{
|
||||
if ((addr < bank->base) ||
|
||||
((addr + len) > (uint32_t)(bank->base + (bank->size * 256)))) {
|
||||
printf("%s %s INVALID write addr %04x len %d %04x:%04x\n",
|
||||
__func__, bank->name, addr, (int)len,
|
||||
bank->base, bank->base + (bank->size * 256));
|
||||
return;
|
||||
}
|
||||
uint8_t page_index = (addr - bank->base) >> 8;
|
||||
if (bank->access && bank->access[page_index].cb) {
|
||||
if (bank->access[page_index].cb(bank, bank->access[page_index].param,
|
||||
addr, (uint8_t *)data, true))
|
||||
return;
|
||||
}
|
||||
if (!bank->mem) {
|
||||
bank->alloc = 1;
|
||||
bank->mem = calloc(1, bank->size * 256);
|
||||
}
|
||||
addr -= bank->base;
|
||||
for (uint16_t i = 0; i < len; i++, addr++) {
|
||||
bank->mem[addr] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mii_bank_read(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint8_t *data,
|
||||
uint16_t len)
|
||||
{
|
||||
if (addr < bank->base ||
|
||||
(addr + len) > (uint32_t)(bank->base + (bank->size * 256))) {
|
||||
printf("%s %s INVALID read addr %04x len %d %04x-%04x\n",
|
||||
__func__, bank->name, addr, (int)len,
|
||||
bank->base, bank->base + (bank->size * 256));
|
||||
return;
|
||||
}
|
||||
uint8_t page_index = (addr - bank->base) >> 8;
|
||||
if (bank->access && bank->access[page_index].cb) {
|
||||
if (bank->access[page_index].cb(bank, bank->access[page_index].param,
|
||||
addr, data, false))
|
||||
return;
|
||||
}
|
||||
if (!bank->mem) {
|
||||
bank->alloc = 1;
|
||||
bank->mem = calloc(1, bank->size * 256);
|
||||
}
|
||||
addr -= bank->base;
|
||||
for (uint16_t i = 0; i < len; i++, addr++) {
|
||||
data[i] = bank->mem[addr];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mii_bank_install_access_cb(
|
||||
mii_bank_t *bank,
|
||||
mii_bank_access_cb cb,
|
||||
void *param,
|
||||
uint8_t page,
|
||||
uint8_t end)
|
||||
{
|
||||
if (!end)
|
||||
end = page;
|
||||
if ((page << 8) < bank->base || (end << 8) > (bank->base + bank->size * 256)) {
|
||||
printf("%s %s INVALID install access cb %p param %p page %02x-%02x\n",
|
||||
__func__, bank->name, cb, param, page, end);
|
||||
return;
|
||||
}
|
||||
page -= bank->base >> 8;
|
||||
end -= bank->base >> 8;
|
||||
if (!bank->access) {
|
||||
bank->access = calloc(1, bank->size * sizeof(bank->access[0]));
|
||||
}
|
||||
printf("%s %s install access cb page %02x:%02x\n",
|
||||
__func__, bank->name, page, end);
|
||||
for (int i = page; i <= end; i++) {
|
||||
bank->access[i].cb = cb;
|
||||
bank->access[i].param = param;
|
||||
}
|
||||
}
|
81
src/mii_bank.h
Normal file
81
src/mii_bank.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* mii_bank.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mii_bank_t;
|
||||
typedef bool (*mii_bank_access_cb)(
|
||||
struct mii_bank_t *bank,
|
||||
void *param,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write);
|
||||
|
||||
typedef struct mii_bank_access_t {
|
||||
mii_bank_access_cb cb;
|
||||
void *param;
|
||||
} mii_bank_access_t;
|
||||
|
||||
typedef struct mii_bank_t {
|
||||
uint64_t base : 16, // base address
|
||||
size : 9, // in pages
|
||||
alloc : 1, // been calloced()
|
||||
ro : 1; // read only
|
||||
char * name;
|
||||
mii_bank_access_t * access;
|
||||
uint8_t *mem;
|
||||
} mii_bank_t;
|
||||
|
||||
void
|
||||
mii_bank_write(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
const uint8_t *data,
|
||||
uint16_t len);
|
||||
void
|
||||
mii_bank_read(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
uint8_t *data,
|
||||
uint16_t len);
|
||||
|
||||
/* return the number of pages dirty (written into since last time) between
|
||||
* addr1 and addr2 (inclusive) */
|
||||
uint8_t
|
||||
mii_bank_is_dirty(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr1,
|
||||
uint16_t addr2);
|
||||
void
|
||||
mii_bank_install_access_cb(
|
||||
mii_bank_t *bank,
|
||||
mii_bank_access_cb cb,
|
||||
void *param,
|
||||
uint8_t page,
|
||||
uint8_t end);
|
||||
|
||||
static inline void
|
||||
mii_bank_poke(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr,
|
||||
const uint8_t data)
|
||||
{
|
||||
mii_bank_write(bank, addr, &data, 1);
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
mii_bank_peek(
|
||||
mii_bank_t *bank,
|
||||
uint16_t addr)
|
||||
{
|
||||
uint8_t res = 0;
|
||||
mii_bank_read(bank, addr, &res, 1);
|
||||
return res;
|
||||
}
|
86
src/mii_slot.c
Normal file
86
src/mii_slot.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* mii_slot.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
|
||||
mii_slot_drv_t *
|
||||
mii_slot_drv_find(
|
||||
mii_t *mii,
|
||||
const char * name)
|
||||
{
|
||||
mii_slot_drv_t * drv = mii_slot_drv_list;
|
||||
while (drv) {
|
||||
if (!strcmp(drv->name, name))
|
||||
return drv;
|
||||
drv = drv->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
mii_slot_drv_register(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id,
|
||||
const char *driver_name)
|
||||
{
|
||||
if (!mii || !driver_name) {
|
||||
printf("%s invalid args\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
if (slot_id < 1 || slot_id > 7) {
|
||||
printf("%s invalid slot id %d\n", __func__, slot_id);
|
||||
return -1;
|
||||
}
|
||||
if (mii->slot[slot_id - 1].drv) {
|
||||
printf("%s slot %d already has a driver (%s)\n",
|
||||
__func__, slot_id, mii->slot[slot_id - 1].drv->name);
|
||||
return -1;
|
||||
}
|
||||
mii_slot_drv_t * drv = mii_slot_drv_find(mii, driver_name);
|
||||
if (!drv) {
|
||||
printf("%s driver %s not found\n", __func__, driver_name);
|
||||
return -1;
|
||||
}
|
||||
if (drv->init) {
|
||||
if (drv->init(mii, &mii->slot[slot_id - 1]) != 0) {
|
||||
printf("%s driver %s init failed\n", __func__, driver_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
mii->slot[slot_id - 1].drv = drv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mii_slot_drv_t *
|
||||
mii_slot_drv_get(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id)
|
||||
{
|
||||
if (!mii || slot_id < 1 || slot_id > 7)
|
||||
return NULL;
|
||||
return (mii_slot_drv_t *)mii->slot[slot_id - 1].drv;
|
||||
}
|
||||
|
||||
int
|
||||
mii_slot_command(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id,
|
||||
uint8_t cmd,
|
||||
void * param)
|
||||
{
|
||||
mii_slot_drv_t * drv = mii_slot_drv_get(mii, slot_id);
|
||||
if (!drv)
|
||||
return -1;
|
||||
if (drv->command)
|
||||
return drv->command(mii, &mii->slot[slot_id - 1], cmd, param);
|
||||
return -1;
|
||||
}
|
74
src/mii_slot.h
Normal file
74
src/mii_slot.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* mii_slot.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct mii_slot_drv_t mii_slot_drv_t;
|
||||
|
||||
typedef struct mii_slot_t {
|
||||
uint8_t id;
|
||||
void * drv_priv; // for driver use
|
||||
const mii_slot_drv_t * drv;
|
||||
} mii_slot_t;
|
||||
|
||||
typedef struct mii_t mii_t;
|
||||
|
||||
/*
|
||||
* That won't handle secondary ROM banks, we'll do that if we ever need it
|
||||
*/
|
||||
typedef struct mii_slot_drv_t {
|
||||
struct mii_slot_drv_t *next; // in global driver queue
|
||||
uint32_t enable_flag; // if there is a MII_INIT_xxx flag
|
||||
const char * name;
|
||||
const char * desc;
|
||||
int (*probe)(mii_t * mii, uint32_t flags);
|
||||
int (*init)(mii_t * mii, struct mii_slot_t *slot);
|
||||
void (*reset)(mii_t * mii, struct mii_slot_t *slot); /* optional */
|
||||
void (*run)(mii_t * mii, struct mii_slot_t *slot); /* optional */
|
||||
uint8_t (*access)(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot,
|
||||
uint16_t addr, uint8_t data, bool write);
|
||||
// arbitrary command for load/save etc /* optional */
|
||||
int (*command)(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot,
|
||||
uint8_t cmd,
|
||||
void * param);
|
||||
} mii_slot_drv_t;
|
||||
|
||||
// get driver installed in slot_id
|
||||
mii_slot_drv_t *
|
||||
mii_slot_drv_get(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id);
|
||||
// install driver 'driver_name' in slot_id slot
|
||||
int
|
||||
mii_slot_drv_register(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id,
|
||||
const char *driver_name);
|
||||
// find a driver 'name' in the global list
|
||||
mii_slot_drv_t *
|
||||
mii_slot_drv_find(
|
||||
mii_t *mii,
|
||||
const char * name);
|
||||
|
||||
enum {
|
||||
MII_SLOT_DRIVE_COUNT = 0x01,
|
||||
MII_SLOT_DRIVE_LOAD = 0x20, // + drive index 0...n
|
||||
};
|
||||
|
||||
// send a command to a slot/driver. Return >=0 if ok, -1 if error
|
||||
int
|
||||
mii_slot_command(
|
||||
mii_t *mii,
|
||||
uint8_t slot_id,
|
||||
uint8_t cmd,
|
||||
void * param);
|
212
src/mii_speaker.c
Normal file
212
src/mii_speaker.c
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* mii_speaker.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_speaker.h"
|
||||
|
||||
// one frame of audio per frame of video?
|
||||
#define MII_SPEAKER_FRAME_SIZE (MII_SPEAKER_FREQ / 60)
|
||||
|
||||
// TODO Make some sort of driver for audio and move alsa code there
|
||||
#ifdef HAS_ALSA
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define PCM_DEVICE "default"
|
||||
|
||||
static int
|
||||
_alsa_init(
|
||||
mii_speaker_t *s)
|
||||
{
|
||||
int pcm;
|
||||
unsigned int rate = 44100, channels = 1;
|
||||
snd_pcm_t *alsa;
|
||||
snd_pcm_hw_params_t *params;
|
||||
snd_pcm_uframes_t frames;
|
||||
|
||||
/* Open the PCM device in playback mode */
|
||||
if ((pcm = snd_pcm_open(&alsa, PCM_DEVICE,
|
||||
SND_PCM_STREAM_PLAYBACK, 0)) < 0)
|
||||
printf("ERROR: Can't open \"%s\" PCM device. %s\n",
|
||||
PCM_DEVICE, snd_strerror(pcm));
|
||||
|
||||
/* Allocate parameters object and fill it with default values*/
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
snd_pcm_hw_params_any(alsa, params);
|
||||
|
||||
/* Set parameters */
|
||||
if ((pcm = snd_pcm_hw_params_set_access(alsa, params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
printf("ERROR: Can't set interleaved mode. %s\n",
|
||||
snd_strerror(pcm));
|
||||
|
||||
if ((pcm = snd_pcm_hw_params_set_format(alsa, params,
|
||||
SND_PCM_FORMAT_S16_LE)) < 0)
|
||||
printf("ERROR: Can't set format. %s\n",
|
||||
snd_strerror(pcm));
|
||||
if ((pcm = snd_pcm_hw_params_set_channels(alsa, params, channels)) < 0)
|
||||
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
|
||||
|
||||
if ((pcm = snd_pcm_hw_params_set_rate_near(alsa, params, &rate, 0)) < 0)
|
||||
printf("ERROR: Can't set rate. %s\n",
|
||||
snd_strerror(pcm));
|
||||
frames = MII_SPEAKER_FRAME_SIZE;
|
||||
/* Write parameters */
|
||||
if ((pcm = snd_pcm_hw_params(alsa, params)) < 0)
|
||||
printf("ERROR: Can't set harware parameters. %s\n",
|
||||
snd_strerror(pcm));
|
||||
// printf("%s frames want %d got %ld\n",
|
||||
// __func__, MII_SPEAKER_FRAME_SIZE, frames);
|
||||
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_sw_params_alloca (&sw_params);
|
||||
snd_pcm_sw_params_current (alsa, sw_params);
|
||||
snd_pcm_sw_params_set_start_threshold(alsa, sw_params, frames * 4);
|
||||
snd_pcm_sw_params_set_avail_min(alsa, sw_params, frames*4);
|
||||
snd_pcm_sw_params(alsa, sw_params);
|
||||
|
||||
s->fsize = frames;
|
||||
s->alsa_pcm = alsa;
|
||||
snd_pcm_prepare(s->alsa_pcm);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize the speaker with the frame size in samples
|
||||
void
|
||||
mii_speaker_init(
|
||||
struct mii_t * mii,
|
||||
mii_speaker_t *s)
|
||||
{
|
||||
s->mii = mii;
|
||||
s->fsize = MII_SPEAKER_FRAME_SIZE;
|
||||
#ifdef HAS_ALSA
|
||||
_alsa_init(s); // this can/will change fsize
|
||||
#endif
|
||||
s->vol_multiplier = 0.2;
|
||||
s->sample = 0x8000;
|
||||
s->findex = 0;
|
||||
for (int i = 0; i < MII_SPEAKER_FRAME_COUNT; i++)
|
||||
s->frame[i].audio = calloc(sizeof(s->frame[i].audio[0]), s->fsize);
|
||||
s->frame[0].start = mii->cycles;
|
||||
}
|
||||
|
||||
// Called when $c030 is touched, place a sample at the 'appropriate' time
|
||||
void
|
||||
mii_speaker_click(
|
||||
mii_speaker_t *s)
|
||||
{
|
||||
// if CPU speed has changed, recalculate the number of cycles per sample
|
||||
if (s->cpu_speed != s->mii->speed) {
|
||||
s->cpu_speed = s->mii->speed;
|
||||
s->clk_per_sample = ((1000000.0 /* / s->mii->speed */) /
|
||||
(float)MII_SPEAKER_FREQ) + 0.5f;
|
||||
printf("%s: %d cycles per sample\n", __func__, s->clk_per_sample);
|
||||
}
|
||||
mii_audio_frame_t *f = &s->frame[s->findex];
|
||||
// if we had stopped playing for 2 frames, restart
|
||||
if (f->start == 0 ||
|
||||
(s->mii->cycles - f->start) > (2 * s->fsize * s->clk_per_sample)) {
|
||||
// printf("Restarting playback\n");
|
||||
#ifdef HAS_ALSA
|
||||
snd_pcm_prepare(s->alsa_pcm);
|
||||
#endif
|
||||
f->start = s->mii->cycles - (s->clk_per_sample * 8);
|
||||
f->fill = 0;
|
||||
// add a small attack to the start of the frame to soften the beeps
|
||||
// we are going to flip the sample, so we need to preemptively
|
||||
// flip the attack as well
|
||||
mii_audio_sample_t attack = s->sample ^ 0xffff;
|
||||
for (int i = 8; i >= 1; i--)
|
||||
f->audio[f->fill++] = (attack / i) * s->vol_multiplier;
|
||||
s->fplay = s->findex; // restart here
|
||||
}
|
||||
|
||||
long sample_index = (s->mii->cycles - f->start) / s->clk_per_sample;
|
||||
// fill from last sample to here with the current sample
|
||||
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
|
||||
f->audio[f->fill] = s->sample * s->vol_multiplier;
|
||||
|
||||
// if we've gone past the end of the frame, switch to the next one
|
||||
if (sample_index >= s->fsize) {
|
||||
sample_index = sample_index % s->fsize;
|
||||
__uint128_t newstart = s->mii->cycles - (sample_index * s->clk_per_sample);
|
||||
s->findex = (s->findex + 1) % MII_SPEAKER_FRAME_COUNT;
|
||||
f = &s->frame[s->findex];
|
||||
f->start = newstart;
|
||||
f->fill = 0;
|
||||
// fill from start of this frame to newly calculated sample_index
|
||||
for (; f->fill < sample_index && f->fill < s->fsize; f->fill++)
|
||||
f->audio[f->fill] = s->sample * s->vol_multiplier;
|
||||
}
|
||||
s->sample ^= 0xffff;
|
||||
f->audio[sample_index] = s->sample * s->vol_multiplier;
|
||||
}
|
||||
|
||||
int g_mii_audio_record_fd = -1;
|
||||
|
||||
// Check to see if there's a new frame to send, send it
|
||||
void
|
||||
mii_speaker_run(
|
||||
mii_speaker_t *s)
|
||||
{
|
||||
mii_audio_frame_t *f = &s->frame[s->fplay];
|
||||
|
||||
// here we check if the frame we want to play is filled, and if it's
|
||||
// been long enough since we started filling it to be sure we have
|
||||
// enough samples to play.
|
||||
// There's also the case were we stopped playing and the last frame
|
||||
// wasn't complete, in which case we flush it as well
|
||||
if (f->fill && ((s->mii->cycles - f->start) >
|
||||
(s->fsize * s->clk_per_sample * 2))) {
|
||||
s->fplaying = s->fplay;
|
||||
s->fplay = (s->fplay + 1) % MII_SPEAKER_FRAME_COUNT;
|
||||
f = &s->frame[s->fplaying];
|
||||
if (!s->muted) {
|
||||
#ifdef HAS_ALSA
|
||||
int pcm;
|
||||
if (g_mii_audio_record_fd != -1)
|
||||
write(g_mii_audio_record_fd, f->audio,
|
||||
f->fill * sizeof(s->frame[0].audio[0]));
|
||||
if ((pcm = snd_pcm_writei(s->alsa_pcm,
|
||||
f->audio, f->fill)) == -EPIPE) {
|
||||
printf("%s Underrun.\n", __func__);
|
||||
snd_pcm_recover(s->alsa_pcm, pcm, 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
f->fill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// this is here so we dont' have to drag in libm math library.
|
||||
double fastPow(double a, double b) {
|
||||
union { double d; int32_t x[2]; } u = { .d = a };
|
||||
u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447);
|
||||
u.x[0] = 0;
|
||||
return u.d;
|
||||
}
|
||||
|
||||
// take the volume from 0 to 10, save it, convert it to a multiplier
|
||||
void
|
||||
mii_speaker_volume(
|
||||
mii_speaker_t *s,
|
||||
float volume)
|
||||
{
|
||||
if (volume < 0) volume = 0;
|
||||
else if (volume > 10) volume = 10;
|
||||
double mul = (fastPow(10.0, volume / 10.0) / 10.0) - 0.09;
|
||||
s->vol_multiplier = mul;
|
||||
s->volume = volume;
|
||||
|
||||
// printf("audio: speaker volume set to %.3f (%.4f)\n", volume, mul);
|
||||
}
|
58
src/mii_speaker.h
Normal file
58
src/mii_speaker.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* mii_speaker.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define MII_SPEAKER_FREQ (44100)
|
||||
#define MII_SPEAKER_FRAME_COUNT 4
|
||||
|
||||
struct mii_t;
|
||||
struct snd_pcm_t;
|
||||
typedef int16_t mii_audio_sample_t;
|
||||
|
||||
typedef struct mii_audio_frame_t {
|
||||
__uint128_t start;
|
||||
uint16_t fill;
|
||||
mii_audio_sample_t * audio;
|
||||
} mii_audio_frame_t;
|
||||
|
||||
typedef struct mii_speaker_t {
|
||||
struct mii_t * mii;
|
||||
void * alsa_pcm; // alsa pcm handle
|
||||
bool muted; // if true, don't play anything
|
||||
float volume; // volume, 0.0 to 10.0
|
||||
float vol_multiplier; // sample multiplier, 0.0 to 1.0
|
||||
float cpu_speed; // CPU speed in MHz, to calculate clk_per_sample
|
||||
uint8_t fplay; // frame we want to play
|
||||
uint8_t fplaying; // index of the current playing frame
|
||||
uint16_t fsize; // size in samples of a frame
|
||||
uint8_t findex; // frame we are currently filling
|
||||
uint16_t clk_per_sample; // number of cycles per sample (at current CPU speed)
|
||||
mii_audio_sample_t sample; // current value for the speaker output
|
||||
mii_audio_frame_t frame[MII_SPEAKER_FRAME_COUNT];
|
||||
} mii_speaker_t;
|
||||
|
||||
// Initialize the speaker with the frame size in samples
|
||||
void
|
||||
mii_speaker_init(
|
||||
struct mii_t * mii,
|
||||
mii_speaker_t *speaker);
|
||||
// Called when $c030 is touched, place a sample at the 'appropriate' time
|
||||
void
|
||||
mii_speaker_click(
|
||||
mii_speaker_t *speaker);
|
||||
// Check to see if there's a new frame to send, send it
|
||||
void
|
||||
mii_speaker_run(
|
||||
mii_speaker_t *speaker);
|
||||
// volume from 0 to 10, sets the audio sample multiplier.
|
||||
void
|
||||
mii_speaker_volume(
|
||||
mii_speaker_t *s,
|
||||
float volume);
|
59
src/mii_sw.h
Normal file
59
src/mii_sw.h
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
enum {
|
||||
SW80STOREOFF = 0xc000,
|
||||
SW80STOREON = 0xc001,
|
||||
SWALTCHARSETOFF = 0xc00e,
|
||||
SWALTCHARSETON = 0xc00f,
|
||||
|
||||
SW80STORE = 0xc018,
|
||||
SWVBL = 0xc019,
|
||||
SW80COL = 0xc01f,
|
||||
SWTEXT = 0xc01a,
|
||||
SWMIXED = 0xc01b,
|
||||
SWPAGE2 = 0xc01c,
|
||||
SWHIRES = 0xc01d,
|
||||
SWALTCHARSET = 0xc01e,
|
||||
|
||||
SW80COLOFF = 0xc00c,
|
||||
SW80COLON = 0xc00d,
|
||||
SWTEXTOFF = 0xc050, // (AKA LORES ON)
|
||||
SWTEXTON = 0xc051,
|
||||
SWMIXEDOFF = 0xc052,
|
||||
SWMIXEDON = 0xc053,
|
||||
SWPAGE2OFF = 0xc054,
|
||||
SWPAGE2ON = 0xc055,
|
||||
SWHIRESOFF = 0xc056,
|
||||
SWHIRESON = 0xc057,
|
||||
|
||||
// this one is inverted, the ON is the even address
|
||||
SWDHIRESOFF = 0xc05f, // AN3_ON
|
||||
SWDHIRESON = 0xc05e, // AN3_OFF
|
||||
SWAN3 = 0xc05e, // AN3 status
|
||||
SWAN3_REGISTER = 0xc05f, // AN3 register for video mode
|
||||
SWRDDHIRES = 0xc07f,
|
||||
|
||||
SWRAMRDOFF = 0xc002,
|
||||
SWRAMRDON = 0xc003,
|
||||
SWRAMWRTOFF = 0xc004,
|
||||
SWRAMWRTON = 0xc005,
|
||||
SWALTPZOFF = 0xc008,
|
||||
SWALTPZON = 0xc009,
|
||||
SWINTCXROMOFF = 0xc006,
|
||||
SWINTCXROMON = 0xc007,
|
||||
SWSLOTC3ROMOFF = 0xc00a,
|
||||
SWSLOTC3ROMON = 0xc00b,
|
||||
SWBSRBANK2 = 0xc011,
|
||||
SWBSRREADRAM = 0xc012,
|
||||
SWRAMRD = 0xc013,
|
||||
SWRAMWRT = 0xc014,
|
||||
SWINTCXROM = 0xc015,
|
||||
SWALTPZ = 0xc016,
|
||||
SWSLOTC3ROM = 0xc017,
|
||||
SWSPEAKER = 0xc030,
|
||||
SWKBD = 0xc000,
|
||||
SWAKD = 0xc010,
|
||||
|
||||
};
|
415
src/mii_video.c
Normal file
415
src/mii_video.c
Normal file
@ -0,0 +1,415 @@
|
||||
/*
|
||||
* mii_video.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_rom_iiee_video.h"
|
||||
#include "mii_sw.h"
|
||||
#include "minipt.h"
|
||||
|
||||
#define VIDEO_RESOLUTION_X 280
|
||||
#define VIDEO_RESOLUTION_Y 192
|
||||
|
||||
#define VIDEO_BYTES_PER_LINE 40
|
||||
#define SCREEN_LINE_OFFSET 0x80
|
||||
#define VIDEO_SEGMENT_OFFSET 0x28
|
||||
|
||||
|
||||
enum {
|
||||
// https://rich12345.tripod.com/aiivideo/vbl.html
|
||||
MII_VBL_DOWN_CYCLES = 12480,
|
||||
MII_VBL_UP_CYCLES = 4550,
|
||||
MII_VIDEO_H_CYCLES = 40,
|
||||
MII_VIDEO_HB_CYCLES = 25,
|
||||
};
|
||||
|
||||
/*
|
||||
* Colors were lifted from
|
||||
* https://comp.sys.apple2.narkive.com/lTSrj2ZI/apple-ii-colour-rgb
|
||||
*/
|
||||
#define HI_RGB(r,g,b) (0xff000000 | ((b) << 16) | ((g) << 8) | (r))
|
||||
static const uint32_t lores_colors[] = {
|
||||
[0x0] = HI_RGB(0x00, 0x00, 0x00), // black
|
||||
[0x1] = HI_RGB(0xe3, 0x1e, 0x60), // magenta
|
||||
[0x2] = HI_RGB(0x60, 0x4e, 0xbd), // dark blue
|
||||
[0x3] = HI_RGB(0xff, 0x44, 0xfd), // purple
|
||||
[0x4] = HI_RGB(0x00, 0xa3, 0x60), // dark green
|
||||
[0x5] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 1
|
||||
[0x6] = HI_RGB(0x14, 0xcf, 0xfd), // medium blue
|
||||
[0x7] = HI_RGB(0xd0, 0xc3, 0xff), // light blue
|
||||
[0x8] = HI_RGB(0x60, 0x72, 0x03), // brown
|
||||
[0x9] = HI_RGB(0xff, 0x6a, 0x3c), // orange
|
||||
[0xa] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 2
|
||||
[0xb] = HI_RGB(0xff, 0xa0, 0xd0), // pink
|
||||
[0xc] = HI_RGB(0x14, 0xf5, 0x3c), // light green
|
||||
[0xd] = HI_RGB(0xd0, 0xdd, 0x8d), // yellow
|
||||
[0xe] = HI_RGB(0x72, 0xff, 0xd0), // aqua
|
||||
[0xf] = HI_RGB(0xff, 0xff, 0xff), // white
|
||||
};
|
||||
|
||||
enum {
|
||||
C_BLACK = HI_RGB(0x00, 0x00, 0x00), // black
|
||||
C_PURPLE = HI_RGB(0xff, 0x44, 0xfd), // purple
|
||||
C_GREEN = HI_RGB(0x14, 0xf5, 0x3c), // green
|
||||
C_BLUE = HI_RGB(0x14, 0xcf, 0xfd), // blue
|
||||
C_ORANGE = HI_RGB(0xff, 0x6a, 0x3c), // orange
|
||||
C_WHITE = HI_RGB(0xff, 0xff, 0xff), // white
|
||||
};
|
||||
|
||||
static const uint32_t hires_colors[] = {
|
||||
C_BLACK,
|
||||
C_PURPLE,
|
||||
C_GREEN,
|
||||
C_GREEN,
|
||||
C_PURPLE,
|
||||
C_BLUE,
|
||||
C_ORANGE,
|
||||
C_ORANGE,
|
||||
C_BLUE,
|
||||
C_WHITE,
|
||||
};
|
||||
|
||||
static const uint32_t dhires_colors[] = {
|
||||
[0x0] = HI_RGB(0x00, 0x00, 0x00), // black
|
||||
[0x1] = HI_RGB(0xe3, 0x1e, 0x60), // magenta
|
||||
[0x2] = HI_RGB(0x60, 0x72, 0x03), // brown
|
||||
[0x3] = HI_RGB(0xff, 0x6a, 0x3c), // orange
|
||||
[0x4] = HI_RGB(0x00, 0xa3, 0x60), // dark green
|
||||
[0x5] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 1
|
||||
[0x6] = HI_RGB(0x14, 0xf5, 0x3c), // light green
|
||||
[0x7] = HI_RGB(0xd0, 0xdd, 0x8d), // yellow
|
||||
[0x8] = HI_RGB(0x60, 0x4e, 0xbd), // dark blue
|
||||
[0x9] = HI_RGB(0xff, 0x44, 0xfd), // purple
|
||||
[0xa] = HI_RGB(0x9c, 0x9c, 0x9c), // gray 2
|
||||
[0xb] = HI_RGB(0xff, 0xa0, 0xd0), // pink
|
||||
[0xc] = HI_RGB(0x14, 0xcf, 0xfd), // medium blue
|
||||
[0xd] = HI_RGB(0xd0, 0xc3, 0xff), // light blue
|
||||
[0xe] = HI_RGB(0x72, 0xff, 0xd0), // aqua
|
||||
[0xf] = HI_RGB(0xff, 0xff, 0xff), // white
|
||||
};
|
||||
|
||||
static const uint32_t mono[3][2] = {
|
||||
{ 0xff000000, C_WHITE },
|
||||
{ 0xff000000, C_GREEN },
|
||||
{ 0xff000000, C_ORANGE },
|
||||
};
|
||||
|
||||
|
||||
/* this 'dims' the colors for every second line of pixels */
|
||||
#define C_SCANLINE_MASK 0xffc0c0c0
|
||||
|
||||
static inline uint16_t
|
||||
_mii_line_to_video_addr(
|
||||
uint16_t addr,
|
||||
uint8_t line)
|
||||
{
|
||||
addr += ((line & 0x07) << 10) |
|
||||
(((line >> 3) & 7) << 7) |
|
||||
((line >> 6) << 5) | ((line >> 6) << 3);
|
||||
return addr;
|
||||
}
|
||||
/*
|
||||
* This is the state of the video output
|
||||
* All timings lifted from https://rich12345.tripod.com/aiivideo/vbl.html
|
||||
*
|
||||
* This is a 'protothread' basically cooperative scheduling using an
|
||||
* old compiler trick. It's not a real thread, but it's a way to
|
||||
* write code that looks like a thread, and is easy to read.
|
||||
* The 'pt_start' macro starts the thread, and pt_yield() yields
|
||||
* the thread to the main loop.
|
||||
* The pt_end() macro ends the thread.
|
||||
* Remeber you cannot have locals in the thread, they must be
|
||||
* static or global.
|
||||
* *everything* before the pt_start call is ran every time, so you can use
|
||||
* that to reload some sort of state, as here, were we reload all the
|
||||
* video mode softwsitches.
|
||||
*/
|
||||
void
|
||||
mii_video_run(
|
||||
mii_t *mii)
|
||||
{
|
||||
// no need to do anything, we're waiting cycles
|
||||
if (mii->video.wait) {
|
||||
if (mii->video.wait > mii->cycles)
|
||||
return;
|
||||
// extra cycles we waited are kept around for next delay
|
||||
mii->video.wait = mii->cycles - mii->video.wait;
|
||||
}
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
bool text = !!mii_bank_peek(main, SWTEXT);
|
||||
bool page2 = !!mii_bank_peek(main, SWPAGE2);
|
||||
bool col80 = !!mii_bank_peek(main, SW80COL);
|
||||
bool store80 = !!mii_bank_peek(main, SW80STORE);
|
||||
bool mixed = !!mii_bank_peek(main, SWMIXED);
|
||||
bool hires = !!mii_bank_peek(main, SWHIRES);
|
||||
bool dhires = !!mii_bank_peek(main, SWRDDHIRES);
|
||||
|
||||
pt_start(mii->video.state);
|
||||
/*
|
||||
We cheat and draw a whole line at a time, then 'wait' until
|
||||
horizontal blanking, then wait until vertical blanking.
|
||||
*/
|
||||
do {
|
||||
// 'clear' VBL flag. Flag is 0 during retrace
|
||||
mii_bank_poke(main, SWVBL, 0x80);
|
||||
if (mixed && !text) {
|
||||
text = mii->video.line >= 192 - (4 * 8);
|
||||
hires = 0;
|
||||
}
|
||||
// http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.03.html
|
||||
if (hires && !text && col80 && dhires) {
|
||||
if (store80)
|
||||
page2 = 0;
|
||||
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
|
||||
uint16_t a = (0x2000 + (0x2000 * page2));
|
||||
a = _mii_line_to_video_addr(a, mii->video.line);
|
||||
uint32_t * screen = mii->video.pixels +
|
||||
(mii->video.line * MII_VRAM_WIDTH * 2);
|
||||
uint32_t * l2 = screen + MII_VRAM_WIDTH;
|
||||
|
||||
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
|
||||
|
||||
if (reg == 0 || mii->video.color_mode != MII_VIDEO_COLOR) {
|
||||
for (int x = 0; x < 40; x += 1) {
|
||||
uint32_t ext = (mii_bank_peek(aux, a + x) & 0x7f) |
|
||||
((mii_bank_peek(main, a + x) & 0x7f) << 7);
|
||||
for (int bi = 0; bi < 14; bi++) {
|
||||
uint8_t pixel = (ext >> bi) & 1;
|
||||
uint32_t col = pixel ?
|
||||
mono[mii->video.color_mode][1] :
|
||||
mono[mii->video.color_mode][0];
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
}
|
||||
}
|
||||
} else { // color mode
|
||||
int x = 0, dx = 0;
|
||||
do {
|
||||
uint64_t run = 0;
|
||||
// get 8 bytes, so we get 8*7=56 bits for 14 pixels
|
||||
for (int bx = 0; bx < 8 && x < 80; bx++, x++) {
|
||||
uint8_t b = mii_bank_peek(
|
||||
x & 1 ? main : aux, a + (x / 2));
|
||||
run |= ((uint64_t)(b & 0x7f) << (bx * 7));
|
||||
}
|
||||
for (int px = 0; px < 14 && dx < 80*2; px++, dx++) {
|
||||
uint8_t pixel = run & 0xf;
|
||||
run >>= 4;
|
||||
uint32_t col = dhires_colors[pixel];
|
||||
*screen++ = col;
|
||||
*screen++ = col;
|
||||
*screen++ = col;
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
}
|
||||
} while (x < 80);
|
||||
}
|
||||
} else if (hires && !text) {
|
||||
if (store80)
|
||||
page2 = 0;
|
||||
uint16_t a = (0x2000 + (0x2000 * page2));
|
||||
a = _mii_line_to_video_addr(a, mii->video.line);
|
||||
uint32_t * screen = mii->video.pixels +
|
||||
(mii->video.line * MII_VRAM_WIDTH * 2);
|
||||
uint32_t * l2 = screen + MII_VRAM_WIDTH;
|
||||
|
||||
uint8_t b0 = 0;
|
||||
uint8_t b1 = mii_bank_peek(main, a + 0);
|
||||
for (int x = 0; x < 40; x++) {
|
||||
uint8_t b2 = mii_bank_peek(main, a + x + 1);
|
||||
// last 2 pixels, current 7 pixels, next 2 pixels
|
||||
uint16_t run = ((b0 & 0x60) >> ( 5 )) |
|
||||
((b1 & 0x7f) << ( 2 )) |
|
||||
((b2 & 0x03) << ( 9 ));
|
||||
int odd = (x & 1) << 1;
|
||||
int offset = (b1 & 0x80) >> 5;
|
||||
|
||||
if (mii->video.color_mode == MII_VIDEO_COLOR) {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
uint8_t left = (run >> (1 + i)) & 1;
|
||||
uint8_t pixel = (run >> (2 + i)) & 1;
|
||||
uint8_t right = (run >> (3 + i)) & 1;
|
||||
|
||||
int idx = 0; // black
|
||||
if (pixel) {
|
||||
if (left || right) {
|
||||
idx = 9; // white
|
||||
} else {
|
||||
idx = offset + odd + (i & 1) + 1;
|
||||
}
|
||||
} else {
|
||||
if (left && right) {
|
||||
idx = offset + odd + 1 - (i & 1) + 1;
|
||||
}
|
||||
}
|
||||
uint32_t col = hires_colors[idx];
|
||||
*screen++ = col;
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
uint8_t pixel = (run >> (2 + i)) & 1;
|
||||
uint32_t col = pixel ?
|
||||
mono[mii->video.color_mode][1] :
|
||||
mono[mii->video.color_mode][0];
|
||||
*screen++ = col;
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
}
|
||||
}
|
||||
b0 = b1;
|
||||
b1 = b2;
|
||||
}
|
||||
} else {
|
||||
if (store80)
|
||||
page2 = 0;
|
||||
uint16_t a = (0x400 + (0x400 * page2));
|
||||
int i = mii->video.line >> 3;
|
||||
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
|
||||
|
||||
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
|
||||
uint32_t * screen = mii->video.pixels +
|
||||
(mii->video.line * MII_VRAM_WIDTH * 2);
|
||||
uint32_t * l2 = screen + MII_VRAM_WIDTH;
|
||||
for (int x = 0; x < 40 + (40 * col80); x++) {
|
||||
uint8_t c = 0;
|
||||
if (col80)
|
||||
c = mii_bank_peek(
|
||||
x & 1 ? main : aux, a + (x >> 1));
|
||||
else
|
||||
c = mii_bank_peek(main, a + x);
|
||||
if (text) {
|
||||
const uint8_t * rom = iie_enhanced_video + (c << 3);
|
||||
uint8_t bits = rom[mii->video.line & 0x07];
|
||||
for (int pi = 0; pi < 7; pi++) {
|
||||
uint8_t pixel = (bits >> pi) & 1;
|
||||
uint32_t col = pixel ?
|
||||
mono[mii->video.color_mode][0] :
|
||||
mono[mii->video.color_mode][1];
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
if (!col80) {
|
||||
*screen++ = col;
|
||||
*l2++ = col & C_SCANLINE_MASK;
|
||||
}
|
||||
}
|
||||
} else { // lores graphics
|
||||
int lo_line = mii->video.line / 4;
|
||||
c = c >> ((lo_line & 1) * 4);
|
||||
uint32_t pixel = lores_colors[c & 0x0f];
|
||||
for (int pi = 0; pi < 7; pi++) {
|
||||
*screen++ = pixel;
|
||||
*l2++ = pixel & C_SCANLINE_MASK;
|
||||
if (!col80) {
|
||||
*screen++ = pixel;
|
||||
*l2++ = pixel & C_SCANLINE_MASK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mii->video.line++;
|
||||
if (mii->video.line == 192) {
|
||||
mii->video.line = 0;
|
||||
mii->video.wait = mii->cycles - mii->video.wait +
|
||||
MII_VIDEO_H_CYCLES * mii->speed;
|
||||
pt_yield(mii->video.state);
|
||||
mii_bank_poke(main, SWVBL, 0x00);
|
||||
if (mii->video.vbl_irq)
|
||||
mii->cpu_state.irq = 1;
|
||||
mii->video.wait = mii->cycles - mii->video.wait +
|
||||
MII_VBL_UP_CYCLES * mii->speed;
|
||||
mii->video.frame_count++;
|
||||
pt_yield(mii->video.state);
|
||||
} else {
|
||||
mii->video.wait = mii->cycles - mii->video.wait +
|
||||
(MII_VIDEO_H_CYCLES + MII_VIDEO_HB_CYCLES) *
|
||||
mii->speed;
|
||||
pt_yield(mii->video.state);
|
||||
}
|
||||
} while (1);
|
||||
pt_end(mii->video.state);
|
||||
return;
|
||||
}
|
||||
|
||||
bool
|
||||
mii_access_video(
|
||||
mii_t *mii,
|
||||
uint16_t addr,
|
||||
uint8_t * byte,
|
||||
bool write)
|
||||
{
|
||||
bool res = false;
|
||||
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
|
||||
switch (addr) {
|
||||
case SWALTCHARSETOFF:
|
||||
case SWALTCHARSETON:
|
||||
if (!write) break;
|
||||
res = true;
|
||||
mii_bank_poke(main, SWALTCHARSET, (addr & 1) << 7);
|
||||
break;
|
||||
case SWVBL:
|
||||
case SW80COL:
|
||||
case SWTEXT:
|
||||
case SWMIXED:
|
||||
case SWPAGE2:
|
||||
case SWHIRES:
|
||||
case SWALTCHARSET:
|
||||
case SWRDDHIRES:
|
||||
res = true;
|
||||
if (!write)
|
||||
*byte = mii_bank_peek(main, addr);
|
||||
break;
|
||||
case SW80COLOFF:
|
||||
case SW80COLON:
|
||||
if (!write) break;
|
||||
res = true;
|
||||
mii_bank_poke(main, SW80COL, (addr & 1) << 7);
|
||||
// printf("80COL %s\n", on ? "ON" : "OFF");
|
||||
break;
|
||||
case SWDHIRESOFF: // 0xc05f,
|
||||
case SWDHIRESON: { // = 0xc05e,
|
||||
res = true;
|
||||
uint8_t an3 = !!mii_bank_peek(main, SWAN3);
|
||||
bool an3_on = !!(addr & 1); // 5f is ON, 5e is OFF
|
||||
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
|
||||
if (an3_on && !an3) {
|
||||
uint8_t bit = !!mii_bank_peek(main, SW80COL);
|
||||
reg = ((reg << 1) | bit) & 3;
|
||||
printf("VIDEO 80:%d REG now %x\n", bit, reg);
|
||||
mii_bank_poke(main, SWAN3_REGISTER, reg);
|
||||
}
|
||||
mii_bank_poke(main, SWAN3, an3_on);
|
||||
printf("DHRES IS %s mode:%d\n",
|
||||
(addr & 1) ? "OFF" : "ON", reg);
|
||||
mii_bank_poke(main, SWRDDHIRES, (!(addr & 1)) << 7);
|
||||
} break;
|
||||
case SWTEXTOFF:
|
||||
case SWTEXTON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWTEXT, (addr & 1) << 7);
|
||||
break;
|
||||
case SWMIXEDOFF:
|
||||
case SWMIXEDON:
|
||||
res = true;
|
||||
mii_bank_poke(main, SWMIXED, (addr & 1) << 7);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user