├── .gitignore ├── LICENSE ├── fonts ├── 4x6.png ├── 6x10.png └── 6x8.png ├── include ├── zoo.h ├── zoo_config.h ├── zoo_io_path.h ├── zoo_io_posix.h ├── zoo_io_romfs.h ├── zoo_sidebar.h ├── zoo_sound_pcm.h ├── zoo_ui.h └── zoo_ui_input.h ├── src ├── Makefile ├── drivers │ ├── zoo_io_path.c │ ├── zoo_io_posix.c │ ├── zoo_io_romfs.c │ └── zoo_sound_pcm.c ├── frontend │ ├── atari_tos │ │ ├── Makefile │ │ ├── src │ │ │ └── main.c │ │ └── tools │ │ │ └── pack1bit.py │ ├── gba │ │ ├── Makefile │ │ ├── src │ │ │ ├── main.c │ │ │ ├── sound_gba.c │ │ │ ├── sound_gba.h │ │ │ ├── video_gba.c │ │ │ ├── video_gba.h │ │ │ └── zoo_gba.h │ │ └── tools │ │ │ ├── pack4x6.py │ │ │ └── pack6x10.py │ ├── psp │ │ ├── Makefile │ │ ├── src │ │ │ └── main.c │ │ └── tools │ │ │ └── fontpack.py │ └── sdl │ │ ├── Makefile │ │ └── src │ │ ├── 8x14.c │ │ ├── main.c │ │ ├── render_software.c │ │ ├── render_software.h │ │ └── types.h ├── libzoo │ ├── zoo.c │ ├── zoo_callstack.c │ ├── zoo_element_defs.inc │ ├── zoo_elements.c │ ├── zoo_game.c │ ├── zoo_game_io.c │ ├── zoo_input.c │ ├── zoo_internal.h │ ├── zoo_io.c │ ├── zoo_oop.c │ ├── zoo_oop_label_cache.c │ ├── zoo_oop_token.tok │ ├── zoo_sound.c │ ├── zoo_window.c │ └── zoo_window_classic.c └── ui │ ├── zoo_sidebar_classic.c │ ├── zoo_sidebar_slim.c │ ├── zoo_ui.c │ ├── zoo_ui_debug.c │ ├── zoo_ui_file_select.c │ ├── zoo_ui_input.c │ ├── zoo_ui_internal.h │ ├── zoo_ui_osk.c │ ├── zoo_ui_prompt.c │ └── zoo_ui_util.c └── tools ├── bin2c.py ├── tok2c.py └── tok2c_tpl.c /.gitignore: -------------------------------------------------------------------------------- 1 | **/build 2 | **/*.elf 3 | src/frontend/gba/*.gba 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Adrian Siekierka 2 | 3 | Based on a reconstruction of code from ZZT, 4 | Copyright 1991 Epic MegaGames, used with permission. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /fonts/4x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asiekierka/libzoo/7fb3e89a8467e66a4b8cbc290c708b9fa5290886/fonts/4x6.png -------------------------------------------------------------------------------- /fonts/6x10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asiekierka/libzoo/7fb3e89a8467e66a4b8cbc290c708b9fa5290886/fonts/6x10.png -------------------------------------------------------------------------------- /fonts/6x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asiekierka/libzoo/7fb3e89a8467e66a4b8cbc290c708b9fa5290886/fonts/6x8.png -------------------------------------------------------------------------------- /include/zoo_config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __ZOO_CONFIG_H__ 24 | #define __ZOO_CONFIG_H__ 25 | 26 | #ifndef ZOO_CONFIG_SOUND_PCM_BUFFER_LEN 27 | #define ZOO_CONFIG_SOUND_PCM_BUFFER_LEN 32 // ~1.5 seconds of audio 28 | #endif 29 | 30 | // Feature flags 31 | #ifdef ZOO_USE_ROM_POINTERS 32 | // If we can't write to object code memory, we must enable some workarounds. 33 | #ifndef ZOO_USE_LABEL_CACHE 34 | #error Label cache required for ROM pointer support! 35 | #endif 36 | #define ZOO_STORE_LABEL_CACHE 37 | #define ZOO_NO_OBJECT_CODE_WRITES 38 | #endif 39 | 40 | #endif /* __ZOO_CONFIG_H__ */ 41 | -------------------------------------------------------------------------------- /include/zoo_io_path.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __ZOO_IO_PATH_H__ 24 | #define __ZOO_IO_PATH_H__ 25 | 26 | #include 27 | #include "zoo.h" 28 | 29 | #ifndef ZOO_PATH_SEPARATOR 30 | #ifdef _WIN32 31 | #define ZOO_PATH_SEPARATOR '\\' 32 | #define ZOO_PATH_SEPARATOR_STR "\\" 33 | #else 34 | #define ZOO_PATH_SEPARATOR '/' 35 | #define ZOO_PATH_SEPARATOR_STR "/" 36 | #endif 37 | #endif 38 | 39 | #ifndef ZOO_PATH_MAX 40 | #ifdef _WIN32 41 | #define ZOO_PATH_MAX 260 42 | #else 43 | // TODO: Linux uses 4096 by default 44 | #define ZOO_PATH_MAX 512 45 | #endif 46 | #endif 47 | 48 | struct s_zoo_io_path_driver; 49 | 50 | typedef enum { 51 | TYPE_DIR, 52 | TYPE_FILE 53 | } zoo_io_type; 54 | 55 | typedef struct { 56 | zoo_io_type type; 57 | char name[ZOO_PATH_MAX + 1]; 58 | int64_t mtime; 59 | } zoo_io_dirent; 60 | 61 | typedef bool (*zoo_func_io_scan_dir_callback)(struct s_zoo_io_path_driver *drv, zoo_io_dirent *e, void *arg); 62 | 63 | typedef struct s_zoo_io_path_driver { 64 | zoo_io_driver parent; 65 | 66 | char path[ZOO_PATH_MAX + 1]; 67 | 68 | // directory traversal 69 | zoo_io_handle (*func_open_file_absolute)(struct s_zoo_io_path_driver *drv, const char *filename, zoo_io_mode mode); 70 | bool (*func_dir_scan)(struct s_zoo_io_path_driver *drv, const char *dir, zoo_func_io_scan_dir_callback cb, void *cb_arg); 71 | bool (*func_dir_advance)(struct s_zoo_io_path_driver *drv, char *dest, const char *curr, const char *next, size_t len); 72 | } zoo_io_path_driver; 73 | 74 | void zoo_path_cat(char *dest, const char *src, size_t n); 75 | void zoo_io_internal_init_path_driver(zoo_io_path_driver *drv); 76 | 77 | #endif /* __ZOO_IO_PATH_H__ */ 78 | -------------------------------------------------------------------------------- /include/zoo_io_posix.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __ZOO_IO_POSIX_H__ 24 | #define __ZOO_IO_POSIX_H__ 25 | 26 | #include 27 | #include "zoo_io_path.h" 28 | 29 | void zoo_io_create_posix_driver(zoo_io_path_driver *drv); 30 | 31 | #endif /* __ZOO_IO_POSIX_H__ */ -------------------------------------------------------------------------------- /include/zoo_io_romfs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __ZOO_IO_ROMFS_H__ 24 | #define __ZOO_IO_ROMFS_H__ 25 | 26 | #include 27 | #include "zoo_io_path.h" 28 | 29 | typedef struct { 30 | zoo_io_path_driver parent; 31 | uint8_t *fs; 32 | uint8_t *root_ptr; 33 | } zoo_io_romfs_driver; 34 | 35 | bool zoo_io_create_romfs_driver(zoo_io_romfs_driver *drv, uint8_t *fs_ptr); 36 | 37 | #endif /* __ZOO_IO_ROMFS_H__ */ -------------------------------------------------------------------------------- /include/zoo_sidebar.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #ifndef __ZOO_SIDEBAR_H__ 27 | #define __ZOO_SIDEBAR_H__ 28 | 29 | #include "zoo.h" 30 | 31 | void zoo_draw_sidebar_classic(zoo_state *state, uint16_t flags); 32 | void zoo_draw_sidebar_slim(zoo_state *state, uint16_t flags); 33 | 34 | #endif /* __ZOO_SIDEBAR_H__ */ -------------------------------------------------------------------------------- /include/zoo_sound_pcm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __ZOO_SOUND_PCM__ 24 | #define __ZOO_SOUND_PCM__ 25 | 26 | #include 27 | #include 28 | #include "zoo.h" 29 | 30 | typedef struct { 31 | uint32_t tick; 32 | uint32_t emitted; 33 | const uint16_t *freqs; 34 | uint16_t pos, len; 35 | bool clear; 36 | } zoo_pcm_entry; 37 | 38 | typedef struct { 39 | zoo_sound_driver parent; 40 | 41 | // user-configurable 42 | uint32_t frequency; 43 | uint8_t channels; 44 | uint8_t volume; 45 | bool format_signed; 46 | uint8_t latency; // in ticks 47 | 48 | // generated 49 | zoo_pcm_entry buffer[ZOO_CONFIG_SOUND_PCM_BUFFER_LEN]; 50 | uint16_t buf_pos; 51 | uint16_t buf_len; 52 | int32_t buf_ticks; 53 | double sub_ticks; 54 | int32_t ticks; 55 | } zoo_sound_pcm_driver; 56 | 57 | void zoo_sound_pcm_init(zoo_sound_pcm_driver *drv); 58 | void zoo_sound_pcm_generate(zoo_sound_pcm_driver *drv, uint8_t *stream, size_t len); 59 | void zoo_sound_pcm_tick(zoo_sound_pcm_driver *drv); 60 | 61 | #endif /* __ZOO_SOUND_PCM__ */ -------------------------------------------------------------------------------- /include/zoo_ui.h: -------------------------------------------------------------------------------- 1 | #ifndef __ZOO_UI_H__ 2 | #define __ZOO_UI_H__ 3 | 4 | #include "zoo.h" 5 | #include "zoo_io_path.h" 6 | #include "zoo_ui_input.h" 7 | 8 | #ifdef ZOO_UI_OSK 9 | #define ZOO_UI_CHEAT_HISTORY 10 | #endif 11 | 12 | struct s_zoo_ui_state; 13 | 14 | // game operations 15 | 16 | void zoo_ui_load_world(struct s_zoo_ui_state *state, bool as_save); 17 | void zoo_ui_main_menu(struct s_zoo_ui_state *state); 18 | 19 | // state definitions 20 | 21 | #define ZOO_UI_CHEAT_HISTORY_SIZE 8 22 | 23 | typedef struct s_zoo_ui_state { 24 | zoo_state *zoo; 25 | zoo_ui_input_state input; 26 | 27 | zoo_text_window window; 28 | char filesel_extension[5]; 29 | 30 | #ifdef ZOO_UI_CHEAT_HISTORY 31 | char *cheat_history[ZOO_UI_CHEAT_HISTORY_SIZE]; 32 | #endif 33 | } zoo_ui_state; 34 | 35 | void zoo_ui_init(zoo_ui_state *state, zoo_state *zoo); 36 | void zoo_ui_tick(zoo_ui_state *state); 37 | 38 | #endif /* __ZOO_UI_H__ */ -------------------------------------------------------------------------------- /include/zoo_ui_input.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | // zoo_ui_input.h - keyboard/joystick/mouse -> libzoo/ui adapter 27 | 28 | #ifndef __ZOO_UI_INPUT_H__ 29 | #define __ZOO_UI_INPUT_H__ 30 | 31 | #include 32 | #include "zoo.h" 33 | 34 | // TODO: joystick/mouse adapter 35 | 36 | #define ZOO_UI_KBDBUF_SIZE 16 37 | 38 | #define ZOO_KEY_RELEASED 0x8000 39 | 40 | typedef struct { 41 | uint16_t ui_kbd_buffer[ZOO_UI_KBDBUF_SIZE]; 42 | uint16_t action_to_kbd_button[ZOO_ACTION_MAX]; 43 | } zoo_ui_input_state; 44 | 45 | void zoo_ui_input_init(zoo_ui_input_state *inp); 46 | 47 | void zoo_ui_input_key(zoo_state *zoo, zoo_ui_input_state *inp, uint16_t key, bool pressed); 48 | void zoo_ui_input_key_map(zoo_ui_input_state *inp, zoo_input_action action, uint16_t key); 49 | uint16_t zoo_ui_input_key_pop(zoo_ui_input_state *inp); 50 | 51 | // keybinds 52 | 53 | #define ZOO_KEY_BACKSPACE 8 54 | #define ZOO_KEY_TAB 9 55 | #define ZOO_KEY_ENTER 13 56 | #define ZOO_KEY_ESCAPE 27 57 | #define ZOO_KEY_F1 187 58 | #define ZOO_KEY_F2 188 59 | #define ZOO_KEY_F3 189 60 | #define ZOO_KEY_F4 190 61 | #define ZOO_KEY_F5 191 62 | #define ZOO_KEY_F6 192 63 | #define ZOO_KEY_F7 193 64 | #define ZOO_KEY_F8 194 65 | #define ZOO_KEY_F9 195 66 | #define ZOO_KEY_F10 196 67 | #define ZOO_KEY_UP 200 68 | #define ZOO_KEY_PAGE_UP 201 69 | #define ZOO_KEY_LEFT 203 70 | #define ZOO_KEY_RIGHT 205 71 | #define ZOO_KEY_DOWN 208 72 | #define ZOO_KEY_PAGE_DOWN 209 73 | #define ZOO_KEY_INSERT 210 74 | #define ZOO_KEY_DELETE 211 75 | #define ZOO_KEY_HOME 212 76 | #define ZOO_KEY_END 213 77 | 78 | #define ZOO_KEY_SHIFT 256 79 | 80 | #endif /* __ZOO_UI_INPUT_H__ */ -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # base defines 2 | 3 | INCLUDEDIR := $(BASEDIR)/include 4 | SRCDIR := $(BASEDIR)/src 5 | TOOLSDIR := $(BASEDIR)/tools 6 | 7 | SOURCES := $(foreach srcf,$(SOURCES),$(abspath $(srcf))) 8 | SOURCES += \ 9 | $(SRCDIR)/libzoo/zoo_callstack.c \ 10 | $(SRCDIR)/libzoo/zoo_elements.c \ 11 | $(SRCDIR)/libzoo/zoo_game_io.c \ 12 | $(SRCDIR)/libzoo/zoo_game.c \ 13 | $(SRCDIR)/libzoo/zoo_input.c \ 14 | $(SRCDIR)/libzoo/zoo_io.c \ 15 | $(SRCDIR)/libzoo/zoo_oop.c \ 16 | $(SRCDIR)/libzoo/zoo_sound.c \ 17 | $(SRCDIR)/libzoo/zoo_window.c \ 18 | $(SRCDIR)/libzoo/zoo_window_classic.c \ 19 | $(SRCDIR)/libzoo/zoo.c 20 | 21 | $(SRCDIR)/libzoo/zoo_oop.c : $(BUILDDIR)/libzoo/zoo_oop_token.c 22 | 23 | INCLUDE_DIRS += $(INCLUDEDIR) 24 | 25 | # automatically generated headers 26 | INCLUDE_DIRS += $(BUILDDIR) 27 | 28 | # shared configuration 29 | 30 | ifeq (${ZOO_TYPE},library) 31 | ZOO_CONFIG_ENABLE_EDITOR_CONSTANTS = 1 32 | else ifeq (${ZOO_TYPE},frontend) 33 | ifneq ($(or ${ZOO_USE_EDITOR}),) 34 | ZOO_CONFIG_ENABLE_EDITOR_CONSTANTS = 1 35 | endif 36 | endif 37 | 38 | # dependencies 39 | 40 | ifneq ($(or ${ZOO_USE_UI}),) 41 | ZOO_USE_DRIVER_IO_PATH = 1 42 | endif 43 | 44 | ifneq ($(or ${ZOO_USE_DRIVER_IO_POSIX},${ZOO_USE_DRIVER_IO_ROMFS}),) 45 | ZOO_USE_DRIVER_IO_PATH = 1 46 | endif 47 | 48 | ifneq ($(or ${ZOO_USE_ROM_POINTERS}),) 49 | # ROM pointer functionality necessiaties that object data be read-only. 50 | ZOO_USE_LABEL_CACHE = 1 51 | endif 52 | 53 | # compiler flags 54 | 55 | OPTIMIZE_CFLAGS ?= -O2 56 | OPTIMIZE_LDFLAGS ?= 57 | 58 | OPTIMIZE_CFLAGS += -DZOO_OPTIMIZE 59 | 60 | CFLAGS := -g -Wall -Wno-unused $(OPTIMIZE_CFLAGS) $(ARCH_CFLAGS) 61 | LDFLAGS := -g $(OPTIMIZE_LDFLAGS) $(ARCH_LDFLAGS) 62 | 63 | # auxillary inclusions 64 | 65 | ifdef ZOO_USE_DRIVER_IO_PATH 66 | SOURCES += $(SRCDIR)/drivers/zoo_io_path.c 67 | endif 68 | 69 | ifdef ZOO_USE_DRIVER_IO_POSIX 70 | SOURCES += $(SRCDIR)/drivers/zoo_io_posix.c 71 | endif 72 | 73 | ifdef ZOO_USE_DRIVER_IO_ROMFS 74 | SOURCES += $(SRCDIR)/drivers/zoo_io_romfs.c 75 | endif 76 | 77 | ifdef ZOO_USE_DRIVER_SOUND_PCM 78 | SOURCES += $(SRCDIR)/drivers/zoo_sound_pcm.c 79 | endif 80 | 81 | ifdef ZOO_USE_UI_SIDEBAR_CLASSIC 82 | SOURCES += $(SRCDIR)/ui/zoo_sidebar_classic.c 83 | endif 84 | 85 | ifdef ZOO_USE_UI_SIDEBAR_SLIM 86 | SOURCES += $(SRCDIR)/ui/zoo_sidebar_slim.c 87 | endif 88 | 89 | ifdef ZOO_USE_UI_OSK 90 | SOURCES += $(SRCDIR)/ui/zoo_ui_osk.c 91 | CFLAGS += -DZOO_UI_OSK 92 | endif 93 | 94 | ifdef ZOO_USE_UI 95 | SOURCES += $(SRCDIR)/ui/zoo_ui.c \ 96 | $(SRCDIR)/ui/zoo_ui_file_select.c \ 97 | $(SRCDIR)/ui/zoo_ui_input.c \ 98 | $(SRCDIR)/ui/zoo_ui_prompt.c \ 99 | $(SRCDIR)/ui/zoo_ui_util.c 100 | 101 | ifdef ZOO_DEBUG_MENU 102 | CFLAGS += -DZOO_DEBUG_MENU 103 | SOURCES += $(SRCDIR)/ui/zoo_ui_debug.c 104 | endif 105 | endif # ZOO_USE_UI 106 | 107 | ifdef ZOO_USE_LABEL_CACHE 108 | CFLAGS += -DZOO_USE_LABEL_CACHE 109 | SOURCES += $(SRCDIR)/libzoo/zoo_oop_label_cache.c 110 | endif 111 | 112 | ifdef ZOO_USE_ROM_POINTERS 113 | CFLAGS += -DZOO_USE_ROM_POINTERS 114 | endif 115 | 116 | # tools 117 | 118 | LD := $(CC) 119 | PYTHON3 := python3 120 | 121 | # add include/library directories 122 | 123 | CFLAGS += $(foreach dir,$(INCLUDE_DIRS),-I$(abspath $(dir))) 124 | LDFLAGS_LIBS := $(foreach dir,$(LIB_DIRS),-L$(abspath $(dir))) $(LIB_FLAGS) 125 | 126 | # final definitions 127 | 128 | CFLAGS_DEPS += -MT $@ -MMD -MP -MF $(BUILDDIR)/$*.d 129 | 130 | EARLY_OBJECTS := $(OBJECTS) 131 | OBJECTS += $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.c=.o)) 132 | 133 | $(OUTPUT)$(OUTEXT) : $(OBJECTS) 134 | @echo linking $(notdir $@) 135 | @mkdir -p $(@D) 136 | $(LD) $(LDFLAGS) $(OBJECTS) $(LDFLAGS_LIBS) -o $@ 137 | 138 | $(BUILDDIR)/%.o : $(SRCDIR)/%.c $(BUILDDIR)/%.d $(EARLY_OBJECTS) 139 | @echo $(notdir $<) 140 | @mkdir -p $(@D) 141 | $(CC) $(CFLAGS_DEPS) $(CFLAGS) -c $< -o $@ 142 | 143 | $(BUILDDIR)/%.o : $(BUILDDIR)/%.c $(BUILDDIR)/%.d 144 | @echo $(notdir $<) 145 | @mkdir -p $(@D) 146 | $(CC) $(CFLAGS_DEPS) $(CFLAGS) -c $< -o $@ 147 | 148 | $(BUILDDIR)/%.c : $(SRCDIR)/%.tok 149 | @echo $(notdir $<) 150 | @mkdir -p $(@D) 151 | $(PYTHON3) $(TOOLSDIR)/tok2c.py $< $@ 152 | 153 | .PHONY: clean 154 | 155 | clean: 156 | rm -rf $(BUILDDIR) $(OUTPUT).* 157 | 158 | DEPFILES := $(OBJECTS:.o=.d) 159 | $(DEPFILES): 160 | 161 | include $(wildcard $(DEPFILES)) 162 | -------------------------------------------------------------------------------- /src/drivers/zoo_io_path.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "zoo_io_path.h" 3 | 4 | void zoo_path_cat(char *dest, const char *src, size_t n) { 5 | size_t len = strlen(dest); 6 | if (len < n && dest[len - 1] != ZOO_PATH_SEPARATOR) { 7 | dest[len++] = ZOO_PATH_SEPARATOR; 8 | dest[len] = '\0'; 9 | } 10 | strncpy(dest + len, src, n - len); 11 | } 12 | 13 | typedef struct { 14 | char fn_cmp[ZOO_PATH_MAX + 1]; 15 | char fn_found[ZOO_PATH_MAX + 1]; 16 | } zoo_io_translate_state; 17 | 18 | static bool zoo_io_translate_compare(zoo_io_path_driver *drv, zoo_io_dirent *e, void *cb_arg) { 19 | zoo_io_translate_state *ts = (zoo_io_translate_state *) cb_arg; 20 | 21 | if (e->type == TYPE_FILE && !strcasecmp(e->name, ts->fn_cmp)) { 22 | strncpy(ts->fn_found, e->name, ZOO_PATH_MAX); 23 | return false; 24 | } else { 25 | return true; 26 | } 27 | } 28 | 29 | static void zoo_io_translate(zoo_io_path_driver *drv, const char *filename, char *buffer, size_t buflen) { 30 | zoo_io_translate_state ts; 31 | 32 | // fn_cmp - searched name, fn_found - found actual name 33 | strncpy(ts.fn_cmp, filename, ZOO_PATH_MAX); 34 | ts.fn_found[0] = '\0'; 35 | 36 | drv->func_dir_scan(drv, drv->path, zoo_io_translate_compare, &ts); 37 | 38 | strncpy(buffer, drv->path, buflen); 39 | zoo_path_cat(buffer, (ts.fn_found[0] != '\0') ? ts.fn_found : ts.fn_cmp, buflen); 40 | } 41 | 42 | static zoo_io_handle zoo_io_open_relative(zoo_io_driver *p_drv, const char *filename, zoo_io_mode mode) { 43 | zoo_io_path_driver *drv = (zoo_io_path_driver*) p_drv; 44 | char buffer[ZOO_PATH_MAX + 1]; 45 | 46 | // only support files, not directories, here 47 | if (strchr(filename, ZOO_PATH_SEPARATOR) != NULL) { 48 | return zoo_io_open_file_empty(); 49 | } 50 | 51 | if (drv->func_dir_scan != NULL) { 52 | zoo_io_translate(drv, filename, buffer, ZOO_PATH_MAX); 53 | } else { 54 | strncpy(buffer, drv->path, ZOO_PATH_MAX); 55 | zoo_path_cat(buffer, filename, ZOO_PATH_MAX); 56 | } 57 | 58 | // try opening file 59 | return drv->func_open_file_absolute(drv, buffer, mode); 60 | } 61 | 62 | static bool zoo_io_path_advance(zoo_io_path_driver *drv, char *dest, const char *curr, const char *next, size_t len) { 63 | char *pathsep_pos; 64 | int pathsep_len; 65 | 66 | if (curr == NULL) { 67 | return false; 68 | } else if (next == NULL || !strcmp(next, ".")) { 69 | strncpy(dest, curr, len); 70 | return true; 71 | } else if (!strcmp(next, "..")) { 72 | pathsep_pos = strrchr(curr, ZOO_PATH_SEPARATOR); 73 | if (pathsep_pos == NULL) { 74 | return false; 75 | } 76 | pathsep_len = pathsep_pos - curr; 77 | if (pathsep_len >= len) { 78 | return false; 79 | } 80 | memcpy(dest, curr, pathsep_len); 81 | dest[pathsep_len] = '\0'; 82 | return true; 83 | } else { 84 | strncpy(dest, curr, len); 85 | zoo_path_cat(dest, next, len); 86 | return true; 87 | } 88 | } 89 | 90 | void zoo_io_internal_init_path_driver(zoo_io_path_driver *drv) { 91 | memset(drv, 0, sizeof(zoo_io_path_driver)); 92 | drv->parent.func_open_file = zoo_io_open_relative; 93 | drv->func_dir_advance = zoo_io_path_advance; 94 | } 95 | -------------------------------------------------------------------------------- /src/drivers/zoo_io_posix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "zoo_io_posix.h" 6 | 7 | #include 8 | 9 | static uint8_t zoo_io_file_getc(zoo_io_handle *h) { 10 | FILE *f = (FILE*) h->p; 11 | int result = fgetc(f); 12 | return (result == EOF) ? 0 : result; 13 | } 14 | 15 | static size_t zoo_io_file_putc(zoo_io_handle *h, uint8_t v) { 16 | FILE *f = (FILE*) h->p; 17 | return (fputc(v, f) == v) ? 1 : 0; 18 | } 19 | 20 | static size_t zoo_io_file_read(zoo_io_handle *h, uint8_t *d_ptr, size_t len) { 21 | FILE *f = (FILE*) h->p; 22 | return fread(d_ptr, 1, len, f); 23 | } 24 | 25 | static size_t zoo_io_file_write(zoo_io_handle *h, const uint8_t *d_ptr, size_t len) { 26 | FILE *f = (FILE*) h->p; 27 | return fwrite(d_ptr, 1, len, f); 28 | } 29 | 30 | static size_t zoo_io_file_skip(zoo_io_handle *h, size_t len) { 31 | FILE *f = (FILE*) h->p; 32 | // TODO: check if works on writes 33 | fseek(f, len, SEEK_CUR); 34 | return len; 35 | } 36 | 37 | static size_t zoo_io_file_tell(zoo_io_handle *h) { 38 | FILE *f = (FILE*) h->p; 39 | return ftell(f); 40 | } 41 | 42 | static void zoo_io_file_close(zoo_io_handle *h) { 43 | FILE *f = (FILE*) h->p; 44 | fclose(f); 45 | } 46 | 47 | static zoo_io_handle zoo_io_open_file_posix(zoo_io_path_driver *drv, const char *name, zoo_io_mode mode) { 48 | FILE *file; 49 | zoo_io_handle h; 50 | 51 | file = fopen(name, mode == MODE_WRITE ? "wb" : "rb"); 52 | if (file == NULL) { 53 | return zoo_io_open_file_empty(); 54 | } 55 | 56 | h.p = file; 57 | h.func_getptr = NULL; 58 | h.func_getc = zoo_io_file_getc; 59 | h.func_putc = zoo_io_file_putc; 60 | h.func_read = zoo_io_file_read; 61 | h.func_write = zoo_io_file_write; 62 | h.func_skip = zoo_io_file_skip; 63 | h.func_tell = zoo_io_file_tell; 64 | h.func_close = zoo_io_file_close; 65 | return h; 66 | } 67 | 68 | static inline int64_t zoo_io_get_mtime(const char *basename, const char *name) { 69 | struct stat statinfo; 70 | char path[ZOO_PATH_MAX + 1]; 71 | 72 | strncpy(path, basename, ZOO_PATH_MAX); 73 | zoo_path_cat(path, basename, ZOO_PATH_MAX); 74 | stat(path, &statinfo); 75 | return statinfo.st_mtime; 76 | } 77 | 78 | static bool zoo_io_scan_dir_posix(zoo_io_path_driver *drv, const char *name, zoo_func_io_scan_dir_callback cb, void *cb_arg) { 79 | DIR *dir; 80 | struct dirent *dent; 81 | zoo_io_dirent ent; 82 | 83 | dir = opendir(name); 84 | if (dir == NULL) { 85 | return false; 86 | } 87 | 88 | while ((dent = readdir(dir)) != NULL) { 89 | strncpy(ent.name, dent->d_name, ZOO_PATH_MAX); 90 | #if !defined(_DIRENT_HAVE_D_TYPE) 91 | // TODO: unhackify 92 | FILE *test_file = fopen(dent->d_name, "rb"); 93 | if (test_file != NULL) { 94 | ent.type = TYPE_FILE; 95 | fclose(test_file); 96 | } else { 97 | ent.type = TYPE_DIR; 98 | } 99 | #elif defined(_PSP_FW_VERSION) 100 | // TODO: unhackify 101 | ent.type = FIO_SO_ISDIR(dent->d_stat.st_mode) ? TYPE_DIR : TYPE_FILE; 102 | #else 103 | ent.type = dent->d_type == DT_DIR ? TYPE_DIR : TYPE_FILE; 104 | #endif 105 | if (ent.type == TYPE_FILE) { 106 | ent.mtime = zoo_io_get_mtime(name, ent.name); 107 | } 108 | 109 | if (!cb(drv, &ent, cb_arg)) { 110 | break; 111 | } 112 | } 113 | 114 | closedir(dir); 115 | return true; 116 | } 117 | 118 | void zoo_io_create_posix_driver(zoo_io_path_driver *drv) { 119 | zoo_io_internal_init_path_driver(drv); 120 | 121 | if (getcwd(drv->path, ZOO_PATH_MAX) == NULL) { 122 | strncpy(drv->path, "/", ZOO_PATH_MAX); 123 | } 124 | 125 | drv->func_open_file_absolute = zoo_io_open_file_posix; 126 | drv->func_dir_scan = zoo_io_scan_dir_posix; 127 | } 128 | -------------------------------------------------------------------------------- /src/drivers/zoo_io_romfs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../libzoo/zoo_internal.h" 6 | #include "zoo_io_romfs.h" 7 | 8 | // TODO: add support for directories 9 | 10 | static enum { 11 | ROMFS_TYPE_HLINK = 0, 12 | ROMFS_TYPE_DIR = 1, 13 | ROMFS_TYPE_FILE = 2, 14 | ROMFS_TYPE_SLINK = 3, 15 | ROMFS_TYPE_BLOCK = 4, 16 | ROMFS_TYPE_CHAR = 5, 17 | ROMFS_TYPE_SOCKET = 6, 18 | ROMFS_TYPE_FIFO = 7 19 | } romfs_type; 20 | 21 | static ZOO_INLINE uint32_t romfs_read(uint8_t *ptr) { 22 | return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; 23 | } 24 | 25 | static zoo_io_handle zoo_io_open_file_romfs(zoo_io_path_driver *drv, const char *name, zoo_io_mode mode) { 26 | FILE *file; 27 | zoo_io_handle h; 28 | uint8_t *curr_entry = ((zoo_io_romfs_driver *) drv)->root_ptr; 29 | uint8_t *file_ptr; 30 | uint8_t *base = ((zoo_io_romfs_driver *) drv)->fs; 31 | uint32_t offset; 32 | 33 | if (mode == MODE_WRITE) { 34 | return zoo_io_open_file_empty(); 35 | } 36 | 37 | // skip root path 38 | if (name[0] == '/') { 39 | name++; 40 | } 41 | 42 | // search for file 43 | while (true) { 44 | offset = romfs_read(curr_entry); 45 | if ((offset & 7) == ROMFS_TYPE_FILE && !strcasecmp(name, (char *) (curr_entry + 16))) { 46 | // correct file 47 | file_ptr = curr_entry + ((16 + strlen((char*) (curr_entry + 16)) + 16) & (~15)); 48 | h = zoo_io_open_file_mem(file_ptr, romfs_read(curr_entry + 8), MODE_READ); 49 | return h; 50 | } 51 | 52 | // advance 53 | curr_entry = base + (offset & (~15)); 54 | if (curr_entry == base) { 55 | break; 56 | } 57 | } 58 | 59 | // did not find file 60 | h = zoo_io_open_file_empty(); 61 | return h; 62 | } 63 | 64 | static bool zoo_io_scan_dir_romfs(zoo_io_path_driver *drv, const char *name, zoo_func_io_scan_dir_callback cb, void *cb_arg) { 65 | zoo_io_dirent ent; 66 | uint8_t *curr_entry = ((zoo_io_romfs_driver *) drv)->root_ptr; 67 | uint8_t *base = ((zoo_io_romfs_driver *) drv)->fs; 68 | uint32_t offset; 69 | 70 | while (true) { 71 | offset = romfs_read(curr_entry); 72 | if ((offset & 7) == ROMFS_TYPE_FILE) { 73 | ent.type = ((offset & 7) == ROMFS_TYPE_DIR) ? TYPE_DIR : TYPE_FILE; 74 | strncpy(ent.name, (char*) (curr_entry + 16), ZOO_PATH_MAX); 75 | 76 | if (!cb(drv, &ent, cb_arg)) { 77 | break; 78 | } 79 | } 80 | 81 | curr_entry = base + (offset & (~15)); 82 | if (curr_entry == base) { 83 | break; 84 | } 85 | } 86 | 87 | return true; 88 | } 89 | 90 | bool zoo_io_create_romfs_driver(zoo_io_romfs_driver *drv, uint8_t *fs_ptr) { 91 | if (memcmp(fs_ptr, "-rom1fs-", 8)) { 92 | return false; 93 | } 94 | // TODO: add checksum validation 95 | 96 | zoo_io_internal_init_path_driver(&drv->parent); 97 | strncpy(drv->parent.path, "/", ZOO_PATH_MAX); 98 | 99 | drv->parent.parent.read_only = true; 100 | drv->parent.func_open_file_absolute = zoo_io_open_file_romfs; 101 | drv->parent.func_dir_scan = zoo_io_scan_dir_romfs; 102 | 103 | // set up filesystem pointers 104 | drv->fs = fs_ptr; 105 | drv->root_ptr = fs_ptr + ((16 + strlen((char*) (fs_ptr + 16)) + 16) & (~15)); 106 | return true; 107 | } -------------------------------------------------------------------------------- /src/drivers/zoo_sound_pcm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_sound_pcm.h" 26 | 27 | void zoo_sound_pcm_generate(zoo_sound_pcm_driver *pcm, uint8_t *stream, size_t len) { 28 | uint8_t n_min = pcm->format_signed ? (-(pcm->volume >> 1)) : (0x80 - (pcm->volume >> 1)); 29 | uint8_t n_ctr = pcm->format_signed ? 0 : 0x80; 30 | uint8_t n_max = pcm->format_signed ? (pcm->volume >> 1) : (0x80 + (pcm->volume >> 1)); 31 | size_t pos = 0; 32 | double samples_per_tick = pcm->frequency * 55 / 1000.0; 33 | double samples_per_drum = pcm->frequency * 1 / 1000.0; 34 | double e_remains; 35 | double e_len; 36 | bool e_completes; 37 | uint32_t e_ticker, e_freq_id, i; 38 | double e_ticker_mod; 39 | zoo_pcm_entry *e_curr, *e_next; 40 | uint16_t e_next_pos; 41 | 42 | while (pos < len) { 43 | if ((pcm->ticks - pcm->buf_ticks) < pcm->latency) { 44 | pcm->buf_ticks = pcm->ticks - pcm->latency; 45 | } 46 | 47 | e_curr = NULL; 48 | e_next = NULL; 49 | if (pcm->buf_pos != pcm->buf_len) { 50 | e_curr = &(pcm->buffer[pcm->buf_pos]); 51 | if (e_curr->tick > (pcm->buf_ticks - pcm->latency)) { 52 | e_curr = NULL; 53 | } 54 | if (e_curr != NULL) { 55 | e_next_pos = (pcm->buf_pos + 1) % ZOO_CONFIG_SOUND_PCM_BUFFER_LEN; 56 | if (e_next_pos != pcm->buf_len) { 57 | e_next = &(pcm->buffer[e_next_pos]); 58 | } 59 | if (e_next != NULL && e_next->tick <= (pcm->buf_ticks - pcm->latency)) { 60 | // we have already played this... advance 61 | pcm->buf_pos = e_next_pos; 62 | continue; 63 | } 64 | } 65 | } else { 66 | // nothing in queue, use chance to force resync 67 | pcm->buf_ticks = pcm->ticks - pcm->latency; 68 | } 69 | 70 | 71 | e_remains = len - pos; 72 | e_len = samples_per_tick - pcm->sub_ticks; 73 | e_completes = true; 74 | if (e_remains < e_len) { 75 | e_len = e_remains; 76 | e_completes = false; 77 | } 78 | 79 | if (e_curr != NULL) { 80 | e_ticker = e_curr->emitted; 81 | } 82 | 83 | // not played yet! 84 | if (e_curr != NULL && e_curr->len > 0) { 85 | for (i = 0; i < e_len; i++, e_ticker++) { 86 | if (e_ticker < samples_per_drum) { 87 | stream[pos + i] = n_ctr; 88 | } else { 89 | e_freq_id = (e_ticker / samples_per_drum) - 1; 90 | if (e_freq_id < 0) e_freq_id = 0; 91 | if (e_freq_id >= e_curr->len) { 92 | if (!e_curr->clear) { 93 | e_freq_id = e_curr->len - 1; 94 | } else { 95 | // note finished 96 | stream[pos + i] = n_ctr; 97 | continue; 98 | } 99 | } 100 | 101 | e_ticker_mod = ((double) pcm->frequency / e_curr->freqs[e_freq_id]) / 2.0; 102 | stream[pos + i] = ((int) (e_ticker / e_ticker_mod) & 1) ? n_max : n_min; 103 | } 104 | } 105 | } else { 106 | // no sound 107 | memset(stream + pos, n_ctr, e_len); 108 | } 109 | 110 | if (e_curr != NULL) { 111 | e_curr->emitted = e_ticker; 112 | } 113 | 114 | pos += e_len; 115 | if (!e_completes) { 116 | // partial sample/end of processing 117 | pcm->sub_ticks += e_len; 118 | } else { 119 | // full new sample 120 | pcm->sub_ticks = 0; 121 | pcm->buf_ticks++; 122 | } 123 | } 124 | } 125 | 126 | static void zoo_sound_pcm_push(zoo_sound_pcm_driver *pcm, const uint16_t *freqs, uint16_t len, bool clear) { 127 | pcm->buffer[pcm->buf_len].tick = pcm->ticks; 128 | pcm->buffer[pcm->buf_len].emitted = 0; 129 | pcm->buffer[pcm->buf_len].freqs = freqs; 130 | pcm->buffer[pcm->buf_len].pos = 0; 131 | pcm->buffer[pcm->buf_len].len = len; 132 | pcm->buffer[pcm->buf_len].clear = clear; 133 | pcm->buf_len = (pcm->buf_len + 1) % ZOO_CONFIG_SOUND_PCM_BUFFER_LEN; 134 | } 135 | 136 | void zoo_sound_pcm_init(zoo_sound_pcm_driver *pcm) { 137 | pcm->parent.func_play_freqs = zoo_sound_pcm_push; 138 | 139 | pcm->buf_pos = 0; 140 | pcm->buf_len = 0; 141 | pcm->buf_ticks = 0; 142 | pcm->sub_ticks = 0; 143 | pcm->ticks = pcm->latency; 144 | } 145 | 146 | void zoo_sound_pcm_tick(zoo_sound_pcm_driver *pcm) { 147 | pcm->ticks++; 148 | } -------------------------------------------------------------------------------- /src/frontend/atari_tos/Makefile: -------------------------------------------------------------------------------- 1 | BASEDIR := $(abspath ../../..) 2 | BUILDDIR := $(abspath ./build) 3 | CURDIR := $(abspath .) 4 | 5 | ZOO_TYPE := frontend 6 | ZOO_USE_DRIVER_IO_POSIX := 1 7 | ZOO_USE_LABEL_CACHE := 1 8 | ZOO_USE_UI := 1 9 | ZOO_USE_UI_SIDEBAR_CLASSIC := 1 10 | ZOO_USE_UI_SIDEBAR_SLIM := 1 11 | SOURCES := \ 12 | src/main.c 13 | OBJECTS := $(BUILDDIR)/4x8_bin.o 14 | 15 | OPTIMIZE_CFLAGS := -s -Os 16 | OPTIMIZE_LDFLAGS := -s -Os 17 | 18 | OUTPUT := zoo 19 | OUTEXT := .tos 20 | 21 | all: $(OUTPUT)$(OUTEXT) 22 | 23 | # arch settings - libcmini 24 | #ARCH_CFLAGS := -m68000 -isystem "$(abspath src/)" -DZOO_PLATFORM_ATARI_ST 25 | #ARCH_LDFLAGS := -m68000 -nostdlib 26 | #LIB_FLAGS := -lgem -lcmini -lgcc 27 | # arch settings - mintlib 28 | ARCH_CFLAGS := -m68000 -DZOO_PLATFORM_ATARI_ST 29 | ARCH_LDFLAGS := -m68000 30 | LIB_FLAGS := -lgem 31 | 32 | CC := m68k-atari-mint-gcc 33 | 34 | include $(abspath ${BASEDIR})/src/Makefile 35 | 36 | $(BUILDDIR)/4x8_bin.c $(BUILDDIR)/4x8_bin.h : $(BUILDDIR)/4x8.bin 37 | @echo $(notdir $@) 38 | @mkdir -p $(@D) 39 | @$(PYTHON3) $(TOOLSDIR)/bin2c.py $(BUILDDIR)/4x8_bin.c $(BUILDDIR)/4x8_bin.h $< 40 | 41 | $(BUILDDIR)/4x8.bin : $(CURDIR)/tools/pack1bit.py $(BASEDIR)/fonts/4x8.png 42 | @echo packing $(notdir $@) 43 | @mkdir -p $(@D) 44 | @$(PYTHON3) $(CURDIR)/tools/pack1bit.py $(BASEDIR)/fonts/4x8.png $@ 45 | -------------------------------------------------------------------------------- /src/frontend/atari_tos/src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, 2019, 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "zoo.h" 32 | #include "zoo_io_posix.h" 33 | #include "zoo_sidebar.h" 34 | #include "zoo_sound_pcm.h" 35 | #include "zoo_ui.h" 36 | 37 | enum graphics_mode { 38 | GFX_MODE_MONO, 39 | GFX_MODE_COLOR16 40 | }; 41 | 42 | extern uint8_t _4x8_bin[]; 43 | static MFDB font_4x8_m; 44 | static MFDB screen_m; 45 | 46 | static short wk_work_in[11], wk_work_out[57]; 47 | static short wk_handle; 48 | 49 | static enum graphics_mode gfx_mode; 50 | static short x_offset, y_offset; 51 | static short old_pal_colors[16 * 3]; 52 | static short pal_colors[16 * 3] = { 53 | 0, 0, 0, 54 | 0, 0, 667, 55 | 0, 667, 0, 56 | 0, 667, 667, 57 | 667, 0, 0, 58 | 667, 0, 667, 59 | 667, 333, 0, 60 | 667, 667, 667, 61 | 333, 333, 333, 62 | 333, 333, 1000, 63 | 333, 1000, 333, 64 | 333, 1000, 1000, 65 | 1000, 333, 333, 66 | 1000, 333, 1000, 67 | 1000, 1000, 333, 68 | 1000, 1000, 1000 69 | }; 70 | 71 | static void init_gfx(void) { 72 | memset(&font_4x8_m, 0, sizeof(MFDB)); 73 | memset(&screen_m, 0, sizeof(MFDB)); 74 | 75 | font_4x8_m.fd_addr = _4x8_bin; 76 | font_4x8_m.fd_w = 4 * 32; 77 | font_4x8_m.fd_h = 8 * 8; 78 | font_4x8_m.fd_wdwidth = font_4x8_m.fd_w >> 4; 79 | font_4x8_m.fd_nplanes = 1; 80 | 81 | if (gfx_mode == GFX_MODE_COLOR16) { 82 | for (short i = 0; i < 16; i++) { 83 | vq_color(wk_handle, i, 0, old_pal_colors + (i * 3)); 84 | vs_color(wk_handle, i, pal_colors + (i * 3)); 85 | } 86 | } 87 | } 88 | 89 | static void deinit_gfx(void) { 90 | if (gfx_mode == GFX_MODE_COLOR16) { 91 | for (short i = 0; i < 16; i++) { 92 | vs_color(wk_handle, i, old_pal_colors + (i * 3)); 93 | } 94 | } 95 | } 96 | 97 | static zoo_state state; 98 | static zoo_ui_state ui_state; 99 | static zoo_video_driver video_driver; 100 | static zoo_io_path_driver io_driver; 101 | 102 | static void vdi_write_char(zoo_video_driver *drv, int16_t x, int16_t y, uint8_t col, uint8_t chr) { 103 | short coords[8]; 104 | short colors[2]; 105 | 106 | if (gfx_mode == GFX_MODE_COLOR16) { 107 | colors[0] = col & 15; 108 | colors[1] = (col >> 4) & 7; 109 | } else { 110 | uint8_t fg = col & 15; 111 | uint8_t bg = (col >> 4) & 7; 112 | 113 | colors[0] = fg != 0 ? 0 : 1; 114 | colors[1] = (fg == 0 && bg != 0) ? 0 : 1; 115 | } 116 | 117 | coords[4] = (x << 2) + x_offset; 118 | coords[5] = (y << 3) + y_offset; 119 | coords[6] = coords[4] + 3; 120 | coords[7] = coords[5] + 7; 121 | 122 | coords[0] = (chr & 31) << 2; 123 | coords[1] = (chr >> 5) << 3; 124 | coords[2] = coords[0] + 3; 125 | coords[3] = coords[1] + 7; 126 | 127 | vrt_cpyfm(wk_handle, 1, coords, &font_4x8_m, &screen_m, colors); 128 | } 129 | 130 | int main(int argc, char **argv) { 131 | srand(time(NULL)); 132 | appl_init(); 133 | 134 | wk_handle = graf_handle(NULL, NULL, NULL, NULL); 135 | for (int i = 0; i < 10; i++) 136 | wk_work_in[i] = 1; 137 | wk_work_in[10] = 2; 138 | 139 | v_opnvwk(wk_work_in, &wk_handle, wk_work_out); 140 | 141 | // TODO: crashes 142 | /* if (wk_work_out[0] < 319 || wk_work_out[1] < 199) { 143 | form_alert(1, "[3][Cannot launch:|Resolution too low!][ OK ]"); 144 | goto FinishWk; 145 | } */ 146 | 147 | x_offset = (wk_work_out[0] + 1 - 320) >> 1; 148 | y_offset = (wk_work_out[1] + 1 - 200) >> 1; 149 | gfx_mode = wk_work_out[13] >= 16 ? GFX_MODE_COLOR16 : GFX_MODE_MONO; 150 | 151 | v_exit_cur(wk_handle); 152 | init_gfx(); 153 | 154 | zoo_state_init(&state); 155 | zoo_io_create_posix_driver(&io_driver); 156 | video_driver.func_write = vdi_write_char; 157 | state.d_io = &io_driver.parent; 158 | state.d_video = &video_driver; 159 | state.random_seed = rand(); 160 | 161 | state.func_draw_sidebar = zoo_draw_sidebar_classic; 162 | 163 | zoo_redraw(&state); 164 | zoo_ui_init(&ui_state, &state); 165 | 166 | bool running = true; 167 | short tdelay = 55; 168 | while (running) { 169 | short kmeta; 170 | short kreturn; 171 | short recv_events = evnt_multi( 172 | MU_KEYBD | MU_TIMER, 173 | 0, 0, 0, 174 | 0, 0, 0, 0, 0, 175 | 0, 0, 0, 0, 0, 176 | NULL, 177 | tdelay, 178 | NULL, NULL, NULL, 179 | NULL, &kreturn, 180 | NULL 181 | ); 182 | 183 | if (recv_events & MU_KEYBD) { 184 | int action = ZOO_ACTION_MAX; 185 | switch (kreturn >> 8) { /* scancode */ 186 | case 1: /* ESC */ 187 | action = ZOO_ACTION_CANCEL; 188 | break; 189 | case 28: /* Enter */ 190 | action = ZOO_ACTION_OK; 191 | break; 192 | case 20: /* T */ 193 | action = ZOO_ACTION_TORCH; 194 | break; 195 | case 72: /* Up */ 196 | action = ZOO_ACTION_UP; 197 | break; 198 | case 75: /* Left */ 199 | action = ZOO_ACTION_LEFT; 200 | break; 201 | case 77: /* Right */ 202 | action = ZOO_ACTION_RIGHT; 203 | break; 204 | case 80: /* Down */ 205 | action = ZOO_ACTION_DOWN; 206 | break; 207 | } 208 | if (action < ZOO_ACTION_MAX) { 209 | zoo_input_action_down(&state.input, action); 210 | zoo_input_action_up(&state.input, action); 211 | } else if ((kreturn >> 8) == 59) { 212 | /* F1 */ 213 | if (zoo_call_empty(&state.call_stack)) { 214 | zoo_ui_main_menu(&ui_state); 215 | } 216 | } else if ((kreturn & 0xDF) == 'Q') { 217 | running = false; 218 | } 219 | } 220 | if (recv_events & MU_TIMER) { 221 | graf_mkstate(NULL, NULL, NULL, &kmeta); 222 | zoo_input_action_set(&state.input, ZOO_ACTION_SHOOT, (kmeta & 0x03) != 0); 223 | 224 | zoo_tick_advance_pit(&state); 225 | zoo_sound_tick(&state.sound); 226 | zoo_input_tick(&state.input); 227 | 228 | bool ticking = true; 229 | while (ticking) { 230 | switch (zoo_tick(&state)) { 231 | case RETURN_IMMEDIATE: 232 | break; 233 | case RETURN_NEXT_FRAME: 234 | tdelay = 16; 235 | ticking = false; 236 | break; 237 | case RETURN_NEXT_CYCLE: 238 | tdelay = 55; 239 | ticking = false; 240 | break; 241 | } 242 | } 243 | } 244 | } 245 | 246 | deinit_gfx(); 247 | 248 | FinishWk: 249 | v_clsvwk(wk_handle); 250 | 251 | appl_exit(); 252 | 253 | return 0; 254 | } 255 | -------------------------------------------------------------------------------- /src/frontend/atari_tos/tools/pack1bit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (c) 2020 Adrian Siekierka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | # format: raster image 24 | 25 | from PIL import Image 26 | import struct, sys 27 | 28 | im = Image.open(sys.argv[1]).convert("RGBA") 29 | with open(sys.argv[2], "wb") as fp: 30 | for y in range(0, im.height): 31 | for x in range(0, im.width, 8): 32 | ti = 0 33 | for xi in range(0, 8): 34 | pxl = im.getpixel((x+xi, y)) 35 | ti = (ti << 1) 36 | if pxl[0] > 128: 37 | ti = ti + 1 38 | fp.write(struct.pack(" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "zoo.h" 29 | #include "zoo_io_romfs.h" 30 | #include "zoo_sidebar.h" 31 | #include "zoo_ui.h" 32 | #include "zoo_gba.h" 33 | #include "sound_gba.h" 34 | #include "video_gba.h" 35 | 36 | #include "4x6_bin.h" 37 | 38 | extern u32 __rom_end__; 39 | 40 | static zoo_state state; 41 | static zoo_ui_state ui_state; 42 | // EWRAM_BSS 43 | static zoo_io_romfs_driver d_io; 44 | 45 | extern volatile uint16_t keys_down; 46 | extern volatile uint16_t keys_held; 47 | volatile bool tick_requested = false; 48 | volatile uint16_t ticks = 0; 49 | 50 | #ifdef ZOO_DEBUG_MENU 51 | 52 | #include 53 | #include 54 | 55 | /* https://devkitpro.org/viewtopic.php?f=6&t=3057 */ 56 | 57 | extern uint8_t *fake_heap_end; 58 | extern uint8_t *fake_heap_start; 59 | 60 | int platform_debug_free_memory(void) { 61 | struct mallinfo info = mallinfo(); 62 | return info.fordblks + (fake_heap_end - (uint8_t*)sbrk(0)); 63 | } 64 | 65 | #endif 66 | 67 | bool platform_is_rom_ptr(void *ptr) { 68 | return (((u32) ptr) & 0x08000000) != 0; 69 | } 70 | 71 | IWRAM_ARM_CODE static void irq_timer_pit(void) { 72 | REG_IE |= (IRQ_VBLANK | IRQ_VCOUNT); 73 | REG_IME = 1; 74 | 75 | ticks++; 76 | tick_requested = true; 77 | zoo_tick_advance_pit(&state); 78 | zoo_sound_tick(&(state.sound)); 79 | zoo_input_tick(&(state.input)); 80 | } 81 | 82 | #define dbg_ticks() (REG_TM2CNT_L | (REG_TM3CNT_L << 16)) 83 | 84 | #ifdef DEBUG_CONSOLE 85 | #define DBG_TICK_TIME_LEN 16 86 | static u32 dbg_tick_times[DBG_TICK_TIME_LEN]; 87 | static u16 dbg_tick_time_pos = 0; 88 | 89 | void zoo_ui_debug_printf(bool status, const char *format, ...); 90 | #endif 91 | 92 | int main(void) { 93 | zoo_io_handle io_h; 94 | 95 | zoo_video_gba_hide(); 96 | irq_init(isr_master_nest); 97 | 98 | // init game speed timer 99 | REG_TM0CNT_L = 65536 - 14398; 100 | REG_TM0CNT_H = TM_FREQ_64 | TM_IRQ | TM_ENABLE; 101 | 102 | #ifdef DEBUG_CONSOLE 103 | // init ticktime counter 104 | REG_TM2CNT_L = 0; 105 | REG_TM2CNT_H = TM_FREQ_1 | TM_ENABLE; 106 | REG_TM3CNT_L = 0; 107 | REG_TM3CNT_H = TM_FREQ_1 | TM_CASCADE | TM_ENABLE; 108 | #endif 109 | 110 | zoo_state_init(&state); 111 | zoo_video_gba_install(&state, _4x6_bin); 112 | zoo_sound_gba_install(&state); 113 | state.func_draw_sidebar = zoo_draw_sidebar_slim; 114 | 115 | if (!zoo_io_create_romfs_driver(&d_io, (uint8_t *) &__rom_end__)) { 116 | return 0; 117 | } 118 | state.d_io = (zoo_io_driver *) &d_io; 119 | 120 | zoo_ui_init(&ui_state, &state); 121 | zoo_video_gba_set_blinking(true); 122 | zoo_video_gba_show(); 123 | 124 | irq_add(II_TIMER0, irq_timer_pit); 125 | 126 | bool tick_in_progress = false; 127 | bool tick_next_frame = false; 128 | bool used_start = false; 129 | 130 | while(true) { 131 | if (tick_requested) { 132 | tick_requested = false; 133 | #ifdef DEBUG_CONSOLE 134 | u32 tick_time = dbg_ticks(); 135 | #endif 136 | keys_held = keys_down; 137 | 138 | zoo_input_action_set(&state.input, ZOO_ACTION_UP, keys_held & KEY_UP); 139 | zoo_input_action_set(&state.input, ZOO_ACTION_LEFT, keys_held & KEY_LEFT); 140 | zoo_input_action_set(&state.input, ZOO_ACTION_RIGHT, keys_held & KEY_RIGHT); 141 | zoo_input_action_set(&state.input, ZOO_ACTION_DOWN, keys_held & KEY_DOWN); 142 | zoo_input_action_set(&state.input, ZOO_ACTION_SHOOT, keys_held & KEY_A); 143 | zoo_input_action_set(&state.input, ZOO_ACTION_TORCH, keys_held & KEY_B); 144 | zoo_input_action_set(&state.input, ZOO_ACTION_OK, keys_held & KEY_A); 145 | zoo_input_action_set(&state.input, ZOO_ACTION_CANCEL, keys_held & KEY_B); 146 | 147 | if ((keys_down & KEY_START) && zoo_call_empty(&state.call_stack)) { 148 | zoo_input_action_pressed(&state.input, ZOO_ACTION_CANCEL); 149 | zoo_ui_main_menu(&ui_state); 150 | } 151 | 152 | keys_down = 0; 153 | 154 | tick_in_progress = true; 155 | while (tick_in_progress) { 156 | switch (zoo_tick(&state)) { 157 | case RETURN_IMMEDIATE: 158 | break; 159 | case RETURN_NEXT_FRAME: 160 | tick_next_frame = true; 161 | tick_in_progress = false; 162 | break; 163 | case RETURN_NEXT_CYCLE: 164 | tick_in_progress = false; 165 | break; 166 | case EXIT: 167 | break; 168 | } 169 | } 170 | 171 | #ifdef DEBUG_CONSOLE 172 | if (!tick_next_frame) { 173 | dbg_tick_times[(dbg_tick_time_pos++) % DBG_TICK_TIME_LEN] = dbg_ticks() - tick_time; 174 | 175 | u32 avg_tick_time = 0; 176 | u32 max_tick_time = 0; 177 | for (int i = 0; i < DBG_TICK_TIME_LEN; i++) { 178 | avg_tick_time += dbg_tick_times[i]; 179 | if (dbg_tick_times[i] > max_tick_time) { 180 | max_tick_time = dbg_tick_times[i]; 181 | } 182 | } 183 | avg_tick_time = (avg_tick_time + (DBG_TICK_TIME_LEN >> 1)) / DBG_TICK_TIME_LEN; 184 | 185 | zoo_ui_debug_printf(true, "tick time: avg %d cy, max %d cy\n", avg_tick_time, max_tick_time); 186 | fflush(stdout); 187 | } 188 | #endif 189 | } 190 | 191 | if (tick_next_frame) { 192 | VBlankIntrWait(); 193 | tick_requested = true; 194 | tick_next_frame = false; 195 | } else if (!tick_requested) { 196 | Halt(); 197 | } 198 | } 199 | return 0; 200 | } 201 | -------------------------------------------------------------------------------- /src/frontend/gba/src/sound_gba.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "zoo.h" 28 | #include "zoo_gba.h" 29 | #include "sound_gba.h" 30 | 31 | IWRAM_ARM_CODE static void gba_play_freqs(zoo_sound_driver *drv, const uint16_t *freqs, uint16_t len, bool clear); 32 | 33 | static zoo_sound_gba_driver d_sound_gba = { 34 | .parent = { 35 | .func_play_freqs = gba_play_freqs 36 | } 37 | }; 38 | 39 | IWRAM_ARM_CODE static inline void gba_play_sound(uint16_t freq) { 40 | if (freq < 64) { 41 | REG_SOUND2CNT_L = SSQR_DUTY1_2 | SSQR_IVOL(0); 42 | REG_SOUND2CNT_H = SFREQ_RESET; 43 | } else { 44 | REG_SOUND2CNT_L = SSQR_DUTY1_2 | SSQR_IVOL(12); 45 | REG_SOUND2CNT_H = (2048 - (131072 / (int)freq)) | SFREQ_RESET; 46 | } 47 | } 48 | 49 | IWRAM_ARM_CODE static void irq_timer_sound(void) { 50 | REG_IE |= (IRQ_VBLANK | IRQ_VCOUNT); 51 | REG_IME = 1; 52 | 53 | if (d_sound_gba.sound_len == 0) { 54 | gba_play_sound(0); 55 | } else { 56 | gba_play_sound(*(d_sound_gba.sound_freqs++)); 57 | d_sound_gba.sound_len--; 58 | if (d_sound_gba.sound_len > 0 || d_sound_gba.sound_clear_flag) { 59 | // set timer 60 | REG_TM1CNT_L = 65536 - 262; 61 | REG_TM1CNT_H = TM_FREQ_64 | TM_IRQ | TM_ENABLE; 62 | return; 63 | } 64 | } 65 | 66 | // reset timer 67 | REG_TM1CNT_H = 0; 68 | } 69 | 70 | IWRAM_ARM_CODE static void gba_play_freqs(zoo_sound_driver *drv, const uint16_t *freqs, uint16_t len, bool clear) { 71 | zoo_sound_gba_driver *gba_drv = (zoo_sound_gba_driver *) drv; 72 | 73 | gba_drv->sound_freqs = freqs; 74 | gba_drv->sound_len = len; 75 | gba_drv->sound_clear_flag = clear; 76 | irq_timer_sound(); 77 | } 78 | 79 | void zoo_sound_gba_clear(void) { 80 | gba_play_sound(0); 81 | } 82 | 83 | void zoo_sound_gba_install(zoo_state *state) { 84 | irq_add(II_TIMER1, irq_timer_sound); 85 | 86 | // init sound 87 | REG_SOUNDCNT_X = SSTAT_ENABLE; 88 | REG_SOUNDCNT_L = SDMG_LVOL(7) | SDMG_RVOL(7) | SDMG_LSQR2 | SDMG_RSQR2; 89 | REG_SOUNDCNT_H = SDS_DMG100; 90 | 91 | state->sound.d_sound = (zoo_sound_driver *) &d_sound_gba; 92 | } 93 | -------------------------------------------------------------------------------- /src/frontend/gba/src/sound_gba.h: -------------------------------------------------------------------------------- 1 | #ifndef __SOUND_GBA__ 2 | #define __SOUND_GBA__ 3 | 4 | #include 5 | #include 6 | #include "zoo.h" 7 | 8 | typedef struct { 9 | zoo_sound_driver parent; 10 | 11 | const uint16_t *sound_freqs; 12 | uint16_t sound_len; 13 | bool sound_clear_flag; 14 | } zoo_sound_gba_driver; 15 | 16 | void zoo_sound_gba_clear(void); 17 | void zoo_sound_gba_install(zoo_state *state); 18 | 19 | #endif /* __SOUND_GBA__ */ -------------------------------------------------------------------------------- /src/frontend/gba/src/video_gba.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "zoo.h" 28 | #include "zoo_gba.h" 29 | #include "video_gba.h" 30 | 31 | #define FONT_HEIGHT 6 32 | #define MAP_Y_OFFSET 1 33 | #define MAP_ADDR_OFFSET 0x8000 34 | 35 | static const u16 default_palette[] = { 36 | 0x0000, 37 | 0x5000, 38 | 0x0280, 39 | 0x5280, 40 | 0x0014, 41 | 0x5014, 42 | 0x0154, 43 | 0x5294, 44 | 0x294A, 45 | 0x7D4A, 46 | 0x2BEA, 47 | 0x7FEA, 48 | 0x295F, 49 | 0x7D5F, 50 | 0x2BFF, 51 | 0x7FFF 52 | }; 53 | 54 | static zoo_state *inst_state; 55 | static uint16_t disp_y_offset; 56 | static bool blinking_enabled = false; 57 | static uint8_t blink_ticks; 58 | 59 | volatile uint16_t keys_down = 0; 60 | volatile uint16_t keys_held = 0; 61 | 62 | IWRAM_ARM_CODE 63 | static void vram_update_bgcnt(void) { 64 | #ifdef ZOO_GBA_ENABLE_BLINKING 65 | int fg_cbb = (blinking_enabled && (blink_ticks & 16)) ? 1 : 0; 66 | #else 67 | int fg_cbb = 0; 68 | #endif 69 | 70 | REG_BG0CNT = BG_PRIO(3) | BG_CBB(0) | BG_SBB((MAP_ADDR_OFFSET >> 11) + 0) | BG_4BPP | BG_SIZE0; 71 | REG_BG1CNT = BG_PRIO(2) | BG_CBB(0) | BG_SBB((MAP_ADDR_OFFSET >> 11) + 1) | BG_4BPP | BG_SIZE0; 72 | REG_BG2CNT = BG_PRIO(1) | BG_CBB(fg_cbb) | BG_SBB((MAP_ADDR_OFFSET >> 11) + 2) | BG_4BPP | BG_SIZE0; 73 | REG_BG3CNT = BG_PRIO(0) | BG_CBB(fg_cbb) | BG_SBB((MAP_ADDR_OFFSET >> 11) + 3) | BG_4BPP | BG_SIZE0; 74 | } 75 | 76 | IWRAM_ARM_CODE 77 | static void vram_write_tile_1bpp(const uint8_t *data, uint32_t *vram_pos) { 78 | for (int iy = 0; iy < 8; iy++, data++) { 79 | uint32_t out = 0; 80 | uint8_t in = *data; 81 | out |= ((in >> 7) & 1) << 28; 82 | out |= ((in >> 6) & 1) << 24; 83 | out |= ((in >> 5) & 1) << 20; 84 | out |= ((in >> 4) & 1) << 16; 85 | out |= ((in >> 3) & 1) << 12; 86 | out |= ((in >> 2) & 1) << 8; 87 | out |= ((in >> 1) & 1) << 4; 88 | out |= ((in) & 1); 89 | *(vram_pos++) = out; 90 | } 91 | } 92 | 93 | #define GET_VRAM_PTRS \ 94 | u16* tile_bg_ptr = (u16*) (MEM_VRAM + MAP_ADDR_OFFSET + ((x&1) << 11) + ((x>>1) << 1) + ((y + MAP_Y_OFFSET) << 6)); \ 95 | u16* tile_fg_ptr = &tile_bg_ptr[1 << 11] 96 | 97 | IWRAM_ARM_CODE static void vram_write_char(zoo_video_driver *drv, int16_t x, int16_t y, uint8_t col, uint8_t chr) { 98 | GET_VRAM_PTRS; 99 | 100 | #ifdef ZOO_GBA_ENABLE_BLINKING 101 | *tile_bg_ptr = '\xDB' | (((col >> 4) & 0x07) << 12); 102 | *tile_fg_ptr = chr | ((col & 0x80) << 1) | (col << 12); 103 | #else 104 | *tile_bg_ptr = '\xDB' | (((col >> 4) & 0x07) << 12); 105 | *tile_fg_ptr = chr | (col << 12); 106 | #endif 107 | } 108 | 109 | static void vram_read_char(zoo_video_driver *drv, int16_t x, int16_t y, uint8_t *col, uint8_t *chr) { 110 | GET_VRAM_PTRS; 111 | 112 | #ifdef ZOO_GBA_ENABLE_BLINKING 113 | *chr = (*tile_fg_ptr & 0xFF); 114 | *col = (*tile_fg_ptr >> 12) | ((*tile_bg_ptr >> 8) & 0x70) | ((*tile_fg_ptr >> 1) & 0x80); 115 | #else 116 | *chr = (*tile_fg_ptr & 0xFF); 117 | *col = (*tile_fg_ptr >> 12) | ((*tile_bg_ptr >> 8) & 0x70); 118 | #endif 119 | } 120 | 121 | IWRAM_ARM_CODE static void irq_vcount(void) { 122 | uint16_t next_vcount; 123 | disp_y_offset += (8 - FONT_HEIGHT); 124 | next_vcount = REG_VCOUNT + FONT_HEIGHT; 125 | REG_BG0VOFS = disp_y_offset; 126 | REG_BG1VOFS = disp_y_offset; 127 | REG_BG2VOFS = disp_y_offset; 128 | REG_BG3VOFS = disp_y_offset; 129 | REG_DISPSTAT = DSTAT_VBL_IRQ | DSTAT_VCT_IRQ | DSTAT_VCT(next_vcount); 130 | } 131 | 132 | IWRAM_ARM_CODE static void irq_vblank(void) { 133 | uint16_t ki = REG_KEYINPUT; 134 | 135 | disp_y_offset = (inst_state->game_state == GS_PLAY) 136 | ? ((FONT_HEIGHT * MAP_Y_OFFSET) - ((SCREEN_HEIGHT - (FONT_HEIGHT * 26)) / 2)) 137 | : ((FONT_HEIGHT * MAP_Y_OFFSET) - ((SCREEN_HEIGHT - (FONT_HEIGHT * 25)) / 2)); 138 | REG_DISPSTAT = DSTAT_VBL_IRQ | DSTAT_VCT_IRQ | DSTAT_VCT(FONT_HEIGHT - disp_y_offset); 139 | 140 | #ifdef DEBUG_CONSOLE 141 | if ((~ki) & KEY_R) { 142 | disp_y_offset = (8 * 31) - (8 * 26) + 2; 143 | REG_DISPSTAT = DSTAT_VBL_IRQ | DSTAT_VCT_IRQ | DSTAT_VCT(4); 144 | } 145 | #endif 146 | 147 | REG_BG0VOFS = disp_y_offset; 148 | REG_BG1VOFS = disp_y_offset; 149 | REG_BG2VOFS = disp_y_offset; 150 | REG_BG3VOFS = disp_y_offset; 151 | 152 | keys_down |= ~ki; 153 | 154 | #ifdef ZOO_GBA_ENABLE_BLINKING 155 | if (blinking_enabled) { 156 | if (((blink_ticks++) & 15) == 0) { 157 | vram_update_bgcnt(); 158 | } 159 | } 160 | #endif 161 | } 162 | 163 | static zoo_video_driver d_video_gba = { 164 | .func_write = vram_write_char, 165 | .func_read = vram_read_char 166 | }; 167 | 168 | void zoo_video_gba_hide(void) { 169 | REG_DISPCNT = DCNT_BLANK; 170 | } 171 | 172 | void zoo_video_gba_show(void) { 173 | zoo_redraw(inst_state); 174 | 175 | VBlankIntrWait(); 176 | REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3; 177 | } 178 | 179 | void zoo_video_gba_set_blinking(bool val) { 180 | blinking_enabled = val; 181 | vram_update_bgcnt(); 182 | } 183 | 184 | #ifdef DEBUG_CONSOLE 185 | static u16 console_x = 0; 186 | static u16 console_y = 0; 187 | 188 | #define CONSOLE_WIDTH 60 189 | #define CONSOLE_HEIGHT 3 190 | #define CONSOLE_YOFFSET 27 191 | 192 | void platform_debug_puts(const char *text, bool status) { 193 | if (status) { 194 | // clear line 195 | int x = 0; 196 | int y = CONSOLE_YOFFSET + CONSOLE_HEIGHT; 197 | GET_VRAM_PTRS; 198 | 199 | memset32(tile_fg_ptr, 0, 16); 200 | memset32(tile_fg_ptr + (1 << 10), 0, 16); 201 | 202 | while (*text != '\0') { 203 | char c = *(text++); 204 | if (c == '\n') continue; 205 | vram_write_char(NULL, x++, CONSOLE_YOFFSET + CONSOLE_HEIGHT, 0x0A, c); 206 | } 207 | } else { 208 | while (*text != '\0') { 209 | char c = *(text++); 210 | 211 | if (c == '\n') { 212 | console_x = 0; 213 | console_y += 1; 214 | continue; 215 | } 216 | 217 | while (console_y >= CONSOLE_HEIGHT) { 218 | // scroll one up 219 | int x = 0; 220 | int y = CONSOLE_YOFFSET; 221 | GET_VRAM_PTRS; 222 | 223 | memcpy32(tile_fg_ptr, tile_fg_ptr + 32, 16 * (CONSOLE_HEIGHT - 1)); 224 | memcpy32(tile_fg_ptr + (1 << 10), tile_fg_ptr + 32 + (1 << 10), 16 * (CONSOLE_HEIGHT - 1)); 225 | 226 | memset32(tile_fg_ptr + (32*(CONSOLE_HEIGHT-1)), 0, 16); 227 | memset32(tile_fg_ptr + (32*(CONSOLE_HEIGHT-1)) + (1 << 10), 0, 16); 228 | 229 | console_y--; 230 | } 231 | 232 | vram_write_char(NULL, console_x, console_y + CONSOLE_YOFFSET, 0x0F, c); 233 | console_x += 1; 234 | if (console_x >= CONSOLE_WIDTH) { 235 | console_x = 0; 236 | console_y += 1; 237 | } 238 | } 239 | } 240 | } 241 | #endif 242 | 243 | void zoo_video_gba_install(zoo_state *state, const uint8_t *charset_bin) { 244 | // initialize state 245 | inst_state = state; 246 | state->d_video = &d_video_gba; 247 | 248 | // add interrupts 249 | irq_add(II_VCOUNT, irq_vcount); 250 | irq_add(II_VBLANK, irq_vblank); 251 | 252 | // load 4x6 charset 253 | for (int i = 0; i < 256; i++) { 254 | vram_write_tile_1bpp(charset_bin + i*8, ((uint32_t*) (MEM_VRAM + i*32))); 255 | } 256 | #ifdef ZOO_GBA_ENABLE_BLINKING 257 | // 32KB is used to faciliate blinking: 258 | // chars 0-255: charset, not blinking 259 | // chars 256-511: charset, blinking, visible 260 | // chars 512-767: [blink] charset, not blinking 261 | // chars 768-1023: [blink] empty space, not visible 262 | memcpy32(((uint32_t*) (MEM_VRAM + (256*32))), ((uint32_t*) (MEM_VRAM)), 256 * 32); 263 | memcpy32(((uint32_t*) (MEM_VRAM + (512*32))), ((uint32_t*) (MEM_VRAM)), 256 * 32); 264 | memset32(((uint32_t*) (MEM_VRAM + (768*32))), 0x0000000, 256 * 32); 265 | #endif 266 | 267 | // load palette 268 | for (int i = 0; i < 16; i++) { 269 | pal_bg_mem[(i<<4) | 0] = 0x0000; 270 | pal_bg_mem[(i<<4) | 1] = default_palette[i]; 271 | } 272 | 273 | // initialize background registers 274 | vram_update_bgcnt(); 275 | REG_BG0HOFS = 4; 276 | REG_BG0VOFS = 0; 277 | REG_BG1HOFS = 0; 278 | REG_BG1VOFS = 0; 279 | REG_BG2HOFS = 4; 280 | REG_BG2VOFS = 0; 281 | REG_BG3HOFS = 0; 282 | REG_BG3VOFS = 0; 283 | 284 | // clear display 285 | memset32((void*) (MEM_VRAM + MAP_ADDR_OFFSET), 0x00000000, 64 * 32 * 2); 286 | } 287 | -------------------------------------------------------------------------------- /src/frontend/gba/src/video_gba.h: -------------------------------------------------------------------------------- 1 | #ifndef __VIDEO_GBA__ 2 | #define __VIDEO_GBA__ 3 | 4 | #include 5 | #include 6 | #include "zoo.h" 7 | 8 | void zoo_video_gba_hide(void); 9 | void zoo_video_gba_show(void); 10 | void zoo_video_gba_install(zoo_state *state, const uint8_t *charset_bin); 11 | void zoo_video_gba_set_blinking(bool val); 12 | 13 | #endif /* __VIDEO_GBA__ */ -------------------------------------------------------------------------------- /src/frontend/gba/src/zoo_gba.h: -------------------------------------------------------------------------------- 1 | #ifndef __ZOO_GBA_H__ 2 | #define __ZOO_GBA_H__ 3 | 4 | #ifdef ZOO_DEBUG_MENU 5 | #define DEBUG_CONSOLE 6 | #endif 7 | 8 | #define ZOO_GBA_ENABLE_BLINKING 9 | 10 | #define IWRAM_ARM_CODE __attribute__((section(".iwram"), long_call, target("arm"))) 11 | #define EWRAM_DATA __attribute__((section(".ewram"))) 12 | #define EWRAM_BSS __attribute__((section(".sbss"))) 13 | 14 | #endif /* __ZOO_GBA_H__ */ 15 | -------------------------------------------------------------------------------- /src/frontend/gba/tools/pack4x6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (c) 2020 Adrian Siekierka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | # format: 24 | # 8-byte 1-bit 8x8 bitmaps for each tile 25 | 26 | from PIL import Image 27 | import struct, sys 28 | 29 | tiles = [] 30 | 31 | def write_tile(fp, tile): 32 | for i in range(0, 8): 33 | if i >= len(tile): 34 | fp.write(struct.pack("> 1) 46 | if pxl[0] > 128: 47 | ti = ti + 128 48 | tile[iy] = ti 49 | tile = tuple(tile) 50 | tiles.append(tile) 51 | 52 | im = Image.open(sys.argv[1]).convert("RGBA") 53 | for c in range(256): 54 | cx = int(c % 32) * 4 55 | cy = int(c / 32) * 6 56 | add_tile(im, cx, cy, 4, 6) 57 | 58 | with open(sys.argv[2], "wb") as fp: 59 | for i in range(len(tiles)): 60 | write_tile(fp, tiles[i]) 61 | -------------------------------------------------------------------------------- /src/frontend/gba/tools/pack6x10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (c) 2020 Adrian Siekierka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | # format: 24 | # first 1024 bytes are a array of 256 uint16_t[2]s 25 | # describing the two tiles encompassing a 6x10 character 26 | # next, a uint16_t stores the total count of tiles 27 | # next, 8-byte 1-bit 8x8 bitmaps follow for each tile 28 | 29 | from PIL import Image 30 | import struct, sys 31 | 32 | tile_ids = 0 33 | tile_id_dict = {} 34 | tiles = [] 35 | tile_descs = [] 36 | 37 | def write_tile(fp, tile): 38 | for i in range(0, 8): 39 | if i >= len(tile): 40 | fp.write(struct.pack("> 1) 52 | if pxl[0] > 128: 53 | ti = ti + 128 54 | tile.append(ti) 55 | tile = tuple(tile) 56 | if tile in tile_id_dict: 57 | return tile_id_dict[tile] 58 | else: 59 | tile_id_dict[tile] = tile_ids 60 | tiles.append(tile) 61 | tile_ids = tile_ids + 1 62 | return tile_id_dict[tile] 63 | 64 | im = Image.open(sys.argv[1]).convert("RGBA") 65 | for c in range(256): 66 | cx = int(c % 32) * 6 67 | cy = int(c / 32) * 10 68 | tile_descs.append(add_tile(im, cx, cy, 6, 8)) 69 | tile_descs.append(add_tile(im, cx, cy + 8, 6, 2)) 70 | 71 | with open(sys.argv[2], "wb") as fp: 72 | for i in range(0, 256 * 2): 73 | fp.write(struct.pack(" 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | static u32 __attribute__((aligned(16))) gu_clut4[16]; 40 | static u32 __attribute__((aligned(16))) gu_list[262144]; 41 | static u32 palette_colors[16]; 42 | 43 | int main(int argc, char** argv) { 44 | return 0; 45 | } 46 | 47 | /* 48 | #include "../frontend_curses_tables.c" 49 | 50 | extern unsigned char build_psp_obj_6x10_psp_bin[]; 51 | 52 | PSP_MODULE_INFO("Zeta", 0, 1, 1); 53 | PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU); 54 | PSP_HEAP_SIZE_KB(4096); 55 | PSP_MAIN_THREAD_STACK_SIZE_KB(64); 56 | 57 | long zeta_time_ms(void) { 58 | clock_t c = clock(); 59 | return c / (CLOCKS_PER_SEC/1000); 60 | } 61 | 62 | void cpu_ext_log(const char* s) { 63 | fprintf(stderr, "%s\n", s); 64 | } 65 | 66 | int zeta_has_feature(int feature) { 67 | return 1; 68 | } 69 | 70 | void zeta_update_charset(int width, int height, u8* data) { 71 | } 72 | 73 | void zeta_update_palette(u32* data) { 74 | for (int i = 0; i < 16; i++) { 75 | palette_colors[i] = (data[i] & 0xFF00) | ((data[i] >> 16) & 0xFF) | ((data[i] & 0xFF) << 16) | 0xFF000000; 76 | } 77 | } 78 | 79 | typedef struct { 80 | u32 color; 81 | u16 x, y, z, v_pad; 82 | } point_bg; 83 | 84 | typedef struct { 85 | u16 u, v; 86 | u32 color; 87 | u16 x, y, z, v_pad; 88 | } point_fg; 89 | 90 | #define FRAME_Y_OFFSET ((272-250)/2) 91 | 92 | int psp_exit_cb(int a1, int a2, void *a3) { 93 | sceKernelExitGame(); 94 | return 0; 95 | } 96 | 97 | int psp_exit_thread(SceSize args, void *argp) { 98 | int cbid = sceKernelCreateCallback("exit callback", (void*) psp_exit_cb, NULL); 99 | sceKernelRegisterExitCallback(cbid); 100 | sceKernelSleepThreadCB(); 101 | return 0; 102 | } 103 | 104 | static int zzt_thread_running = 1; 105 | static int psp_osk_open = 0; 106 | static long timer_last_ms; 107 | 108 | int psp_zzt_thread(SceSize args, void *argp) { 109 | int opcodes = 1000; 110 | 111 | timer_last_ms = zeta_time_ms(); 112 | while (zzt_thread_running) { 113 | if (psp_osk_open) { 114 | sceKernelDelayThreadCB(20 * 1000); 115 | continue; 116 | } 117 | 118 | long duration = zeta_time_ms(); 119 | int rcode = zzt_execute(opcodes); 120 | long timer_curr_ms = zeta_time_ms(); 121 | duration = timer_curr_ms - duration; 122 | if (rcode == STATE_CONTINUE) { 123 | if (duration < 2) { 124 | opcodes = (opcodes * 20 / 19); 125 | } else if (duration > 4) { 126 | opcodes = (opcodes * 19 / 20); 127 | } 128 | } 129 | if (rcode == STATE_END) { 130 | zzt_thread_running = 0; 131 | } 132 | 133 | sceKernelDelayThreadCB(1); 134 | } 135 | 136 | return 0; 137 | } 138 | 139 | static long first_timer_tick; 140 | static double timer_time; 141 | 142 | static u16 osk_text[65]; 143 | static int osk_text_pos = 0; 144 | static int osk_counter = 0; 145 | 146 | int psp_timer_thread(SceSize args, void *argp) { 147 | while (zzt_thread_running) { 148 | long curr_timer_tick = zeta_time_ms(); 149 | if (psp_osk_open) { 150 | timer_time = curr_timer_tick - first_timer_tick; 151 | sceKernelDelayThreadCB(20 * 1000); 152 | continue; 153 | } 154 | 155 | zzt_mark_timer(); 156 | 157 | timer_time += SYS_TIMER_TIME; 158 | long duration = curr_timer_tick - first_timer_tick; 159 | long tick_time = ((long) (timer_time + SYS_TIMER_TIME)) - duration; 160 | 161 | while (tick_time <= 0) { 162 | zzt_mark_timer(); 163 | timer_time += SYS_TIMER_TIME; 164 | tick_time = ((long) (timer_time + SYS_TIMER_TIME)) - duration; 165 | } 166 | 167 | osk_counter = (osk_counter + 1) % 3; 168 | if (osk_counter == 0 && osk_text[0] != 0) { 169 | u16 chr = osk_text[osk_text_pos++]; 170 | if (chr == 0) { 171 | osk_text[0] = 0; 172 | osk_text_pos = 0; 173 | zzt_key(13, 0x1C); 174 | zzt_keyup(0x1C); 175 | } else { 176 | if (chr >= 32 && chr <= 127) { 177 | zzt_key(chr, map_char_to_key[chr]); 178 | zzt_keyup(map_char_to_key[chr]); 179 | } 180 | } 181 | } 182 | 183 | sceKernelDelayThreadCB(tick_time * 1000); 184 | } 185 | 186 | return 0; 187 | } 188 | 189 | static void psp_timer_init(void) { 190 | int thid = sceKernelCreateThread("timer", psp_timer_thread, 0x11, 0x800, PSP_THREAD_ATTR_USER, NULL); 191 | if (thid >= 0) { 192 | sceKernelStartThread(thid, 0, 0); 193 | } 194 | } 195 | 196 | static int psp_is_blink_phase(long curr_time) { 197 | return ((curr_time % (BLINK_TOGGLE_DURATION_MS*2)) >= BLINK_TOGGLE_DURATION_MS); 198 | } 199 | 200 | static void psp_draw_frame(void) { 201 | sceGuStart(GU_DIRECT, gu_list); 202 | 203 | // draw 2000 BG cells 204 | point_bg *bg_cells = sceGuGetMemory(sizeof(point_bg) * 4000); 205 | // draw 2000 FG cells 206 | point_fg *fg_cells = sceGuGetMemory(sizeof(point_fg) * 4000); 207 | point_bg *bg_cells_origin = bg_cells; 208 | point_fg *fg_cells_origin = fg_cells; 209 | 210 | u8* ram = zzt_get_ram(); 211 | int i = 0; 212 | int should_blink = psp_is_blink_phase(zeta_time_ms()); 213 | 214 | for (int y = 0; y < 25; y++) { 215 | u16 cy0 = y*10+FRAME_Y_OFFSET; 216 | u16 cy1 = (y+1)*10+FRAME_Y_OFFSET; 217 | for (int x = 0; x < 80; x++, i+=2) { 218 | u8 chr = ram[TEXT_ADDR(x,y)]; 219 | u8 col = ram[TEXT_ADDR(x,y)+1]; 220 | int should_blink_now = 0; 221 | if (col >= 0x80) { 222 | col &= 0x7F; 223 | should_blink_now = should_blink; 224 | } 225 | u32 bg_col = palette_colors[col >> 4]; 226 | u32 fg_col = palette_colors[col & 0xF]; 227 | u16 cx0 = x*6; 228 | u16 cx1 = (x+1)*6; 229 | u32 cu = (chr & 31)<<3; 230 | u32 cv = (chr >> 5)<<4; 231 | 232 | bg_cells[0].color = bg_col; 233 | bg_cells[0].x = cx0; 234 | bg_cells[0].y = cy0; 235 | bg_cells[0].z = 0; 236 | bg_cells[1].color = bg_col; 237 | bg_cells[1].x = cx1; 238 | bg_cells[1].y = cy1; 239 | bg_cells[1].z = 0; 240 | bg_cells += 2; 241 | 242 | if (!should_blink_now && ((col ^ (col >> 4)) & 0x0F) && chr != 0 && chr != 32) { 243 | fg_cells[0].u = cu; 244 | fg_cells[0].v = cv; 245 | fg_cells[0].color = fg_col; 246 | fg_cells[0].x = cx0; 247 | fg_cells[0].y = cy0; 248 | fg_cells[0].z = 0; 249 | fg_cells[1].u = cu+6; 250 | fg_cells[1].v = cv+10; 251 | fg_cells[1].color = fg_col; 252 | fg_cells[1].x = cx1; 253 | fg_cells[1].y = cy1; 254 | fg_cells[1].z = 0; 255 | fg_cells += 2; 256 | } 257 | } 258 | } 259 | 260 | sceGuDisable(GU_TEXTURE_2D); 261 | sceGumDrawArray(GU_SPRITES, GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, (bg_cells - bg_cells_origin), 0, bg_cells_origin); 262 | sceGuEnable(GU_TEXTURE_2D); 263 | sceGumDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, (fg_cells - fg_cells_origin), 0, fg_cells_origin); 264 | 265 | sceGuFinish(); 266 | sceGuSync(0, 0); 267 | 268 | sceDisplayWaitVblankStartCB(); 269 | sceGuSwapBuffers(); 270 | 271 | zzt_mark_frame(); 272 | } 273 | 274 | void psp_audio_callback(void *stream, unsigned int len, void *userdata) { 275 | if (psp_osk_open) { 276 | memset(stream, 0, len*sizeof(s16)*2); 277 | return; 278 | } 279 | 280 | u8 *stream_u8 = ((u8*) stream) + (len * 3); 281 | s16 *stream_s16 = ((s16*) stream); 282 | 283 | audio_stream_generate_u8(zeta_time_ms(), stream_u8, len); 284 | for (int i = 0; i < len; i++, stream_u8++, stream_s16+=2) { 285 | s8 sample_s8 = (s8) (stream_u8[0] ^ 0x80); 286 | s16 val = ((s16) sample_s8) << 8; 287 | stream_s16[0] = (s16) val; 288 | stream_s16[1] = (s16) val; 289 | } 290 | } 291 | 292 | void speaker_on(int cycles, double freq) { 293 | audio_stream_append_on(zeta_time_ms(), cycles, freq); 294 | } 295 | 296 | void speaker_off(int cycles) { 297 | audio_stream_append_off(zeta_time_ms(), cycles); 298 | } 299 | 300 | static void psp_init_vfs(void) { 301 | char buf[MAXPATHLEN+1]; 302 | 303 | if (getcwd(buf, MAXPATHLEN) == NULL || strlen(buf) <= 0) { 304 | fprintf(stderr, "psp_init_vfs: getcwd failed, using fallback\n"); 305 | if (chdir("ms0:/PSP/GAME/ZETA/") != 0) { 306 | fprintf(stderr, "psp_init_vfs: chdir failed, using fallback\n"); 307 | init_posix_vfs("ms0:/PSP/GAME/ZETA/"); 308 | return; 309 | } 310 | } 311 | 312 | init_posix_vfs(""); 313 | } 314 | 315 | int main(int argc, char** argv) { 316 | SceCtrlData pad; 317 | u32 last_buttons = 0; 318 | 319 | { 320 | psp_init_vfs(); 321 | zzt_init(-1); 322 | 323 | int exeh = vfs_open("zzt.exe", 0); 324 | if (exeh < 0) return -1; 325 | zzt_load_binary(exeh, ""); 326 | vfs_close(exeh); 327 | 328 | zzt_set_timer_offset((time(NULL) % 86400) * 1000L); 329 | zzt_key('k', 0x25); 330 | zzt_keyup(0x25); 331 | zzt_key('c', 0x2E); 332 | zzt_keyup(0x2E); 333 | zzt_key(13, 0x1C); 334 | zzt_keyup(0x1C); 335 | 336 | init_map_char_to_key(); 337 | } 338 | 339 | { 340 | for(int i = 0; i < 15; i++) 341 | gu_clut4[i] = 0; 342 | gu_clut4[15] = 0xFFFFFFFF; 343 | 344 | sceGuInit(); 345 | sceGuStart(GU_DIRECT, gu_list); 346 | sceGuDrawBuffer(GU_PSM_8888, NULL, 512); 347 | sceGuDispBuffer(480, 272, (void*) 0x88000, 512); 348 | sceGuDepthBuffer((void*) 0x110000, 512); 349 | sceGuOffset(2048 - 240, 2048 - 136); 350 | sceGuViewport(2048, 2048, 480, 272); 351 | sceGuDepthRange(0xC350, 0x2710); 352 | sceGuScissor(0, 0, 480, 272); 353 | sceGuEnable(GU_SCISSOR_TEST); 354 | sceGuDisable(GU_DEPTH_TEST); 355 | sceGuShadeModel(GU_FLAT); 356 | sceGuAlphaFunc(GU_GREATER, 0, 0xFF); 357 | sceGuEnable(GU_ALPHA_TEST); 358 | sceGuEnable(GU_TEXTURE_2D); 359 | sceGuClutMode(GU_PSM_8888, 0, 0x0F, 0); 360 | sceGuClutLoad(2, gu_clut4); 361 | sceGuTexMode(GU_PSM_T4, 0, 0, 0); 362 | sceGuTexImage(0, 256, 128, 256, build_psp_obj_6x10_psp_bin); 363 | sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA); 364 | sceGuTexEnvColor(0x0); 365 | sceGuTexOffset(0.0f, 0.0f); 366 | sceGuTexScale(1.0f / 256.0f, 1.0f / 128.0f); 367 | sceGuTexWrap(GU_REPEAT, GU_REPEAT); 368 | sceGuTexFilter(GU_NEAREST, GU_NEAREST); 369 | sceGuFinish(); 370 | sceGuSync(0, 0); 371 | sceGuDisplay(GU_TRUE); 372 | } 373 | 374 | psp_timer_init(); 375 | 376 | // TODO: Can we control this dynamically? 377 | scePowerSetClockFrequency(300, 300, 150); 378 | 379 | { 380 | int thid = sceKernelCreateThread("zzt", psp_zzt_thread, 0x1C, 0x10000, PSP_THREAD_ATTR_USER, NULL); 381 | if (thid >= 0) { 382 | sceKernelStartThread(thid, 0, 0); 383 | } else { 384 | return 1; 385 | } 386 | } 387 | 388 | { 389 | int thid = sceKernelCreateThread("exit handler", psp_exit_thread, 0x1E, 0x800, PSP_THREAD_ATTR_USER, NULL); 390 | if (thid >= 0) { 391 | sceKernelStartThread(thid, 0, 0); 392 | } else { 393 | return 1; 394 | } 395 | } 396 | 397 | audio_stream_init(zeta_time_ms(), 44100); 398 | audio_stream_set_volume(audio_stream_get_max_volume() >> 1); 399 | 400 | pspAudioInit(); 401 | pspAudioSetChannelCallback(0, psp_audio_callback, NULL); 402 | 403 | clock_t last = clock(); 404 | clock_t curr = last; 405 | 406 | sceCtrlSetSamplingCycle(0); 407 | sceCtrlSetSamplingMode(PSP_CTRL_MODE_DIGITAL); 408 | 409 | while (1) { 410 | curr = clock(); 411 | last = curr; 412 | 413 | sceCtrlReadBufferPositive(&pad, 1); 414 | 415 | u32 bp = (~last_buttons) & (pad.Buttons); 416 | u32 br = (~(pad.Buttons)) & last_buttons; 417 | last_buttons = pad.Buttons; 418 | if (bp != 0) { 419 | if (bp & PSP_CTRL_UP) zzt_key(0, 0x48); 420 | if (bp & PSP_CTRL_LEFT) zzt_key(0, 0x4B); 421 | if (bp & PSP_CTRL_DOWN) zzt_key(0, 0x50); 422 | if (bp & PSP_CTRL_RIGHT) zzt_key(0, 0x4D); 423 | if (bp & PSP_CTRL_CROSS) zzt_kmod_set(0x01); 424 | if (bp & PSP_CTRL_CIRCLE) zzt_key(13, 0x1C); 425 | if (bp & PSP_CTRL_SELECT) { 426 | if (pad.Buttons & PSP_CTRL_LTRIGGER) { 427 | zzt_key('y', 20); 428 | zzt_keyup(20); 429 | } else { 430 | zzt_key('w', 16); 431 | zzt_keyup(16); 432 | } 433 | } 434 | if (bp & PSP_CTRL_START) { 435 | if (pad.Buttons & PSP_CTRL_LTRIGGER) { 436 | zzt_key('q', 15); 437 | zzt_keyup(15); 438 | } else { 439 | zzt_key('p', 24); 440 | zzt_keyup(24); 441 | } 442 | } 443 | if (bp & PSP_CTRL_SQUARE) { 444 | if (pad.Buttons & PSP_CTRL_LTRIGGER) { 445 | zzt_key('r', 30); 446 | zzt_keyup(30); 447 | } else { 448 | zzt_key('t', 19); 449 | zzt_keyup(19); 450 | } 451 | } 452 | if (bp & PSP_CTRL_TRIANGLE) { 453 | if (pad.Buttons & PSP_CTRL_LTRIGGER) { 454 | zzt_key('s', 30); 455 | zzt_keyup(30); 456 | } else { 457 | zzt_key(0x1C, 1); 458 | zzt_keyup(1); 459 | } 460 | } 461 | } 462 | 463 | if (br != 0) { 464 | if (br & PSP_CTRL_UP) zzt_keyup(0x48); 465 | if (br & PSP_CTRL_LEFT) zzt_keyup(0x4B); 466 | if (br & PSP_CTRL_DOWN) zzt_keyup(0x50); 467 | if (br & PSP_CTRL_RIGHT) zzt_keyup(0x4D); 468 | if (br & PSP_CTRL_CROSS) zzt_kmod_clear(0x01); 469 | if (br & PSP_CTRL_CIRCLE) zzt_keyup(0x1C); 470 | if (br & PSP_CTRL_RTRIGGER && osk_text[0] == 0) { 471 | scePowerSetClockFrequency(222, 222, 111); 472 | psp_osk_open = 1; 473 | unsigned short desctext[33]; 474 | unsigned short intext[65]; 475 | SceUtilityOskData data[1]; 476 | SceUtilityOskParams params; 477 | 478 | desctext[0] = 'K'; 479 | desctext[1] = 'e'; 480 | desctext[2] = 'y'; 481 | desctext[3] = 'b'; 482 | desctext[4] = 'o'; 483 | desctext[5] = 'a'; 484 | desctext[6] = 'r'; 485 | desctext[7] = 'd'; 486 | desctext[8] = ':'; 487 | intext[0] = 0; 488 | memset(osk_text, 0, sizeof(osk_text)); 489 | 490 | data[0].language = PSP_UTILITY_OSK_LANGUAGE_ENGLISH; 491 | data[0].lines = 1; 492 | data[0].unk_24 = 1; 493 | data[0].inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL; 494 | data[0].desc = desctext; 495 | data[0].intext = intext; 496 | data[0].outtextlength = 64; 497 | data[0].outtextlimit = 64; 498 | data[0].outtext = osk_text; 499 | 500 | memset(¶ms, 0, sizeof(params)); 501 | params.base.size = sizeof(params); 502 | sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_LANGUAGE, ¶ms.base.language); 503 | sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_UNKNOWN, ¶ms.base.buttonSwap); 504 | params.base.graphicsThread = 18; 505 | params.base.accessThread = 20; 506 | params.base.fontThread = 19; 507 | params.base.soundThread = 17; 508 | params.datacount = 1; 509 | params.data = data; 510 | 511 | sceUtilityOskInitStart(¶ms); 512 | while (psp_osk_open) { 513 | sceGuStart(GU_DIRECT, gu_list); 514 | sceGuClearColor(0); 515 | sceGuClearDepth(0); 516 | sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT); 517 | sceGuFinish(); 518 | sceGuSync(0, 0); 519 | switch (sceUtilityOskGetStatus()) { 520 | case PSP_UTILITY_DIALOG_VISIBLE: 521 | sceUtilityOskUpdate(1); 522 | break; 523 | case PSP_UTILITY_DIALOG_QUIT: 524 | sceUtilityOskShutdownStart(); 525 | break; 526 | case PSP_UTILITY_DIALOG_NONE: 527 | psp_osk_open = 0; 528 | break; 529 | } 530 | sceDisplayWaitVblankStartCB(); 531 | sceGuSwapBuffers(); 532 | } 533 | // TODO: likewise - dynamic changing 534 | scePowerSetClockFrequency(300, 300, 150); 535 | 536 | if (data[0].result != PSP_UTILITY_OSK_RESULT_CHANGED) { 537 | osk_text[0] = 0; 538 | } 539 | } 540 | } 541 | 542 | psp_draw_frame(); 543 | } 544 | 545 | sceGuDisplay(GU_FALSE); 546 | sceGuTerm(); 547 | 548 | return 0; 549 | } 550 | */ -------------------------------------------------------------------------------- /src/frontend/psp/tools/fontpack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # Copyright (c) 2018, 2019, 2020 Adrian Siekierka 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import sys, struct 24 | from PIL import Image 25 | 26 | im = Image.open(sys.argv[1]).convert("RGB") 27 | fp = open(sys.argv[2], "wb") 28 | imgdata = [0] * (256*128) 29 | 30 | for ic in range(256): 31 | for iy in range(10): 32 | for ix in range(6): 33 | icx = ix + ((ic & 31)*6) 34 | icy = iy + ((ic >> 5)*10) 35 | imx = ix + ((ic & 31)*8) 36 | imy = iy + ((ic >> 5)*16) 37 | # block_pos = ((imx >> 5) + ((imy >> 3) * 8)) * 128 + (imx & 31) + ((imy & 7) * 32) 38 | block_pos = imy*256 + imx 39 | ip = im.getpixel((icx, icy)) 40 | if ip[0] >= 128 and ip[1] >= 128 and ip[2] >= 128: 41 | imgdata[block_pos] = 15 42 | 43 | for iy in range(128): 44 | for ix in range(0, 256, 2): 45 | v = (imgdata[iy*256+ix+1] << 4) | (imgdata[iy*256+ix] << 0) 46 | fp.write(struct.pack(" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "zoo.h" 31 | #include "zoo_io_posix.h" 32 | #include "zoo_sidebar.h" 33 | #include "zoo_sound_pcm.h" 34 | #include "zoo_ui.h" 35 | #include "types.h" 36 | #include "render_software.h" 37 | 38 | static const uint32_t default_palette[] = { 39 | 0xff000000, 0xff0000aa, 0xff00aa00, 0xff00aaaa, 40 | 0xffaa0000, 0xffaa00aa, 0xffaa5500, 0xffaaaaaa, 41 | 0xff555555, 0xff5555ff, 0xff55ff55, 0xff55ffff, 42 | 0xffff5555, 0xffff55ff, 0xffffff55, 0xffffffff 43 | }; 44 | extern uint8_t res_8x14_bin[]; 45 | 46 | static zoo_state state; 47 | static zoo_ui_state ui_state; 48 | static zoo_io_path_driver io_driver; 49 | static zoo_sound_pcm_driver pcm_driver; 50 | 51 | static video_buffer video; 52 | static render_options render_opts; 53 | static SDL_Window *window; 54 | static SDL_Renderer *renderer; 55 | static SDL_Texture *playfield; 56 | static void *playfield_buffer; 57 | static int playfield_pitch; 58 | static SDL_mutex *playfield_mutex; 59 | static bool playfield_changed; 60 | static bool stop_tick_thread = false; 61 | static SDL_TimerID tick_thread_game; 62 | static SDL_TimerID tick_thread_pit; 63 | 64 | #ifdef __BIG_ENDIAN__ 65 | #define SOFT_PIXEL_FORMAT SDL_PIXELFORMAT_ARGB32 66 | #else 67 | #define SOFT_PIXEL_FORMAT SDL_PIXELFORMAT_BGRA32 68 | #endif 69 | 70 | // audio logic 71 | 72 | static SDL_AudioDeviceID audio_device; 73 | static SDL_AudioSpec audio_spec; 74 | static SDL_mutex *audio_mutex; 75 | 76 | static void sdl_audio_callback(void *userdata, uint8_t *stream, int len) { 77 | SDL_LockMutex(audio_mutex); 78 | zoo_sound_pcm_generate(&pcm_driver, stream, len); 79 | SDL_UnlockMutex(audio_mutex); 80 | } 81 | 82 | static void init_audio(void) { 83 | SDL_AudioSpec requested_audio_spec; 84 | 85 | SDL_zero(requested_audio_spec); 86 | requested_audio_spec.freq = 48000; 87 | requested_audio_spec.format = AUDIO_S8; 88 | requested_audio_spec.channels = 1; 89 | requested_audio_spec.samples = 4096; 90 | requested_audio_spec.callback = sdl_audio_callback; 91 | 92 | audio_mutex = SDL_CreateMutex(); 93 | 94 | // TODO: add SDL_AUDIO_ALLOW_CHANNELS_CHANGE 95 | audio_device = SDL_OpenAudioDevice(NULL, 0, &requested_audio_spec, &audio_spec, 96 | SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); 97 | if (audio_device == 0) { 98 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not open audio device! %s", SDL_GetError()); 99 | } else { 100 | pcm_driver.frequency = audio_spec.freq; 101 | pcm_driver.channels = audio_spec.channels; 102 | pcm_driver.volume = 200; 103 | pcm_driver.latency = 1; 104 | pcm_driver.format_signed = true; 105 | 106 | zoo_sound_pcm_init(&pcm_driver); 107 | state.sound.d_sound = &pcm_driver.parent; 108 | 109 | SDL_PauseAudioDevice(audio_device, 0); 110 | } 111 | } 112 | 113 | static void exit_audio(void) { 114 | if (audio_device != 0) { 115 | SDL_CloseAudioDevice(audio_device); 116 | } 117 | } 118 | 119 | // game logic 120 | 121 | static uint32_t sdl_pit_tick(uint32_t interval, void *param) { 122 | if (stop_tick_thread) { 123 | // cease 124 | return 1000; 125 | } 126 | 127 | SDL_LockMutex(playfield_mutex); 128 | 129 | SDL_LockMutex(audio_mutex); 130 | zoo_tick_advance_pit(&state); 131 | zoo_sound_tick(&state.sound); 132 | zoo_sound_pcm_tick(&pcm_driver); 133 | SDL_UnlockMutex(audio_mutex); 134 | 135 | zoo_input_tick(&state.input); 136 | 137 | SDL_UnlockMutex(playfield_mutex); 138 | 139 | return ZOO_PIT_TICK_MS; 140 | } 141 | 142 | static uint32_t sdl_game_tick(uint32_t interval, void *param) { 143 | int tick_delay = ZOO_PIT_TICK_MS; 144 | bool ticking = true; 145 | 146 | if (stop_tick_thread) { 147 | // cease 148 | return 1000; 149 | } 150 | 151 | SDL_LockMutex(playfield_mutex); 152 | 153 | zoo_ui_tick(&ui_state); 154 | 155 | while (ticking) { 156 | switch (zoo_tick(&state)) { 157 | case RETURN_IMMEDIATE: 158 | break; 159 | case RETURN_NEXT_FRAME: 160 | tick_delay = 16; 161 | ticking = false; 162 | break; 163 | case RETURN_NEXT_CYCLE: 164 | ticking = false; 165 | break; 166 | } 167 | } 168 | SDL_UnlockMutex(playfield_mutex); 169 | 170 | return tick_delay; 171 | } 172 | 173 | void sdl_draw_char(zoo_video_driver *drv, int16_t x, int16_t y, uint8_t col, uint8_t chr) { 174 | software_draw_char(&render_opts, playfield_buffer, playfield_pitch, x, y, col, chr); 175 | playfield_changed = true; 176 | } 177 | 178 | void sdl_render(void) { 179 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 180 | SDL_RenderClear(renderer); 181 | 182 | SDL_LockMutex(playfield_mutex); 183 | SDL_UnlockTexture(playfield); 184 | SDL_RenderCopy(renderer, playfield, NULL, NULL); 185 | SDL_LockTexture(playfield, NULL, &playfield_buffer, &playfield_pitch); 186 | SDL_UnlockMutex(playfield_mutex); 187 | 188 | SDL_RenderPresent(renderer); 189 | } 190 | 191 | static SDL_KeyCode as_shifted(SDL_KeyCode kcode) { 192 | if (kcode >= 'a' && kcode <= 'z') { 193 | return kcode - 32; 194 | } else switch(kcode) { 195 | case '1': return '!'; 196 | case '2': return '@'; 197 | case '3': return '#'; 198 | case '4': return '$'; 199 | case '5': return '%'; 200 | case '6': return '^'; 201 | case '7': return '&'; 202 | case '8': return '*'; 203 | case '9': return '('; 204 | case '0': return ')'; 205 | case '-': return '_'; 206 | case '=': return '+'; 207 | case '[': return '{'; 208 | case ']': return '}'; 209 | case ';': return ':'; 210 | case '\'': return '"'; 211 | case '\\': return '|'; 212 | case ',': return '<'; 213 | case '.': return '>'; 214 | case '/': return '?'; 215 | case '`': return '~'; 216 | default: return kcode; 217 | } 218 | } 219 | 220 | static uint16_t sdl_to_zoo_keycode(SDL_KeyCode code, bool shift) { 221 | if (code > 0 && code < 128) { 222 | return shift ? as_shifted(code) : code; 223 | } else if (code >= SDLK_F1 && code <= SDLK_F10) { 224 | return ((code - SDLK_F1) + ZOO_KEY_F1); 225 | } else switch (code) { 226 | case SDLK_UP: return ZOO_KEY_UP; 227 | case SDLK_LEFT: return ZOO_KEY_LEFT; 228 | case SDLK_RIGHT: return ZOO_KEY_RIGHT; 229 | case SDLK_DOWN: return ZOO_KEY_DOWN; 230 | case SDLK_PAGEUP: return ZOO_KEY_PAGE_UP; 231 | case SDLK_PAGEDOWN: return ZOO_KEY_PAGE_DOWN; 232 | case SDLK_INSERT: return ZOO_KEY_INSERT; 233 | case SDLK_DELETE: return ZOO_KEY_DELETE; 234 | case SDLK_HOME: return ZOO_KEY_HOME; 235 | case SDLK_END: return ZOO_KEY_END; 236 | default: return 0; 237 | } 238 | } 239 | 240 | // main 241 | 242 | // TODO HACK 243 | zoo_video_driver video_driver; 244 | 245 | int main(int argc, char **argv) { 246 | bool use_slim_ui = true; 247 | SDL_Event event; 248 | 249 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) { 250 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed! %s", SDL_GetError()); 251 | return 1; 252 | } 253 | 254 | srand(time(NULL)); 255 | 256 | zoo_state_init(&state); 257 | zoo_io_create_posix_driver(&io_driver); 258 | video_driver.func_write = sdl_draw_char; 259 | state.d_io = &io_driver.parent; 260 | state.d_video = &video_driver; 261 | state.random_seed = rand(); 262 | 263 | if (use_slim_ui) { 264 | state.func_draw_sidebar = zoo_draw_sidebar_slim; 265 | video.width = 60; 266 | video.height = 26; 267 | } else { 268 | state.func_draw_sidebar = zoo_draw_sidebar_classic; 269 | video.width = 80; 270 | video.height = 25; 271 | } 272 | 273 | video.buffer = malloc(video.width * video.height * 2); 274 | 275 | render_opts.charset = res_8x14_bin; 276 | render_opts.palette = default_palette; 277 | render_opts.char_width = 8; 278 | render_opts.char_height = 14; 279 | render_opts.flags = 0; 280 | render_opts.bpp = 32; 281 | 282 | window = SDL_CreateWindow("libzoo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 283 | video.width * render_opts.char_width, 284 | video.height * render_opts.char_height, 285 | SDL_WINDOW_ALLOW_HIGHDPI); 286 | // TODO: SDL_WINDOW_RESIZABLE 287 | 288 | if (window == NULL) { 289 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed! %s", SDL_GetError()); 290 | return 1; 291 | } 292 | 293 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); 294 | 295 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); 296 | if (renderer == NULL) { 297 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer failed! %s", SDL_GetError()); 298 | return 1; 299 | } 300 | 301 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 302 | 303 | playfield = SDL_CreateTexture(renderer, SOFT_PIXEL_FORMAT, 304 | SDL_TEXTUREACCESS_STREAMING, 305 | video.width * render_opts.char_width, 306 | video.height * render_opts.char_height); 307 | SDL_LockTexture(playfield, NULL, &playfield_buffer, &playfield_pitch); 308 | 309 | playfield_mutex = SDL_CreateMutex(); 310 | 311 | zoo_redraw(&state); 312 | zoo_ui_init(&ui_state, &state); 313 | 314 | init_audio(); 315 | 316 | tick_thread_pit = SDL_AddTimer(ZOO_PIT_TICK_MS, sdl_pit_tick, NULL); 317 | tick_thread_game = SDL_AddTimer(1, sdl_game_tick, NULL); 318 | 319 | // run 320 | bool cont_loop = true; 321 | 322 | while (cont_loop) { 323 | SDL_LockMutex(playfield_mutex); 324 | while (SDL_PollEvent(&event)) { 325 | switch (event.type) { 326 | case SDL_KEYDOWN: { 327 | zoo_input_action_set(&(state.input), ZOO_ACTION_SHOOT, (event.key.keysym.mod & KMOD_SHIFT)); 328 | uint16_t kcode = sdl_to_zoo_keycode(event.key.keysym.sym, event.key.keysym.mod & KMOD_SHIFT); 329 | if (kcode != 0) { 330 | zoo_ui_input_key(&state, &ui_state.input, kcode, true); 331 | } 332 | } break; 333 | case SDL_KEYUP: { 334 | zoo_input_action_set(&(state.input), ZOO_ACTION_SHOOT, (event.key.keysym.mod & KMOD_SHIFT)); 335 | uint16_t kcode = sdl_to_zoo_keycode(event.key.keysym.sym, event.key.keysym.mod & KMOD_SHIFT); 336 | if (kcode != 0) { 337 | zoo_ui_input_key(&state, &ui_state.input, kcode, false); 338 | } 339 | } break; 340 | case SDL_QUIT: 341 | cont_loop = false; 342 | break; 343 | } 344 | } 345 | SDL_UnlockMutex(playfield_mutex); 346 | 347 | sdl_render(); 348 | } 349 | 350 | SDL_LockMutex(playfield_mutex); 351 | stop_tick_thread = true; 352 | SDL_RemoveTimer(tick_thread_pit); 353 | SDL_RemoveTimer(tick_thread_game); 354 | SDL_UnlockMutex(playfield_mutex); 355 | 356 | exit_audio(); 357 | 358 | SDL_DestroyTexture(playfield); 359 | SDL_DestroyRenderer(renderer); 360 | SDL_DestroyWindow(window); 361 | 362 | SDL_Quit(); 363 | 364 | return 0; 365 | } 366 | -------------------------------------------------------------------------------- /src/frontend/sdl/src/render_software.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, 2019, 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "render_software.h" 26 | 27 | static void software_draw_char_32bpp(render_options *opts, uint32_t *buffer, int pitch, int x, int y, uint8_t col, uint8_t chr) { 28 | int pos_mul = (opts->flags & RENDER_40COL) ? 2 : 1; 29 | 30 | uint8_t bg = col >> 4; 31 | uint8_t fg = col & 0xF; 32 | const uint8_t *co = opts->charset + (chr * opts->char_height); 33 | 34 | for (int cy = 0; cy < opts->char_height; cy++, co++) { 35 | int line = *co; 36 | int bpos = ((y * opts->char_height + cy) * (pitch >> 2)) + ((x * opts->char_width) * pos_mul); 37 | for (int cx = 0; cx < opts->char_width; cx++, line <<= 1, bpos += pos_mul) { 38 | uint32_t bcol = opts->palette[(line & 0x80) ? fg : bg]; 39 | buffer[bpos] = bcol; 40 | if (pos_mul == 2) buffer[bpos+1] = bcol; 41 | } 42 | } 43 | } 44 | 45 | static void software_draw_char_8bpp(render_options *opts, uint8_t *buffer, int pitch, int x, int y, uint8_t col, uint8_t chr) { 46 | int pos_mul = (opts->flags & RENDER_40COL) ? 2 : 1; 47 | 48 | uint8_t bg = col >> 4; 49 | uint8_t fg = col & 0xF; 50 | const uint8_t *co = opts->charset + (chr * opts->char_height); 51 | 52 | for (int cy = 0; cy < opts->char_height; cy++, co++) { 53 | int line = *co; 54 | int bpos = ((y * opts->char_height + cy) * (pitch >> 2)) + ((x * opts->char_width) * pos_mul); 55 | for (int cx = 0; cx < opts->char_width; cx++, line <<= 1, bpos += pos_mul) { 56 | uint8_t bcol = (line & 0x80) ? fg : bg; 57 | buffer[bpos] = bcol; 58 | if (pos_mul == 2) buffer[bpos+1] = bcol; 59 | } 60 | } 61 | } 62 | 63 | void software_draw_char(render_options *opts, void *buffer, int pitch, int x, int y, uint8_t col, uint8_t chr) { 64 | if (col >= 0x80 && !(opts->flags & RENDER_BLINK_OFF)) { 65 | col &= 0x7F; 66 | if (opts->flags & RENDER_BLINK_PHASE) { 67 | col = (col >> 4) * 0x11; 68 | } 69 | } 70 | 71 | switch (opts->bpp) { 72 | case 8: 73 | software_draw_char_8bpp(opts, buffer, pitch, x, y, col, chr); 74 | break; 75 | case 32: 76 | software_draw_char_32bpp(opts, buffer, pitch, x, y, col, chr); 77 | break; 78 | } 79 | } 80 | 81 | void software_draw_screen(render_options *opts, void *buffer, int pitch, video_buffer *video) { 82 | int pos = 0; 83 | 84 | for (int y = 0; y < video->height; y++) { 85 | for (int x = 0; x < video->width; x++, pos += 2) { 86 | uint8_t chr = video->buffer[pos]; 87 | uint8_t col = video->buffer[pos + 1]; 88 | software_draw_char(opts, buffer, pitch, x, y, col, chr); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/frontend/sdl/src/render_software.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, 2019, 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __RENDER_SOFTWARE_H__ 24 | #define __RENDER_SOFTWARE_H__ 25 | 26 | #include "types.h" 27 | 28 | void software_draw_char(render_options *opts, void *buffer, int pitch, int x, int y, uint8_t col, uint8_t chr); 29 | void software_draw_screen(render_options *opts, void *buffer, int pitch, video_buffer *video); 30 | 31 | #endif /* __RENDER_SOFTWARE_H__ */ 32 | -------------------------------------------------------------------------------- /src/frontend/sdl/src/types.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef __TYPES_H__ 24 | #define __TYPES_H__ 25 | 26 | typedef struct { 27 | int width, height; 28 | uint8_t *buffer; 29 | } video_buffer; 30 | 31 | typedef struct { 32 | const uint8_t *charset; 33 | const uint32_t *palette; 34 | int char_width, char_height; 35 | int flags, bpp; 36 | } render_options; 37 | 38 | #define RENDER_BLINK_OFF 1 39 | #define RENDER_BLINK_PHASE 2 40 | #define RENDER_BLINK_MASK 3 41 | #define RENDER_40COL 4 42 | 43 | #endif /* __TYPES_H__ */ 44 | -------------------------------------------------------------------------------- /src/libzoo/zoo.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_internal.h" 26 | 27 | int zoo_world_reload(zoo_state *state) { 28 | char filename[33]; 29 | zoo_io_handle h; 30 | int ret = 0; 31 | 32 | if (state->world.info.name[0] == '\0') { 33 | // TODO: is this an unsaved world? what does RoZ do on unsaved worlds? 34 | return 0; 35 | } 36 | 37 | if (state->d_io != NULL) { 38 | strncpy(filename, state->world.info.name, sizeof(filename) - 1); 39 | strncat(filename, ".ZZT", sizeof(filename) - 1); 40 | h = state->d_io->func_open_file(state->d_io, filename, MODE_READ); 41 | ret = zoo_world_load(state, &h, false); 42 | state->return_board_id = state->world.info.current_board; 43 | } 44 | 45 | return ret; 46 | } 47 | 48 | int zoo_world_play(zoo_state *state) { 49 | int ret = 0; 50 | 51 | if (state->world.info.is_save) { 52 | ret = zoo_world_reload(state); 53 | } 54 | 55 | if (!ret) { 56 | zoo_game_start(state, GS_PLAY); 57 | 58 | zoo_board_change(state, state->return_board_id); 59 | zoo_board_enter(state); 60 | 61 | zoo_redraw(state); 62 | } 63 | 64 | return ret; 65 | } 66 | 67 | int zoo_world_return_title(zoo_state *state) { 68 | zoo_board_change(state, 0); 69 | zoo_game_start(state, GS_TITLE); 70 | zoo_redraw(state); 71 | return 0; 72 | } 73 | 74 | static int16_t zoo_default_random(zoo_state *state, int16_t max) { 75 | state->random_seed = (state->random_seed * 134775813) + 1; 76 | return state->random_seed % max; 77 | } 78 | 79 | void* zoo_store_display(zoo_state *state, int16_t x, int16_t y, int16_t width, int16_t height) { 80 | int16_t ix, iy; 81 | uint8_t *data, *dp; 82 | 83 | if (state->d_video->func_store_display != NULL) { 84 | return state->d_video->func_store_display(state->d_video, x, y, width, height); 85 | } else if (state->d_video->func_read != NULL) { 86 | data = malloc(width * height * 2); 87 | if (data != NULL) { 88 | // if null, assume nop route - this way out of memory isn't fatal 89 | dp = data; 90 | for (iy = 0; iy < height; iy++) { 91 | for (ix = 0; ix < width; ix++, dp += 2) { 92 | state->d_video->func_read(state->d_video, x + ix, y + iy, dp, dp + 1); 93 | } 94 | } 95 | } 96 | 97 | return data; 98 | } else { 99 | // nop route 100 | return NULL; 101 | } 102 | } 103 | 104 | void zoo_restore_display(zoo_state *state, void *data, int16_t width, int16_t height, int16_t srcx, int16_t srcy, int16_t srcwidth, int16_t srcheight, int16_t dstx, int16_t dsty) { 105 | int16_t ix, iy; 106 | uint8_t *data8; 107 | 108 | if (state->d_video->func_restore_display != NULL) { 109 | return state->d_video->func_restore_display(state->d_video, data, width, height, srcx, srcy, srcwidth, srcheight, dstx, dsty); 110 | } else if (data != NULL && state->d_video->func_write != NULL) { 111 | data8 = ((uint8_t *) data) + (srcy * width * 2) + (srcx * 2); 112 | for (iy = 0; iy < srcheight; iy++) { 113 | for (ix = 0; ix < srcwidth; ix++, data8 += 2) { 114 | state->d_video->func_write(state->d_video, dstx + ix, dsty + iy, data8[0], data8[1]); 115 | } 116 | } 117 | } else { 118 | for (iy = 1; iy <= srcheight; iy++) { 119 | for (ix = 1; ix <= srcwidth; ix++) { 120 | zoo_board_draw_tile(state, dstx + ix, dsty + iy); 121 | } 122 | } 123 | } 124 | } 125 | 126 | void zoo_free_display(zoo_state *state, void *data) { 127 | if (data != NULL) { 128 | free(data); 129 | } 130 | } 131 | 132 | static void zoo_default_video_write(zoo_video_driver *drv, int16_t x, int16_t y, uint8_t a, uint8_t b) { 133 | // pass 134 | } 135 | 136 | static zoo_video_driver d_video_none = { 137 | zoo_default_video_write 138 | }; 139 | 140 | static void zoo_default_draw_sidebar(zoo_state *state, uint16_t flags) { 141 | // pass 142 | } 143 | 144 | void zoo_state_init(zoo_state *state) { 145 | memset(state, 0, sizeof(zoo_state)); 146 | state->d_video = &d_video_none; 147 | 148 | state->func_random = zoo_default_random; 149 | state->func_draw_sidebar = zoo_default_draw_sidebar; 150 | 151 | state->tick_speed = 4; 152 | 153 | zoo_sound_state_init(&(state->sound)); 154 | 155 | state->input.repeat_start = 4; 156 | state->input.repeat_end = 6; 157 | 158 | zoo_world_create(state); 159 | zoo_game_start(state, GS_TITLE); 160 | } 161 | 162 | bool zoo_check_hsecs_elapsed(zoo_state *state, int16_t *hsecs_counter, int16_t hsecs_value) { 163 | int16_t hsecs_total = (int16_t) (state->time_elapsed / 10); 164 | int16_t hsecs_diff = (hsecs_total - (*hsecs_counter) + 6000) % 6000; 165 | return hsecs_diff >= hsecs_value; 166 | } 167 | 168 | bool zoo_has_hsecs_elapsed(zoo_state *state, int16_t *hsecs_counter, int16_t hsecs_value) { 169 | int16_t hsecs_total = (int16_t) (state->time_elapsed / 10); 170 | int16_t hsecs_diff = (hsecs_total - (*hsecs_counter) + 6000) % 6000; 171 | if (hsecs_diff >= hsecs_value) { 172 | *hsecs_counter = hsecs_total; 173 | return true; 174 | } else { 175 | return false; 176 | } 177 | } 178 | 179 | #ifdef ZOO_CONFIG_USE_DOUBLE_FOR_MS 180 | int16_t zoo_hsecs_to_pit_ticks(int16_t hsecs) { 181 | zoo_time_ms ms = hsecs * 10; 182 | return (int16_t) ceil((hsecs * 10) / ZOO_PIT_TICK_MS); 183 | } 184 | 185 | zoo_time_ms zoo_hsecs_to_pit_ms(int16_t hsecs) { 186 | return zoo_hsecs_to_pit_ticks(hsecs) * ZOO_PIT_TICK_MS; 187 | } 188 | #else 189 | zoo_time_ms zoo_hsecs_to_pit_ms(int16_t hsecs) { 190 | zoo_time_ms ms = hsecs * 10 + ZOO_PIT_TICK_MS - 1; 191 | return ms - (ms % ZOO_PIT_TICK_MS); 192 | } 193 | 194 | int16_t zoo_hsecs_to_pit_ticks(int16_t hsecs) { 195 | zoo_time_ms ms = hsecs * 10 + ZOO_PIT_TICK_MS - 1; 196 | return ms / ZOO_PIT_TICK_MS; 197 | } 198 | #endif 199 | 200 | void zoo_redraw(zoo_state *state) { 201 | zoo_board_draw(state); 202 | state->func_draw_sidebar(state, ZOO_SIDEBAR_UPDATE_ALL_REDRAW); 203 | } 204 | -------------------------------------------------------------------------------- /src/libzoo/zoo_callstack.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_internal.h" 26 | 27 | zoo_call *zoo_call_push(zoo_call_stack *stack, zoo_call_type type, uint8_t state) { 28 | zoo_call *new_call; 29 | 30 | new_call = malloc(sizeof(zoo_call)); 31 | if (new_call == NULL) { 32 | return NULL; 33 | } 34 | new_call->next = stack->call; 35 | new_call->type = type; 36 | new_call->state = state; 37 | stack->call = new_call; 38 | 39 | return new_call; 40 | } 41 | 42 | zoo_call *zoo_call_push_callback(zoo_call_stack *stack, zoo_func_callback func, void *arg) { 43 | zoo_call *c = zoo_call_push(stack, CALLBACK, 0); 44 | 45 | if (c != NULL) { 46 | c->args.cb.func = func; 47 | c->args.cb.arg = arg; 48 | } 49 | 50 | return c; 51 | } 52 | 53 | void zoo_call_pop(zoo_call_stack *stack) { 54 | zoo_call *old_call; 55 | 56 | old_call = stack->call; 57 | stack->call = old_call->next; 58 | free(old_call); 59 | } 60 | -------------------------------------------------------------------------------- /src/libzoo/zoo_input.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_internal.h" 26 | 27 | void zoo_input_update(zoo_input_state *state) { 28 | int i, max_order = -1; 29 | int move_action = ZOO_ACTION_MAX; 30 | 31 | for (i = ZOO_ACTION_UP; i <= ZOO_ACTION_DOWN; i++) { 32 | if (state->actions_down[i] && state->actions_order[i] > max_order) { 33 | max_order = state->actions_order[i]; 34 | move_action = i; 35 | } 36 | } 37 | 38 | switch (move_action) { 39 | case ZOO_ACTION_UP: 40 | state->delta_x = 0; 41 | state->delta_y = -1; 42 | break; 43 | case ZOO_ACTION_DOWN: 44 | state->delta_x = 0; 45 | state->delta_y = 1; 46 | break; 47 | case ZOO_ACTION_LEFT: 48 | state->delta_x = -1; 49 | state->delta_y = 0; 50 | break; 51 | case ZOO_ACTION_RIGHT: 52 | state->delta_x = 1; 53 | state->delta_y = 0; 54 | break; 55 | default: 56 | state->delta_x = 0; 57 | state->delta_y = 0; 58 | break; 59 | } 60 | } 61 | 62 | void zoo_input_clear(zoo_input_state *state) { 63 | int i; 64 | 65 | // update counts 66 | for (i = 0; i < ZOO_ACTION_MAX; i++) { 67 | state->actions_down[i] = false; 68 | } 69 | } 70 | 71 | void zoo_input_tick(zoo_input_state *state) { 72 | int i; 73 | 74 | // update counts 75 | for (i = 0; i < ZOO_ACTION_MAX; i++) { 76 | if (state->actions_held[i]) { 77 | state->actions_count[i]++; 78 | if (state->actions_count[i] >= state->repeat_end) { 79 | state->actions_count[i] = state->repeat_start; 80 | state->actions_down[i] = true; 81 | } 82 | } 83 | } 84 | } 85 | 86 | bool zoo_input_action_pressed(zoo_input_state *state, zoo_input_action action) { 87 | if (state->actions_down[action]) { 88 | state->actions_down[action] = false; 89 | return true; 90 | } else { 91 | return false; 92 | } 93 | } 94 | 95 | bool zoo_input_action_held(zoo_input_state *state, zoo_input_action action) { 96 | return state->actions_held[action]; 97 | } 98 | 99 | void zoo_input_action_down(zoo_input_state *state, zoo_input_action action) { 100 | if (!state->actions_held[action]) { 101 | state->actions_down[action] = true; 102 | state->actions_held[action] = true; 103 | state->actions_count[action] = 0; 104 | state->actions_order[action] = state->pressed_count++; 105 | } 106 | } 107 | 108 | void zoo_input_action_up(zoo_input_state *state, zoo_input_action action) { 109 | uint8_t old_order; 110 | int i; 111 | 112 | if (state->actions_held[action]) { 113 | state->actions_held[action] = false; 114 | old_order = state->actions_order[action]; 115 | state->actions_order[action] = 0; 116 | 117 | for (i = 0; i < ZOO_ACTION_MAX; i++) { 118 | if (state->actions_order[i] > old_order) { 119 | state->actions_order[i]--; 120 | } 121 | } 122 | 123 | state->pressed_count--; 124 | } 125 | } 126 | 127 | void zoo_input_action_set(zoo_input_state *state, zoo_input_action action, bool value) { 128 | if (value) { 129 | zoo_input_action_down(state, action); 130 | } else { 131 | zoo_input_action_up(state, action); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/libzoo/zoo_internal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | // zoo_internal.h - private libzoo includes 27 | 28 | #ifndef __ZOO_INTERNAL_H__ 29 | #define __ZOO_INTERNAL_H__ 30 | 31 | #include "zoo.h" 32 | 33 | // platform/compiler-specific hacks 34 | 35 | #if __STDC_VERSION__ >= 199901L 36 | #define ZOO_INLINE inline 37 | #else 38 | #define ZOO_INLINE 39 | #endif 40 | 41 | #if defined(__GNUC__) && defined(ZOO_OPTIMIZE) 42 | #define ZOO_FILE_OPTIMIZE_SIZE _Pragma("GCC optimize (\"Os\")") 43 | #else 44 | #define ZOO_FILE_OPTIMIZE_SIZE 45 | #endif 46 | 47 | #ifdef ZOO_PLATFORM_GBA 48 | #define GBA_FAST_CODE __attribute__((section(".iwram"), long_call, target("arm"))) 49 | #else 50 | #define GBA_FAST_CODE 51 | #endif 52 | 53 | #ifdef ZOO_USE_ROM_POINTERS 54 | // Global function. 55 | bool platform_is_rom_ptr(void *ptr); 56 | #else 57 | // No ROM pointers. 58 | #define platform_is_rom_ptr(ptr) 0 59 | #endif 60 | 61 | // zoo_element.c 62 | 63 | extern const zoo_element_def zoo_element_defs[ZOO_MAX_ELEMENT + 1]; 64 | 65 | // zoo_game.c 66 | 67 | extern const char zoo_line_chars[16]; 68 | extern const char zoo_color_names[8][8]; 69 | extern const zoo_stat zoo_stat_template_default; 70 | 71 | extern const int16_t zoo_diagonal_delta_x[8]; 72 | extern const int16_t zoo_diagonal_delta_y[8]; 73 | extern const int16_t zoo_neighbor_delta_x[4]; 74 | extern const int16_t zoo_neighbor_delta_y[4]; 75 | 76 | // zoo_oop_label_cache.c 77 | 78 | void zoo_oop_label_cache_build(zoo_state *state, int16_t stat_id); 79 | int16_t zoo_oop_label_cache_search(zoo_state *state, int16_t stat_id, const char *object_message, bool zapped); 80 | void zoo_oop_label_cache_zap(zoo_state *state, int16_t stat_id, int16_t label_data_pos, bool zapped, bool recurse, const char *label); 81 | 82 | // zoo_window.c 83 | 84 | void zoo_window_sort(zoo_state *state, zoo_text_window *window); 85 | 86 | // zoo_window_classic.c 87 | 88 | typedef enum { 89 | ZOO_WINDOW_PATTERN_TOP, 90 | ZOO_WINDOW_PATTERN_BOTTOM, 91 | ZOO_WINDOW_PATTERN_INNER, 92 | ZOO_WINDOW_PATTERN_SEPARATOR 93 | } zoo_window_pattern_type; 94 | 95 | void zoo_window_draw_pattern(zoo_state *state, int16_t x, int16_t y, int16_t width, uint8_t color, zoo_window_pattern_type ptype); 96 | 97 | #endif /* __ZOO_H__ */ 98 | -------------------------------------------------------------------------------- /src/libzoo/zoo_io.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_internal.h" 26 | 27 | static uint8_t zoo_io_mem_getc(zoo_io_handle *h) { 28 | uint8_t **ptr = (uint8_t **) &h->p; 29 | if (h->len <= 0) return 0; 30 | h->len--; 31 | return *((*ptr)++); 32 | } 33 | 34 | static size_t zoo_io_mem_putc(zoo_io_handle *h, uint8_t v) { 35 | uint8_t **ptr = (uint8_t **) &h->p; 36 | if (h->len <= 0) return 0; 37 | h->len--; 38 | *((*ptr)++) = v; 39 | return 1; 40 | } 41 | 42 | static size_t zoo_io_mem_read(zoo_io_handle *h, uint8_t *d_ptr, size_t len) { 43 | uint8_t **ptr = (uint8_t **) &h->p; 44 | if (len > h->len) len = h->len; 45 | if (len <= 0) return 0; 46 | h->len -= len; 47 | memcpy(d_ptr, *ptr, len); 48 | *ptr += len; 49 | return len; 50 | } 51 | 52 | static size_t zoo_io_mem_write(zoo_io_handle *h, const uint8_t *d_ptr, size_t len) { 53 | uint8_t **ptr = (uint8_t **) &h->p; 54 | if (len > h->len) len = h->len; 55 | if (len <= 0) return 0; 56 | h->len -= len; 57 | memcpy(*ptr, d_ptr, len); 58 | *ptr += len; 59 | return len; 60 | } 61 | 62 | static size_t zoo_io_mem_putc_ro(zoo_io_handle *h, uint8_t v) { 63 | return 0; 64 | } 65 | 66 | static size_t zoo_io_mem_write_ro(zoo_io_handle *h, const uint8_t *d_ptr, size_t len) { 67 | return 0; 68 | } 69 | 70 | static size_t zoo_io_mem_skip(zoo_io_handle *h, size_t len) { 71 | uint8_t **ptr = (uint8_t **) &h->p; 72 | if (len > h->len) len = h->len; 73 | h->len -= len; 74 | *ptr += len; 75 | return len; 76 | } 77 | 78 | static size_t zoo_io_mem_tell(zoo_io_handle *h) { 79 | return h->len_orig - h->len; 80 | } 81 | 82 | static void zoo_io_mem_close(zoo_io_handle *h) { 83 | 84 | } 85 | 86 | static uint8_t *zoo_io_mem_getptr(zoo_io_handle *h) { 87 | return h->p; 88 | } 89 | 90 | zoo_io_handle zoo_io_open_file_mem(uint8_t *ptr, size_t len, zoo_io_mode mode) { 91 | zoo_io_handle h; 92 | h.p = ptr; 93 | h.len = len; 94 | h.len_orig = len; 95 | h.func_getptr = zoo_io_mem_getptr; 96 | h.func_getc = zoo_io_mem_getc; 97 | h.func_putc = (mode == MODE_WRITE) ? zoo_io_mem_putc : zoo_io_mem_putc_ro; 98 | h.func_read = zoo_io_mem_read; 99 | h.func_write = (mode == MODE_WRITE) ? zoo_io_mem_write : zoo_io_mem_write_ro; 100 | h.func_skip = zoo_io_mem_skip; 101 | h.func_tell = zoo_io_mem_tell; 102 | h.func_close = zoo_io_mem_close; 103 | return h; 104 | } -------------------------------------------------------------------------------- /src/libzoo/zoo_oop_label_cache.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "zoo_internal.h" 29 | 30 | /** 31 | * Label cache implementation. 32 | * Method: Keep a simple, pre-calculated, pre-allocated list of 33 | * all areas in object code which look like labels. 34 | * 35 | * Goals: 36 | * - improve performance of common commands, like #SEND, #ZAP and #RESTORE 37 | * - allow blocking writes to object code (on ROM-based platforms) 38 | */ 39 | 40 | void zoo_oop_label_cache_build(zoo_state *state, int16_t stat_id) { 41 | zoo_stat *stat = &state->board.stats[stat_id]; 42 | int16_t label_count = 0; 43 | int16_t pos, label_pos, last_label_pos; 44 | 45 | if (stat->data != NULL && stat->data_len > 0) { 46 | if (stat->label_cache_size > 0) { 47 | return; 48 | } 49 | 50 | // check existing stats 51 | for (pos = 1; pos <= state->board.stat_count; pos++) { 52 | if (state->board.stats[pos].data == stat->data && state->board.stats[pos].label_cache_size > 0) { 53 | stat->label_cache = state->board.stats[pos].label_cache; 54 | stat->label_cache_size = state->board.stats[pos].label_cache_size; 55 | return; 56 | } 57 | } 58 | 59 | // count labels 60 | for (pos = 0; pos < (stat->data_len-1); pos++) { 61 | if (stat->data[pos] == '\r' && (stat->data[pos+1] == ':' || stat->data[pos+1] == '\'')) { 62 | label_count++; 63 | last_label_pos = pos; 64 | pos++; 65 | } 66 | } 67 | 68 | // create cache 69 | stat->label_cache_size = label_count + 1; 70 | if (label_count > 0) { 71 | stat->label_cache = malloc(sizeof(zoo_stat_label) * label_count); 72 | 73 | pos = 0; 74 | label_pos = 0; 75 | for (pos = 0; pos <= last_label_pos; pos++) { 76 | if (stat->data[pos] == '\r' && (stat->data[pos+1] == ':' || stat->data[pos+1] == '\'')) { 77 | stat->label_cache[label_pos].pos = pos; 78 | stat->label_cache[label_pos].zapped = stat->data[pos+1] == '\''; 79 | pos++; 80 | label_pos++; 81 | } 82 | } 83 | 84 | // assert(label_pos == label_count); 85 | } 86 | } else { 87 | stat->label_cache_size = 0; 88 | } 89 | } 90 | 91 | static void zoo_oop_label_cache_free(zoo_state *state, int16_t stat_id) { 92 | zoo_stat *stat = &state->board.stats[stat_id]; 93 | void *ptr; 94 | int pos; 95 | 96 | if (stat->label_cache_size > 0) { 97 | ptr = stat->label_cache; 98 | free(ptr); 99 | 100 | for (pos = 1; pos <= state->board.stat_count; pos++) { 101 | stat = &state->board.stats[pos]; 102 | if (stat->label_cache_size > 0 && stat->label_cache == ptr) { 103 | free(stat->label_cache); 104 | stat->label_cache_size = 0; 105 | } 106 | } 107 | } 108 | } 109 | 110 | // FIXME: exposes internal 111 | int16_t zoo_oop_find_string_from(zoo_state *state, int16_t stat_id, const char *str, int16_t start_pos, int16_t end_pos); 112 | 113 | GBA_FAST_CODE 114 | int16_t zoo_oop_label_cache_search(zoo_state *state, int16_t stat_id, const char *object_message, bool zapped) { 115 | int i; 116 | int label_cache_size; 117 | zoo_stat_label *label_cache; 118 | int16_t pos; 119 | 120 | zoo_oop_label_cache_build(state, stat_id); 121 | label_cache = state->board.stats[stat_id].label_cache; 122 | label_cache_size = state->board.stats[stat_id].label_cache_size - 1; 123 | 124 | for (i = 0; i < label_cache_size; i++) { 125 | // printf("id %d, entry %d: pos %d, %s\n", stat_id, i, label_cache[i].pos, label_cache[i].zapped ? "zapped" : "not zapped"); 126 | if (zapped == label_cache[i].zapped) { 127 | pos = zoo_oop_find_string_from(state, stat_id, object_message, 128 | label_cache[i].pos + 2, 129 | label_cache[i].pos + 2 130 | ); 131 | if (pos >= 2) { 132 | return pos - 2; 133 | } 134 | } 135 | } 136 | 137 | return -1; 138 | } 139 | 140 | GBA_FAST_CODE 141 | void zoo_oop_label_cache_zap(zoo_state *state, int16_t stat_id, int16_t label_data_pos, bool zapped, bool recurse, const char *label) { 142 | zoo_stat *stat = &state->board.stats[stat_id]; 143 | int ix; 144 | int16_t pos; 145 | 146 | #ifdef ZOO_NO_OBJECT_CODE_WRITES 147 | // Emulate #ZAP/RESTORE restart. (writes) 148 | if (label_data_pos == 0) { 149 | stat->label_cache_chr2 = zapped ? '\'' : ':'; 150 | zoo_oop_label_cache_free(state, stat_id); 151 | } 152 | #endif 153 | 154 | zoo_oop_label_cache_build(state, stat_id); 155 | for (ix = 0; ix < stat->label_cache_size-1; ix++) { 156 | if (stat->label_cache[ix].pos == label_data_pos) { 157 | stat->label_cache[ix].zapped = zapped; 158 | #ifndef ZOO_NO_OBJECT_CODE_WRITES 159 | stat->data[label_data_pos + 1] = zapped ? '\'' : ':'; 160 | #endif 161 | break; 162 | } 163 | } 164 | 165 | if (recurse) { 166 | // Find remaining positions 167 | for (ix++; ix < stat->label_cache_size-1; ix++) { 168 | if (stat->label_cache[ix].zapped != zapped) { 169 | pos = stat->label_cache[ix].pos; 170 | if (zoo_oop_find_string_from(state, stat_id, label, pos + 2, pos + 2) >= 0) { 171 | stat->label_cache[ix].zapped = zapped; 172 | #ifndef ZOO_NO_OBJECT_CODE_WRITES 173 | stat->data[stat->label_cache[ix].pos + 1] = zapped ? '\'' : ':'; 174 | #endif 175 | } 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/libzoo/zoo_oop_token.tok: -------------------------------------------------------------------------------- 1 | INS 2 | GO 3 | TRY 4 | WALK 5 | SET 6 | CLEAR 7 | IF 8 | SHOOT 9 | THROWSTAR 10 | GIVE 11 | TAKE 12 | END 13 | ENDGAME 14 | IDLE 15 | RESTART 16 | ZAP 17 | RESTORE 18 | LOCK 19 | UNLOCK 20 | SEND 21 | BECOME 22 | PUT 23 | CHANGE 24 | PLAY 25 | CYCLE 26 | CHAR 27 | DIE 28 | BIND 29 | GIVE 30 | HEALTH 31 | AMMO 32 | GEMS 33 | TORCHES 34 | SCORE 35 | TIME 36 | COND 37 | NOT 38 | ALLIGNED 39 | CONTACT 40 | BLOCKED 41 | ENERGIZED 42 | ANY 43 | DIR 44 | NORTH N 45 | SOUTH S 46 | EAST E 47 | WEST W 48 | IDLE I 49 | SEEK 50 | FLOW 51 | RND 52 | RNDNS 53 | RNDNE 54 | CW 55 | CCW 56 | RNDP 57 | OPP 58 | COLOR 59 | BLUE 60 | GREEN 61 | CYAN 62 | RED 63 | PURPLE 64 | YELLOW 65 | WHITE 66 | -------------------------------------------------------------------------------- /src/libzoo/zoo_sound.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include "zoo_internal.h" 30 | 31 | #define NOTE_MIN 16 32 | #define NOTE_MAX 112 33 | #define DRUM_MIN 240 34 | #define DRUM_MAX 250 35 | 36 | static const uint16_t zoo_sound_freqs[NOTE_MAX - NOTE_MIN] = { 37 | 64, 67, 71, 76, 80, 85, 90, 95, 101, 107, 114, 120, 0, 0, 0, 0, 38 | 128, 135, 143, 152, 161, 170, 181, 191, 203, 215, 228, 241, 0, 0, 0, 0, 39 | 256, 271, 287, 304, 322, 341, 362, 383, 406, 430, 456, 483, 0, 0, 0, 0, 40 | 512, 542, 574, 608, 645, 683, 724, 767, 812, 861, 912, 966, 0, 0, 0, 0, 41 | 1024, 1084, 1149, 1217, 1290, 1366, 1448, 1534, 1625, 1722, 1824, 1933, 0, 0, 0, 0, 42 | 2048, 2169, 2298, 2435, 2580, 2733, 2896, 3068, 3250, 3444, 3649, 3866, 0, 0, 0, 0 43 | }; 44 | 45 | typedef struct { 46 | uint16_t len; 47 | uint16_t data[15]; 48 | } zoo_sound_drum; 49 | 50 | static const zoo_sound_drum zoo_sound_drums[DRUM_MAX - DRUM_MIN] = { 51 | { 1, {3200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, 52 | {14, {1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 0}}, 53 | {14, {4800, 4800, 8000, 1600, 4800, 4800, 8000, 1600, 4800, 4800, 8000, 1600, 4800, 4800, 8000}}, 54 | {14, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, 55 | {14, {500, 2556, 1929, 3776, 3386, 4517, 1385, 1103, 4895, 3396, 874, 1616, 5124, 606, 0}}, 56 | {14, {1600, 1514, 1600, 821, 1600, 1715, 1600, 911, 1600, 1968, 1600, 1490, 1600, 1722, 1600}}, 57 | {14, {2200, 1760, 1760, 1320, 2640, 880, 2200, 1760, 1760, 1320, 2640, 880, 2200, 1760, 0}}, 58 | {14, {688, 676, 664, 652, 640, 628, 616, 604, 592, 580, 568, 556, 544, 532, 0}}, 59 | {14, {1207, 1224, 1163, 1127, 1159, 1236, 1269, 1314, 1127, 1224, 1320, 1332, 1257, 1327, 0}}, 60 | {14, {378, 331, 316, 230, 224, 384, 480, 320, 358, 412, 376, 621, 554, 426, 0}} 61 | }; 62 | 63 | static ZOO_INLINE void zoo_nosound(zoo_sound_state *state) { 64 | if (state->d_sound != NULL && state->d_sound->func_play_freqs != NULL) { 65 | state->d_sound->func_play_freqs(state->d_sound, NULL, 0, false); 66 | } 67 | } 68 | 69 | static void zoo_default_play_note(zoo_sound_state *state, uint8_t note, uint8_t duration) { 70 | if (state->d_sound != NULL && state->d_sound->func_play_freqs != NULL) { 71 | state->d_sound->func_play_freqs(state->d_sound, NULL, 0, false); 72 | if (note >= NOTE_MIN && note < NOTE_MAX) { 73 | state->d_sound->func_play_freqs(state->d_sound, &zoo_sound_freqs[note - NOTE_MIN], 1, false); 74 | } else if (note >= DRUM_MIN && note < DRUM_MAX) { 75 | state->d_sound->func_play_freqs(state->d_sound, zoo_sound_drums[note - DRUM_MIN].data, zoo_sound_drums[note - DRUM_MIN].len, true); 76 | } else { 77 | // nosound - already called 78 | } 79 | } 80 | } 81 | 82 | void zoo_sound_queue(zoo_sound_state *state, int16_t priority, const uint8_t *data, int16_t len) { 83 | if (!state->block_queueing && (!state->is_playing || ( 84 | ((priority >= state->current_priority) && (state->current_priority != -1)) 85 | || (priority == -1) 86 | ))) { 87 | if (priority >= 0 || !state->is_playing) { 88 | state->current_priority = priority; 89 | memcpy(state->buffer, data, len); 90 | state->buffer_pos = 0; 91 | state->buffer_len = len; 92 | state->duration_counter = 1; 93 | } else { 94 | // trim buffer 95 | if (state->buffer_pos > 0) { 96 | state->buffer_len -= state->buffer_pos; 97 | memmove(state->buffer, state->buffer + state->buffer_pos, state->buffer_len); 98 | state->buffer_pos = 0; 99 | } 100 | // append buffer 101 | if ((state->buffer_len + len) < 255) { 102 | memcpy(state->buffer + state->buffer_len, data, len); 103 | state->buffer_len += len; 104 | } 105 | } 106 | state->is_playing = true; 107 | } 108 | } 109 | 110 | void zoo_sound_clear_queue(zoo_sound_state *state) { 111 | state->buffer_len = 0; 112 | state->is_playing = false; 113 | zoo_nosound(state); 114 | } 115 | 116 | void zoo_sound_tick(zoo_sound_state *state) { 117 | uint8_t note, duration; 118 | 119 | if (!state->enabled) { 120 | state->is_playing = false; 121 | zoo_nosound(state); 122 | } else if (state->is_playing) { 123 | if (--(state->duration_counter) <= 0) { 124 | if (state->buffer_pos >= state->buffer_len) { 125 | zoo_nosound(state); 126 | state->is_playing = false; 127 | } else { 128 | note = state->buffer[(state->buffer_pos)++]; 129 | duration = state->buffer[(state->buffer_pos)++]; 130 | // TODO 131 | /* if (state->func_play_note != NULL) { 132 | state->func_play_note(state, note, duration); 133 | } */ 134 | zoo_default_play_note(state, note, duration); 135 | state->duration_counter = state->duration_multiplier * duration; 136 | } 137 | } 138 | } 139 | } 140 | 141 | static const uint8_t letter_to_tone[7] = {9, 11, 0, 2, 4, 5, 7}; 142 | 143 | int16_t zoo_sound_parse(const char *input, uint8_t *output, int16_t out_max) { 144 | uint8_t note_octave = 3; 145 | uint8_t note_duration = 1; 146 | uint8_t note_tone; 147 | int16_t inpos; 148 | int16_t outpos = 0; 149 | 150 | for (inpos = 0; inpos < strlen(input); inpos++) { 151 | // check for overflow 152 | if ((outpos + 2) >= out_max) break; 153 | 154 | note_tone = -1; 155 | switch (zoo_toupper(input[inpos])) { 156 | case 'T': note_duration = 1; break; 157 | case 'S': note_duration = 2; break; 158 | case 'I': note_duration = 4; break; 159 | case 'Q': note_duration = 8; break; 160 | case 'H': note_duration = 16; break; 161 | case 'W': note_duration = 32; break; 162 | case '.': note_duration = note_duration * 3 / 2; break; 163 | case '3': note_duration = note_duration / 3; break; 164 | case '+': if (note_octave < 6) note_octave += 1; break; 165 | case '-': if (note_octave > 1) note_octave -= 1; break; 166 | 167 | case 'A': 168 | case 'B': 169 | case 'C': 170 | case 'D': 171 | case 'E': 172 | case 'F': 173 | case 'G': { 174 | note_tone = letter_to_tone[(input[inpos] - 'A') & 0x07]; 175 | 176 | switch (input[inpos + 1]) { 177 | case '!': note_tone -= 1; inpos++; break; 178 | case '#': note_tone += 1; inpos++; break; 179 | } 180 | 181 | output[outpos++] = (note_octave << 4) | note_tone; 182 | output[outpos++] = note_duration; 183 | } break; 184 | 185 | case 'X': { 186 | output[outpos++] = 0; 187 | output[outpos++] = note_duration; 188 | } break; 189 | 190 | case '0': 191 | case '1': 192 | case '2': 193 | case '4': 194 | case '5': 195 | case '6': 196 | case '7': 197 | case '8': 198 | case '9': { 199 | output[outpos++] = input[inpos] + 0xF0 - '0'; 200 | output[outpos++] = note_duration; 201 | } break; 202 | } 203 | } 204 | 205 | return outpos; 206 | } 207 | 208 | void zoo_sound_state_init(zoo_sound_state *state) { 209 | memset(state, 0, sizeof(*state)); 210 | state->enabled = true; 211 | state->block_queueing = false; 212 | state->duration_multiplier = 1; 213 | zoo_sound_clear_queue(state); 214 | } 215 | -------------------------------------------------------------------------------- /src/libzoo/zoo_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_internal.h" 26 | 27 | #define ZOO_WINDOW_LINE_MAX 50 28 | 29 | char *zoo_window_line_at(zoo_text_window *window, int pos) { 30 | if (pos >= 0 && pos < window->line_count) { 31 | return window->lines[pos]; 32 | } else { 33 | return NULL; 34 | } 35 | } 36 | 37 | char *zoo_window_line_selected(zoo_text_window *window) { 38 | return zoo_window_line_at(window, window->line_pos); 39 | } 40 | 41 | void zoo_window_append(zoo_text_window *window, const char *text) { 42 | char *buffer; 43 | int16_t buflen = strlen(text); 44 | if (buflen > ZOO_WINDOW_LINE_MAX) buflen = ZOO_WINDOW_LINE_MAX; 45 | 46 | buffer = malloc(sizeof(char) * (buflen + 1)); 47 | memcpy(buffer, text, buflen); 48 | buffer[buflen] = '\0'; 49 | 50 | if (window->line_count > 0) { 51 | window->lines = realloc(window->lines, sizeof(char*) * (window->line_count + 1)); 52 | } else { 53 | window->lines = malloc(sizeof(char*)); 54 | } 55 | window->lines[(window->line_count)++] = buffer; 56 | } 57 | 58 | void zoo_window_close(zoo_text_window *window) { 59 | int16_t i; 60 | 61 | for (i = 0; i < window->line_count; i++) { 62 | free(window->lines[i]); 63 | } 64 | free(window->lines); 65 | 66 | window->line_pos = 0; 67 | window->line_count = 0; 68 | } 69 | 70 | void zoo_window_append_file(zoo_text_window *window, zoo_io_handle *h) { 71 | char str[ZOO_WINDOW_LINE_MAX + 1]; 72 | char c; 73 | int16_t i = 0; 74 | 75 | while (h->func_read(h, (uint8_t*) &c, 1) > 0) { 76 | if (c == '\x0D') { 77 | str[i++] = '\0'; 78 | zoo_window_append(window, str); 79 | i = 0; 80 | } else if (c != '\x0A' && i < ZOO_WINDOW_LINE_MAX) { 81 | str[i++] = c; 82 | } 83 | } 84 | 85 | str[i++] = '\0'; 86 | zoo_window_append(window, str); 87 | } 88 | 89 | bool zoo_window_open_file(zoo_io_driver *io, zoo_text_window *window, const char *filename) { 90 | char path[ZOO_WINDOW_LINE_MAX]; 91 | zoo_io_handle h; 92 | 93 | strncpy(path, filename, sizeof(path)); 94 | if (strchr(filename, '.') == NULL) { 95 | strncat(path, ".HLP", sizeof(path) - 1); 96 | } 97 | 98 | h = io->func_open_file(io, path, MODE_READ); 99 | zoo_window_append_file(window, &h); 100 | h.func_close(&h); 101 | 102 | return true; 103 | } 104 | -------------------------------------------------------------------------------- /src/libzoo/zoo_window_classic.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include "zoo_internal.h" 30 | 31 | #define WINDOW_STATE_START 0 32 | #define WINDOW_STATE_ANIM_OPEN 1 33 | #define WINDOW_STATE_TICK 2 34 | #define WINDOW_STATE_ANIM_CLOSE 3 35 | 36 | static int16_t window_x = 5; 37 | static int16_t window_y = 3; 38 | static int16_t window_width = 49; 39 | static int16_t window_height = 19; 40 | 41 | #define WINDOW_ANIM_MAX (window_height >> 1) 42 | 43 | void zoo_window_set_position(int16_t x, int16_t y, int16_t width, int16_t height) { 44 | window_x = x; 45 | window_y = y; 46 | window_width = width; 47 | window_height = height; 48 | } 49 | 50 | static void zoo_window_draw_title(zoo_text_window *window, zoo_state *state, uint8_t color, const char *title) { 51 | int16_t i; 52 | int16_t il = strlen(title); 53 | int16_t is = window_x + ((window_width + 1 - il) >> 1); 54 | 55 | for (i = window_x + 2; i < (window_x + window_width - 2); i++) { 56 | if (i >= is && i < (is+il)) { 57 | state->d_video->func_write(state->d_video, i, window_y + 1, color, title[i - is]); 58 | } else { 59 | state->d_video->func_write(state->d_video, i, window_y + 1, color, ' '); 60 | } 61 | } 62 | } 63 | 64 | static const char draw_patterns[4][5] = { 65 | {'\xC6', '\xD1', '\xCD', '\xD1', '\xB5'}, // top 66 | {'\xC6', '\xCF', '\xCD', '\xCF', '\xB5'}, // bottom 67 | {' ', '\xB3', ' ', '\xB3', ' '}, // inner 68 | {' ', '\xC6', '\xCD', '\xB5', ' '} // separator 69 | }; 70 | 71 | void zoo_window_draw_pattern(zoo_state *state, int16_t x, int16_t y, int16_t width, uint8_t color, zoo_window_pattern_type ptype) { 72 | int16_t ix; 73 | zoo_video_driver *d_video = state->d_video; 74 | const char *pattern = draw_patterns[ptype]; 75 | 76 | d_video->func_write(d_video, x, y, color, pattern[0]); 77 | d_video->func_write(d_video, x + 1, y, color, pattern[1]); 78 | for (ix = 2; ix < width - 2; ix++) { 79 | d_video->func_write(d_video, x + ix, y, color, pattern[2]); 80 | } 81 | d_video->func_write(d_video, x + width - 2, y, color, pattern[3]); 82 | d_video->func_write(d_video, x + width - 1, y, color, pattern[4]); 83 | } 84 | 85 | static ZOO_INLINE void zoo_window_draw_border(zoo_text_window *window, zoo_state *state, int16_t y, zoo_window_pattern_type ptype) { 86 | zoo_window_draw_pattern(state, window_x, y, window_width, 0x0F, ptype); 87 | } 88 | 89 | static void zoo_window_draw_open(zoo_text_window *window, zoo_state *state) { 90 | int16_t ix; 91 | int16_t y0 = window_y + window->counter; 92 | int16_t y1 = window_y + window->counter + 1; 93 | int16_t y2 = window_y + window_height - window->counter - 2; 94 | int16_t y3 = window_y + window_height - window->counter - 1; 95 | 96 | zoo_window_draw_border(window, state, y0, ZOO_WINDOW_PATTERN_TOP); 97 | zoo_window_draw_border(window, state, y1, ZOO_WINDOW_PATTERN_INNER); 98 | zoo_window_draw_border(window, state, y2, ZOO_WINDOW_PATTERN_INNER); 99 | zoo_window_draw_border(window, state, y3, ZOO_WINDOW_PATTERN_BOTTOM); 100 | } 101 | 102 | static void zoo_window_draw_open_finish(zoo_text_window *window, zoo_state *state) { 103 | zoo_window_draw_border(window, state, window_y + 2, ZOO_WINDOW_PATTERN_SEPARATOR); 104 | zoo_window_draw_title(window, state, 0x1E, window->title); 105 | } 106 | 107 | static void zoo_window_draw_open_all(zoo_text_window *window, zoo_state *state) { 108 | int16_t iy; 109 | 110 | zoo_window_draw_border(window, state, window_y, ZOO_WINDOW_PATTERN_TOP); 111 | for (iy = 1; iy < window_height - 1; iy++) { 112 | zoo_window_draw_border(window, state, window_y + iy, iy == 2 ? ZOO_WINDOW_PATTERN_SEPARATOR : ZOO_WINDOW_PATTERN_INNER); 113 | } 114 | zoo_window_draw_border(window, state, window_y + window_height - 1, ZOO_WINDOW_PATTERN_BOTTOM); 115 | } 116 | 117 | static void zoo_window_draw_line(zoo_text_window *window, zoo_state *state, int16_t line_pos, bool no_formatting) { 118 | int line_y; 119 | int text_x, text_width; 120 | int text_offset, text_color; 121 | int i; 122 | bool same_line; 123 | bool is_boundary, draw_arrow = false; 124 | char *str = NULL; 125 | char *tmp = NULL; 126 | 127 | line_y = (window_y + line_pos - window->line_pos) + (window_height >> 1) + 1; 128 | same_line = line_pos == window->line_pos; 129 | state->d_video->func_write(state->d_video, window_x + 2, line_y, 0x1C, same_line ? '\xAF' : ' '); 130 | state->d_video->func_write(state->d_video, window_x + window_width - 3, line_y, 0x1C, same_line ? '\xAE' : ' '); 131 | 132 | text_offset = 0; 133 | text_color = 0x1E; 134 | text_x = 0; 135 | text_width = (window_width - 7); 136 | 137 | str = zoo_window_line_at(window, line_pos); 138 | if (str != NULL) { 139 | switch (str[0]) { 140 | case '!': 141 | tmp = strchr(str, ';'); 142 | if (tmp != NULL) str = tmp + 1; 143 | draw_arrow = true; 144 | text_x += 5; 145 | text_color = 0x1F; 146 | break; 147 | case ':': 148 | tmp = strchr(str, ';'); 149 | if (tmp != NULL) str = tmp + 1; 150 | text_color = 0x1F; 151 | break; 152 | case '$': 153 | str++; 154 | text_color = 0x1F; 155 | // (window_width - 8 - strlen(str)) / 2 156 | text_x = (text_width - 1 - strlen(str)) >> 1; 157 | break; 158 | } 159 | } else if (window->viewing_file) { 160 | // TODO: zoo_draw_string refactor? 161 | } 162 | 163 | if (str != NULL) { 164 | for (i = -text_x - 1; i < (text_width - text_x); i++) { 165 | if (draw_arrow && i == -3) { 166 | state->d_video->func_write(state->d_video, window_x + 4 + i + text_x, line_y, 0x1D, '\x10'); 167 | } else { 168 | state->d_video->func_write(state->d_video, window_x + 4 + i + text_x, line_y, text_color, 169 | (i >= 0 && i < strlen(str)) ? str[i] : ' '); 170 | } 171 | } 172 | } else { 173 | is_boundary = line_pos == -1 || line_pos == window->line_count; 174 | for (i = 0; i < (window_width - 4); i++) { 175 | state->d_video->func_write(state->d_video, window_x + 2 + i, line_y, text_color, 176 | (is_boundary && ((i % 5) == 4)) ? '\x07' : ' '); 177 | } 178 | } 179 | 180 | } 181 | 182 | static void zoo_window_draw_text(zoo_text_window *window, zoo_state *state, bool no_formatting) { 183 | int i; 184 | for (i = 0; i < window_height - 4; i++) { 185 | zoo_window_draw_line(window, state, window->line_pos - (window_height >> 1) + i + 2, no_formatting); 186 | } 187 | zoo_window_draw_title(window, state, 0x1E, window->title); 188 | } 189 | 190 | static void zoo_window_draw_close(zoo_text_window *window, zoo_state *state) { 191 | int16_t ix, iy; 192 | int16_t y0, y1; 193 | 194 | // draw line at counter + 1 195 | iy = window->counter + 1; 196 | if (iy <= WINDOW_ANIM_MAX) { 197 | y0 = window_y + iy; 198 | y1 = window_y + window_height - iy - 1; 199 | 200 | zoo_window_draw_border(window, state, y0, ZOO_WINDOW_PATTERN_TOP); 201 | zoo_window_draw_border(window, state, y1, ZOO_WINDOW_PATTERN_BOTTOM); 202 | } 203 | 204 | // restore line at counter 205 | iy = window->counter; 206 | zoo_restore_display(state, window->screen_copy, window_width, window_height, 207 | 0, iy, window_width, 1, window_x, window_y + iy); 208 | zoo_restore_display(state, window->screen_copy, window_width, window_height, 209 | 0, window_height - iy - 1, window_width, 1, window_x, window_y + window_height - iy - 1); 210 | } 211 | 212 | // if TRUE, close the window 213 | static bool zoo_window_hyperlink(zoo_text_window *window, zoo_state *state, char *str) { 214 | char pointer_str[21]; 215 | char pointer_label[21]; 216 | int16_t i; 217 | 218 | for (i = 0; i < sizeof(pointer_str); i++) { 219 | pointer_str[i] = str[i + 1]; 220 | if (pointer_str[i] == '\0' || pointer_str[i] == ';' || i == (sizeof(pointer_str) - 1)) { 221 | pointer_str[i] = '\0'; 222 | break; 223 | } 224 | } 225 | 226 | if (pointer_str[0] == '-') { 227 | zoo_window_close(window); 228 | if (state->d_io != NULL) { 229 | if (zoo_window_open_file(state->d_io, window, pointer_str + 1)) { 230 | window->viewing_file = true; 231 | zoo_window_draw_text(window, state, false); 232 | return false; 233 | } 234 | } 235 | } else { 236 | if (window->hyperlink_as_select) { 237 | strncpy(window->hyperlink, pointer_str, sizeof(window->hyperlink)); 238 | } else { 239 | pointer_label[0] = ':'; 240 | strncpy(pointer_label + 1, pointer_str, sizeof(pointer_label) - 2); 241 | // TODO: in-document label jumps 242 | } 243 | } 244 | return true; 245 | } 246 | 247 | static zoo_tick_retval zoo_window_classic_tick(zoo_state *state, zoo_text_window *window) { 248 | int16_t old_line_pos = window->line_pos; 249 | bool act_ok, act_cancel, should_close; 250 | char *curr_str; 251 | 252 | switch (window->state) { 253 | case WINDOW_STATE_START: 254 | window->screen_copy = zoo_store_display(state, window_x, window_y, window_width, window_height); 255 | window->counter = WINDOW_ANIM_MAX; 256 | window->state = WINDOW_STATE_ANIM_OPEN; 257 | if (window->disable_transitions) { 258 | zoo_window_draw_open_all(window, state); 259 | goto FinishOpenWindow; 260 | } 261 | // fall through 262 | case WINDOW_STATE_ANIM_OPEN: 263 | zoo_window_draw_open(window, state); 264 | if ((--window->counter) < 0) { 265 | zoo_window_draw_open_finish(window, state); 266 | FinishOpenWindow: 267 | zoo_window_draw_text(window, state, false); 268 | zoo_input_clear(&state->input); 269 | window->state = WINDOW_STATE_TICK; 270 | } else { 271 | return RETURN_NEXT_FRAME; 272 | } 273 | break; 274 | case WINDOW_STATE_TICK: 275 | zoo_input_update(&state->input); 276 | 277 | if (zoo_input_action_pressed(&state->input, ZOO_ACTION_LEFT)) window->line_pos -= 4; 278 | else if (zoo_input_action_pressed(&state->input, ZOO_ACTION_UP)) window->line_pos--; 279 | 280 | if (zoo_input_action_pressed(&state->input, ZOO_ACTION_RIGHT)) window->line_pos += 4; 281 | else if (zoo_input_action_pressed(&state->input, ZOO_ACTION_DOWN)) window->line_pos++; 282 | 283 | if (window->line_pos < 0) window->line_pos = 0; 284 | if (window->line_pos >= window->line_count) window->line_pos = window->line_count - 1; 285 | 286 | if (old_line_pos != window->line_pos) { 287 | zoo_window_draw_text(window, state, false); 288 | } 289 | 290 | act_ok = zoo_input_action_pressed(&state->input, ZOO_ACTION_OK); 291 | act_cancel = zoo_input_action_pressed(&state->input, ZOO_ACTION_CANCEL); 292 | if (act_ok || act_cancel) { 293 | window->state = WINDOW_STATE_ANIM_CLOSE; 294 | window->accepted = act_ok; 295 | if (window->disable_transitions) { 296 | zoo_restore_display(state, window->screen_copy, window_width, window_height, 0, 0, window_width, window_height, window_x, window_y); 297 | goto CloseWindow; 298 | } else { 299 | window->counter = 0; 300 | } 301 | } 302 | break; 303 | case WINDOW_STATE_ANIM_CLOSE: 304 | zoo_window_draw_close(window, state); 305 | if ((++window->counter) > WINDOW_ANIM_MAX) { 306 | CloseWindow: 307 | should_close = true; 308 | if (window->accepted) { 309 | curr_str = zoo_window_line_selected(window); 310 | if (curr_str != NULL && curr_str[0] == '!') { 311 | should_close = zoo_window_hyperlink(window, state, curr_str); 312 | } 313 | } 314 | if (should_close) { 315 | zoo_free_display(state, window->screen_copy); 316 | if (!window->manual_close) { 317 | zoo_window_close(window); 318 | } 319 | return EXIT; 320 | } else { 321 | zoo_restore_display(state, window->screen_copy, window_width, window_height, 0, 0, window_width, window_height, window_x, window_y); 322 | zoo_free_display(state, window->screen_copy); 323 | window->state = WINDOW_STATE_START; 324 | zoo_input_clear(&state->input); 325 | return RETURN_IMMEDIATE; 326 | } 327 | } else { 328 | return RETURN_NEXT_FRAME; 329 | } 330 | break; 331 | } 332 | 333 | return RETURN_NEXT_FRAME; 334 | } 335 | 336 | static const char *zw_text_ptr(const char *line) { 337 | char *tmp; 338 | 339 | switch (line[0]) { 340 | case '!': 341 | case ':': 342 | tmp = strchr(line, ';'); 343 | if (tmp != NULL) return tmp + 1; 344 | else return line + 1; 345 | case '$': 346 | return line + 1; 347 | default: 348 | return line; 349 | } 350 | } 351 | 352 | static int zw_sort_compare(const void *line_a, const void *line_b) { 353 | const char *str_a = zw_text_ptr(*((const char**) line_a)); 354 | const char *str_b = zw_text_ptr(*((const char**) line_b)); 355 | return strcasecmp(str_a, str_b); 356 | } 357 | 358 | void zoo_window_sort(zoo_state *state, zoo_text_window *window) { 359 | return qsort(window->lines, window->line_count, sizeof(char*), zw_sort_compare); 360 | } 361 | 362 | void zoo_window_open(zoo_state *state, zoo_text_window *window) { 363 | window->state = 0; 364 | window->counter = 0; 365 | 366 | zoo_call_push_callback(&(state->call_stack), (zoo_func_callback) zoo_window_classic_tick, window); 367 | } 368 | -------------------------------------------------------------------------------- /src/ui/zoo_sidebar_classic.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "../libzoo/zoo_internal.h" // zoo_element_defs 29 | #include "zoo_sidebar.h" 30 | 31 | static void zoo_sidebar_draw_string(zoo_state *state, uint8_t x, uint8_t y, uint8_t col, const char *text) { 32 | uint8_t i; 33 | 34 | for (i = 0; i < strlen(text); i++) { 35 | state->d_video->func_write(state->d_video, x + i, y, col, text[i]); 36 | } 37 | } 38 | 39 | static void zoo_sidebar_draw_value(zoo_state *state, uint8_t x, uint8_t y, uint8_t col, int16_t val, const char *pad) { 40 | char text[21]; 41 | uint8_t i = 0; 42 | int16_t tval = val; 43 | 44 | // build value to string 45 | if (val < 0) { 46 | text[i++] = '-'; 47 | val = -val; 48 | } 49 | // allocate characters 50 | if (val == 0) { 51 | text[i++] = '0'; 52 | text[i++] = '\0'; 53 | } else { 54 | for (tval = val; tval > 0; tval /= 10) { 55 | i++; 56 | } 57 | text[i--] = '\0'; 58 | for (tval = val; tval > 0; tval /= 10) { 59 | text[i--] = '0' + (tval % 10); 60 | } 61 | } 62 | strncat(text, pad, sizeof(text) - 1); 63 | 64 | zoo_sidebar_draw_string(state, x, y, col, text); 65 | } 66 | 67 | static void zoo_sidebar_clear_line(zoo_state *state, uint8_t y) { 68 | uint8_t x; 69 | for (x = 60; x < 80; x++) { 70 | state->d_video->func_write(state->d_video, x, y, 0x11, ' '); 71 | } 72 | } 73 | 74 | void zoo_draw_sidebar_classic(zoo_state *state, uint16_t flags) { 75 | uint8_t y; 76 | 77 | if (flags & ZOO_SIDEBAR_UPDATE_REDRAW) { 78 | for (y = 0; y <= 24; y++) { 79 | zoo_sidebar_clear_line(state, y); 80 | } 81 | zoo_sidebar_draw_string(state, 61, 0, 0x1F, " - - - - - "); 82 | zoo_sidebar_draw_string(state, 62, 1, 0x70, " Zoo "); 83 | zoo_sidebar_draw_string(state, 61, 2, 0x1F, " - - - - - "); 84 | if (state->game_state == GS_PLAY) { 85 | zoo_sidebar_draw_string(state, 64, 7, 0x1E, " Health:"); 86 | zoo_sidebar_draw_string(state, 64, 8, 0x1E, " Ammo:"); 87 | zoo_sidebar_draw_string(state, 64, 9, 0x1E, "Torches:"); 88 | zoo_sidebar_draw_string(state, 64, 10, 0x1E, " Gems:"); 89 | zoo_sidebar_draw_string(state, 64, 11, 0x1E, " Score:"); 90 | zoo_sidebar_draw_string(state, 64, 12, 0x1E, " Keys:"); 91 | state->d_video->func_write(state->d_video, 62, 7, 0x1F, zoo_element_defs[ZOO_E_PLAYER].character); 92 | state->d_video->func_write(state->d_video, 62, 8, 0x1B, zoo_element_defs[ZOO_E_AMMO].character); 93 | state->d_video->func_write(state->d_video, 62, 9, 0x16, zoo_element_defs[ZOO_E_TORCH].character); 94 | state->d_video->func_write(state->d_video, 62, 10, 0x1B, zoo_element_defs[ZOO_E_GEM].character); 95 | state->d_video->func_write(state->d_video, 62, 12, 0x1F, zoo_element_defs[ZOO_E_KEY].character); 96 | zoo_sidebar_draw_string(state, 62, 14, 0x70, " T "); 97 | zoo_sidebar_draw_string(state, 65, 14, 0x1F, " Torch"); 98 | zoo_sidebar_draw_string(state, 62, 15, 0x30, " B "); 99 | zoo_sidebar_draw_string(state, 62, 16, 0x70, " H "); 100 | zoo_sidebar_draw_string(state, 65, 16, 0x1F, " Help"); 101 | zoo_sidebar_draw_string(state, 67, 18, 0x30, " \x18\x19\x1A\x1B "); 102 | zoo_sidebar_draw_string(state, 72, 18, 0x1F, " Move"); 103 | zoo_sidebar_draw_string(state, 61, 19, 0x70, " Shift \x18\x19\x1A\x1B "); 104 | zoo_sidebar_draw_string(state, 72, 19, 0x1F, " Shoot"); 105 | zoo_sidebar_draw_string(state, 62, 21, 0x70, " S "); 106 | zoo_sidebar_draw_string(state, 65, 21, 0x1F, " Save game"); 107 | zoo_sidebar_draw_string(state, 62, 22, 0x30, " P "); 108 | zoo_sidebar_draw_string(state, 65, 22, 0x1F, " Pause"); 109 | zoo_sidebar_draw_string(state, 62, 23, 0x70, " Q "); 110 | zoo_sidebar_draw_string(state, 65, 23, 0x1F, " Quit"); 111 | } else { 112 | // TODO: game speed slider 113 | zoo_sidebar_draw_string(state, 62, 21, 0x70, " S "); 114 | zoo_sidebar_draw_string(state, 62, 7, 0x30, " W "); 115 | zoo_sidebar_draw_string(state, 65, 7, 0x1E, " World:"); 116 | zoo_sidebar_draw_string(state, 69, 8, 0x1F, (strlen(state->world.info.name) != 0) ? state->world.info.name : "Untitled"); 117 | zoo_sidebar_draw_string(state, 62, 11, 0x70, " P "); 118 | zoo_sidebar_draw_string(state, 65, 11, 0x1F, " Play"); 119 | zoo_sidebar_draw_string(state, 62, 12, 0x30, " R "); 120 | zoo_sidebar_draw_string(state, 65, 12, 0x1E, " Restore game"); 121 | zoo_sidebar_draw_string(state, 62, 13, 0x70, " Q "); 122 | zoo_sidebar_draw_string(state, 65, 13, 0x1E, " Quit"); 123 | zoo_sidebar_draw_string(state, 62, 16, 0x30, " A "); 124 | zoo_sidebar_draw_string(state, 65, 16, 0x1F, " About ZZT!"); 125 | zoo_sidebar_draw_string(state, 62, 17, 0x70, " H "); 126 | zoo_sidebar_draw_string(state, 65, 17, 0x1E, " High Scores"); 127 | // TODO: editor enabled 128 | } 129 | } 130 | 131 | if (state->game_state == GS_PLAY) { 132 | if (flags & ZOO_SIDEBAR_UPDATE_PAUSED) { 133 | if (state->game_paused) { 134 | zoo_sidebar_draw_string(state, 64, 5, 0x1F, "Pausing..."); 135 | } else { 136 | zoo_sidebar_clear_line(state, 5); 137 | } 138 | } 139 | 140 | if (state->board.info.time_limit_sec > 0) { 141 | zoo_sidebar_draw_string(state, 64, 6, 0x1E, " Time:"); 142 | zoo_sidebar_draw_value(state, 72, 6, 0x1E, state->board.info.time_limit_sec - state->world.info.board_time_sec, " "); 143 | } else { 144 | zoo_sidebar_clear_line(state, 6); 145 | } 146 | 147 | zoo_sidebar_draw_value(state, 72, 7, 0x1E, state->world.info.health, " "); 148 | zoo_sidebar_draw_value(state, 72, 8, 0x1E, state->world.info.ammo, " "); 149 | zoo_sidebar_draw_value(state, 72, 9, 0x1E, state->world.info.torches, " "); 150 | zoo_sidebar_draw_value(state, 72, 10, 0x1E, state->world.info.gems, " "); 151 | zoo_sidebar_draw_value(state, 72, 11, 0x1E, state->world.info.score, " "); 152 | 153 | if (state->world.info.torch_ticks == 0) { 154 | zoo_sidebar_draw_string(state, 75, 9, 0x16, " "); 155 | } else { 156 | for (y = 2; y <= 5; y++) { 157 | state->d_video->func_write(state->d_video, 73 + y, 9, 0x16, (y <= ((state->world.info.torch_ticks * 5) / ZOO_TORCH_DURATION)) ? '\xB1' : '\xB0'); 158 | } 159 | } 160 | 161 | for (y = 0; y <= 6; y++) { 162 | state->d_video->func_write(state->d_video, 72 + y, 12, 0x19 + y, state->world.info.keys[y] ? zoo_element_defs[ZOO_E_KEY].character : ' '); 163 | } 164 | 165 | zoo_sidebar_draw_string(state, 65, 15, 0x1F, state->sound.enabled ? " Be quiet" : " Be noisy"); 166 | } 167 | } -------------------------------------------------------------------------------- /src/ui/zoo_sidebar_slim.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_sidebar.h" 26 | 27 | static void write_number(zoo_video_driver *d_video, int16_t x, int16_t y, uint8_t col, int val, int len) { 28 | char s[8]; 29 | int16_t pos = sizeof(s); 30 | int16_t i = val < 0 ? -val : val; 31 | 32 | while (i >= 10) { 33 | s[--pos] = '0' + (i % 10); 34 | i /= 10; 35 | } 36 | s[--pos] = '0' + (i % 10); 37 | if (val < 0) { 38 | s[--pos] = '-'; 39 | } 40 | 41 | for (i = pos; i < sizeof(s); i++) { 42 | d_video->func_write(d_video, x + i - pos, y, col, s[i]); 43 | } 44 | for (i = sizeof(s) - pos; i < len; i++) { 45 | d_video->func_write(d_video, x + i, y, col, ' '); 46 | } 47 | } 48 | 49 | static void write_number_torch_bg(zoo_state *state, int16_t x, int16_t y, uint8_t col, int val, int len) { 50 | char s[8]; 51 | int16_t pos = sizeof(s); 52 | int16_t i = val < 0 ? -val : val; 53 | uint8_t torch_col; 54 | int16_t torch_pos; 55 | zoo_video_driver *d_video = state->d_video; 56 | 57 | while (i >= 10) { 58 | s[--pos] = '0' + (i % 10); 59 | i /= 10; 60 | } 61 | s[--pos] = '0' + (i % 10); 62 | if (val < 0) { 63 | s[--pos] = '-'; 64 | } 65 | 66 | torch_col = (col & 0x0F) | 0x60; 67 | torch_pos = state->world.info.torch_ticks > 0 68 | ? (state->world.info.torch_ticks + 39) / 40 69 | : 0; 70 | if (torch_pos > 5) torch_pos = 5; 71 | 72 | for (i = pos; i < sizeof(s); i++) { 73 | int16_t nx = x + i - pos; 74 | d_video->func_write(d_video, nx, y, nx < torch_pos ? torch_col : col, s[i]); 75 | } 76 | 77 | for (i = sizeof(s) - pos; i < torch_pos; i++) { 78 | d_video->func_write(d_video, x + i, y, torch_col, ' '); 79 | } 80 | for (; i < len; i++) { 81 | d_video->func_write(d_video, x + i, y, col, ' '); 82 | } 83 | } 84 | 85 | void zoo_draw_sidebar_slim(zoo_state *state, uint16_t flags) { 86 | int i; 87 | int x = 1; 88 | zoo_video_driver *d_video = state->d_video; 89 | 90 | if (state->game_state != GS_PLAY) { 91 | if (flags & ZOO_SIDEBAR_UPDATE_REDRAW) { 92 | for (i = 0; i < 60; i++) { 93 | d_video->func_write(d_video, i, 25, 0x0F, ' '); 94 | } 95 | } 96 | return; 97 | } 98 | 99 | // left-aligned 100 | 101 | if (flags & ZOO_SIDEBAR_UPDATE_HEALTH) { 102 | d_video->func_write(d_video, x, 25, 0x1C, '\x03'); 103 | d_video->func_write(d_video, x + 1, 25, 0x1C, ' '); 104 | write_number(d_video, x + 2, 25, 0x1F, state->world.info.health, 6); 105 | } 106 | x += 8; 107 | 108 | if (flags & ZOO_SIDEBAR_UPDATE_AMMO) { 109 | d_video->func_write(d_video, x, 25, 0x1B, '\x84'); 110 | d_video->func_write(d_video, x + 1, 25, 0x1B, ' '); 111 | write_number(d_video, x + 2, 25, 0x1F, state->world.info.ammo, 6); 112 | } 113 | x += 8; 114 | 115 | if (flags & ZOO_SIDEBAR_UPDATE_TORCHES) { 116 | d_video->func_write(d_video, x, 25, 0x1E, '\x9D'); 117 | d_video->func_write(d_video, x + 1, 25, 0x1E, ' '); 118 | write_number_torch_bg(state, x + 2, 25, 0x1F, state->world.info.torches, 6); 119 | } 120 | x += 8; 121 | 122 | if (flags & ZOO_SIDEBAR_UPDATE_GEMS) { 123 | d_video->func_write(d_video, x, 25, 0x19, '\x04'); 124 | d_video->func_write(d_video, x + 1, 25, 0x19, ' '); 125 | write_number(d_video, x + 2, 25, 0x1F, state->world.info.gems, 6); 126 | } 127 | x += 8; 128 | 129 | if (flags & ZOO_SIDEBAR_UPDATE_SCORE) { 130 | d_video->func_write(d_video, x, 25, 0x17, '\x9E'); 131 | d_video->func_write(d_video, x + 1, 25, 0x17, ' '); 132 | write_number(d_video, x + 2, 25, 0x1F, state->world.info.score, 6); 133 | } 134 | x += 8; 135 | 136 | if (flags & ZOO_SIDEBAR_UPDATE_KEYS) { 137 | for (i = 0; i < 7; i++) { 138 | if (state->world.info.keys[i]) 139 | d_video->func_write(d_video, x + i, 25, 0x19 + i, '\x0C'); 140 | else 141 | d_video->func_write(d_video, x + i, 25, 0x1F, ' '); 142 | } 143 | d_video->func_write(d_video, x + 7, 25, 0x1F, ' '); 144 | } 145 | x += 8; 146 | 147 | if (flags & ZOO_SIDEBAR_UPDATE_TIME) { 148 | if (state->board.info.time_limit_sec > 0) { 149 | d_video->func_write(d_video, x, 25, 0x1E, 'T'); 150 | d_video->func_write(d_video, x + 1, 25, 0x1E, ' '); 151 | write_number(d_video, x + 2, 25, 0x1F, state->board.info.time_limit_sec - state->world.info.board_time_sec, 6); 152 | } else { 153 | for (i = 0; i < 8; i++) { 154 | d_video->func_write(d_video, x + i, 25, 0x1E, ' '); 155 | } 156 | } 157 | } 158 | x += 8; 159 | 160 | if (flags & ZOO_SIDEBAR_UPDATE_REDRAW) { 161 | d_video->func_write(d_video, 0, 25, 0x1F, ' '); 162 | for (i = x; i < 60; i++) { 163 | d_video->func_write(d_video, i, 25, 0x1F, ' '); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/ui/zoo_ui.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "zoo_ui_internal.h" 29 | 30 | // game operations - LOAD WORLD 31 | 32 | static zoo_tick_retval zoo_ui_load_world_cb(zoo_state *zoo, zoo_ui_state *cb_state) { 33 | char *name = zoo_window_line_selected(&(cb_state->window)); 34 | bool as_save = !strcmp(cb_state->filesel_extension, ".SAV"); 35 | zoo_io_handle h; 36 | int ret; 37 | 38 | if (cb_state->window.accepted && name != NULL) { 39 | // TODO: warn for long filenames (> 20 chars, minus extension); 40 | h = zoo->d_io->func_open_file(zoo->d_io, name, MODE_READ); 41 | ret = zoo_world_load(zoo, &h, false); 42 | if (!ret) { 43 | if (as_save) { 44 | zoo_game_start(zoo, GS_PLAY); 45 | } else { 46 | zoo_board_change(zoo, 0); 47 | zoo_game_start(zoo, GS_TITLE); 48 | } 49 | zoo_redraw(zoo); 50 | } else { 51 | // TODO: I/O error message 52 | } 53 | h.func_close(&h); 54 | } 55 | 56 | zoo_window_close(&cb_state->window); 57 | return EXIT; 58 | } 59 | 60 | void zoo_ui_load_world(zoo_ui_state *state, bool as_save) { 61 | zoo_ui_filesel_call(state, as_save ? "Saved Games" : "ZZT Worlds", as_save ? ".SAV" : ".ZZT", (zoo_func_callback) zoo_ui_load_world_cb); 62 | } 63 | 64 | // game operations - SAVE WORLD 65 | 66 | static zoo_tick_retval zoo_ui_save_world_cb(zoo_ui_state *state, const char *filename, bool accepted) { 67 | char full_filename[ZOO_PATH_MAX + 1]; 68 | zoo_io_handle h; 69 | int ret; 70 | 71 | if (!accepted) return EXIT; 72 | 73 | strncpy(full_filename, filename, ZOO_PATH_MAX); 74 | strncat(full_filename, ".SAV", ZOO_PATH_MAX); 75 | 76 | // TODO: warn for long filenames (> 20 chars, minus extension); 77 | h = state->zoo->d_io->func_open_file(state->zoo->d_io, full_filename, MODE_WRITE); 78 | ret = zoo_world_save(state->zoo, &h); 79 | if (ret) { 80 | // TODO: I/O error message 81 | } 82 | h.func_close(&h); 83 | 84 | return EXIT; 85 | } 86 | 87 | void zoo_ui_save_world(zoo_ui_state *state) { 88 | zoo_ui_popup_prompt_string(state, ZOO_UI_PROMPT_ANY, 4, 18, 50, "Save name? (.SAV)", state->zoo->world.info.name, zoo_ui_save_world_cb); 89 | } 90 | 91 | // game operations - CHEAT 92 | 93 | #ifdef ZOO_UI_CHEAT_HISTORY 94 | static void zoo_ui_cheat_add_history(zoo_ui_state *state, const char *cmd) { 95 | int i; 96 | for (i = 0; i < ZOO_UI_CHEAT_HISTORY_SIZE; i++) { 97 | if (!strcmp(cmd, state->cheat_history[i])) { 98 | return; 99 | } 100 | } 101 | 102 | if (state->cheat_history[ZOO_UI_CHEAT_HISTORY_SIZE - 1] != NULL) { 103 | free(state->cheat_history[ZOO_UI_CHEAT_HISTORY_SIZE - 1]); 104 | } 105 | 106 | memmove(state->cheat_history + 1, state->cheat_history, sizeof(char*) * (ZOO_UI_CHEAT_HISTORY_SIZE - 1)); 107 | state->cheat_history[0] = malloc(strlen(cmd) + 1); 108 | strcpy(state->cheat_history[0], cmd); 109 | } 110 | #endif 111 | 112 | static zoo_tick_retval zoo_ui_cheat_cb(zoo_ui_state *state, const char *cmd, bool accepted) { 113 | if (!accepted) return RETURN_IMMEDIATE; 114 | 115 | zoo_game_debug_command(state->zoo, cmd); 116 | #ifdef ZOO_UI_CHEAT_HISTORY 117 | zoo_ui_cheat_add_history(state, cmd); 118 | #endif 119 | 120 | return RETURN_IMMEDIATE; 121 | } 122 | 123 | static void zoo_ui_cheat_prompt(zoo_ui_state *state) { 124 | zoo_ui_popup_prompt_string(state, ZOO_UI_PROMPT_ANY, 4, 18, 50, "Command?", "", zoo_ui_cheat_cb); 125 | } 126 | 127 | #ifdef ZOO_UI_CHEAT_HISTORY 128 | static zoo_tick_retval zoo_ui_cheat_window_cb(zoo_state *zoo, zoo_ui_state *cb_state) { 129 | char hyperlink[51]; 130 | strncpy(hyperlink, cb_state->window.hyperlink, sizeof(hyperlink)); 131 | zoo_window_close(&cb_state->window); 132 | 133 | if (hyperlink[0] == 'Z') { 134 | // new cheat 135 | zoo_ui_cheat_prompt(cb_state); 136 | } else { 137 | // old cheat 138 | zoo_game_debug_command(zoo, cb_state->cheat_history[(hyperlink[0] - '0') % ZOO_UI_CHEAT_HISTORY_SIZE]); 139 | } 140 | 141 | return EXIT; 142 | } 143 | 144 | void zoo_ui_cheat(zoo_ui_state *state) { 145 | int i; 146 | char line[51]; 147 | 148 | if (state->cheat_history[0] != NULL) { 149 | // cheat history stored, build window 150 | zoo_ui_init_select_window(state, "Select Command"); 151 | for (i = 0; i < ZOO_UI_CHEAT_HISTORY_SIZE; i++) { 152 | if (state->cheat_history[i] == NULL) { 153 | break; 154 | } 155 | line[0] = '!'; 156 | line[1] = i + '0'; 157 | line[2] = ';'; 158 | strncpy(line + 3, state->cheat_history[i], sizeof(line) - 1 - 3); 159 | zoo_window_append(&state->window, line); 160 | } 161 | zoo_window_append(&state->window, "!Z;New command"); 162 | zoo_call_push_callback(&(state->zoo->call_stack), (zoo_func_callback) zoo_ui_cheat_window_cb, state); 163 | zoo_window_open(state->zoo, &state->window); 164 | } else { 165 | zoo_ui_cheat_prompt(state); 166 | } 167 | } 168 | #else 169 | void zoo_ui_cheat(zoo_ui_state *state) { 170 | zoo_ui_cheat_prompt(state); 171 | } 172 | #endif 173 | 174 | // main menu 175 | 176 | static zoo_tick_retval zoo_ui_main_menu_cb(zoo_state *zoo, zoo_ui_state *cb_state) { 177 | char hyperlink[21]; 178 | strncpy(hyperlink, cb_state->window.hyperlink, sizeof(hyperlink)); 179 | 180 | zoo_window_close(&cb_state->window); 181 | zoo_call_pop(&zoo->call_stack); 182 | 183 | if (!strcmp(hyperlink, "play")) { 184 | if (cb_state->zoo->game_state == GS_TITLE) { 185 | zoo_world_play(zoo); 186 | } 187 | } else if (!strcmp(hyperlink, "load")) { 188 | zoo_ui_load_world(cb_state, false); 189 | } else if (!strcmp(hyperlink, "restore")) { 190 | zoo_ui_load_world(cb_state, true); 191 | } else if (!strcmp(hyperlink, "save")) { 192 | zoo_ui_save_world(cb_state); 193 | } else if (!strcmp(hyperlink, "cheat")) { 194 | zoo_ui_cheat(cb_state); 195 | } else if (!strcmp(hyperlink, "quit")) { 196 | if (cb_state->zoo->game_state == GS_PLAY) { 197 | zoo_world_return_title(zoo); 198 | } 199 | #ifdef ZOO_DEBUG_MENU 200 | } else if (!strcmp(hyperlink, "zoo_debug")) { 201 | zoo_ui_debug_menu(cb_state); 202 | #endif 203 | } 204 | 205 | return RETURN_IMMEDIATE; 206 | } 207 | 208 | void zoo_ui_main_menu(struct s_zoo_ui_state *state) { 209 | zoo_ui_init_select_window(state, "Main Menu"); 210 | 211 | if (state->zoo->game_state == GS_PLAY) { 212 | zoo_window_append(&state->window, "!cheat;Input command"); 213 | } 214 | 215 | if (state->zoo->game_state != GS_PLAY) { 216 | zoo_window_append(&state->window, "!play;Play world"); 217 | } 218 | 219 | if (state->zoo->d_io != NULL) { 220 | zoo_window_append(&state->window, "!load;Load world"); 221 | if (!(state->zoo->d_io->read_only)) { 222 | if (state->zoo->game_state == GS_PLAY) { 223 | zoo_window_append(&state->window, "!save;Save world"); 224 | } 225 | zoo_window_append(&state->window, "!restore;Restore world"); 226 | } 227 | } 228 | 229 | if (state->zoo->game_state == GS_PLAY) { 230 | zoo_window_append(&state->window, "!quit;Quit world"); 231 | } 232 | 233 | #ifdef ZOO_DEBUG_MENU 234 | zoo_window_append(&state->window, "!zoo_debug;Debug options"); 235 | #endif 236 | 237 | zoo_call_push_callback(&(state->zoo->call_stack), (zoo_func_callback) zoo_ui_main_menu_cb, state); 238 | zoo_window_open(state->zoo, &state->window); 239 | } 240 | 241 | // state definitions 242 | 243 | void zoo_ui_init(zoo_ui_state *state, zoo_state *zoo) { 244 | memset(state, 0, sizeof(zoo_ui_state)); 245 | state->zoo = zoo; 246 | zoo_ui_input_init(&state->input); 247 | } 248 | 249 | void zoo_ui_tick(zoo_ui_state *state) { 250 | uint16_t key; 251 | bool in_game = state->zoo->game_state == GS_PLAY; 252 | 253 | if (!zoo_call_empty(&state->zoo->call_stack)) { 254 | return; 255 | } 256 | 257 | while ((key = zoo_ui_input_key_pop(&state->input)) != 0) { 258 | if (key & ZOO_KEY_RELEASED) continue; 259 | 260 | // TODO: temporary 261 | if (key == ZOO_KEY_F1) { 262 | zoo_ui_main_menu(state); 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /src/ui/zoo_ui_debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "zoo_ui_internal.h" 6 | 7 | extern int platform_debug_free_memory(void); 8 | extern void platform_debug_puts(const char *str, bool status); 9 | 10 | static char debug_buffer[80 + 1]; 11 | 12 | void zoo_ui_debug_printf(bool status, const char *format, ...) { 13 | va_list args; 14 | va_start(args, format); 15 | vsniprintf(debug_buffer, sizeof(debug_buffer), format, args); 16 | va_end(args); 17 | platform_debug_puts(debug_buffer, status); 18 | } 19 | 20 | static ZOO_INLINE void zoo_ui_dbg_memfree(zoo_state *zoo) { 21 | zoo_ui_debug_printf(false, "free mem = %d bytes\n", platform_debug_free_memory()); 22 | } 23 | 24 | static ZOO_INLINE void zoo_ui_dbg_memtest(zoo_state *zoo) { 25 | // Load every board. 26 | int curr_board = zoo->world.info.current_board; 27 | int i, j; 28 | 29 | zoo_board_close(zoo); 30 | 31 | for (i = 0; i <= zoo->world.board_count; i++) { 32 | zoo_board_open(zoo, i); 33 | zoo_ui_debug_printf(false, "opening board %d, free mem = %d bytes\n", i, platform_debug_free_memory()); 34 | #ifdef ZOO_USE_LABEL_CACHE 35 | for (j = 0; j <= zoo->board.stat_count; j++) 36 | zoo_oop_label_cache_build(zoo, j); 37 | #endif 38 | zoo_board_close(zoo); 39 | } 40 | 41 | zoo_board_open(zoo, curr_board); 42 | } 43 | 44 | static zoo_tick_retval zoo_ui_debug_menu_cb(zoo_state *zoo, zoo_ui_state *cb_state) { 45 | char hyperlink[21]; 46 | strncpy(hyperlink, cb_state->window.hyperlink, sizeof(hyperlink)); 47 | 48 | zoo_window_close(&cb_state->window); 49 | zoo_call_pop(&zoo->call_stack); 50 | 51 | if (!strcmp(hyperlink, "memfree")) { 52 | zoo_ui_dbg_memfree(zoo); 53 | } else if (!strcmp(hyperlink, "bmemtest")) { 54 | zoo_ui_dbg_memtest(zoo); 55 | } 56 | 57 | return RETURN_IMMEDIATE; 58 | } 59 | 60 | void zoo_ui_debug_menu(struct s_zoo_ui_state *state) { 61 | zoo_ui_init_select_window(state, "Debug Menu"); 62 | 63 | zoo_window_append(&state->window, "!memfree;Print free memory"); 64 | if (state->zoo->game_state == GS_PLAY && !state->zoo->game_paused) { 65 | zoo_window_append(&state->window, "!bmemtest;Test board memory usage"); 66 | } 67 | 68 | zoo_call_push_callback(&(state->zoo->call_stack), (zoo_func_callback) zoo_ui_debug_menu_cb, state); 69 | zoo_window_open(state->zoo, &state->window); 70 | } 71 | -------------------------------------------------------------------------------- /src/ui/zoo_ui_file_select.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "zoo_ui_internal.h" 29 | 30 | static bool zoo_ui_filesel_dir_scan_cb(zoo_io_path_driver *drv, zoo_io_dirent *e, void *cb_arg) { 31 | zoo_ui_state *state = (zoo_ui_state *) cb_arg; 32 | char *dirext; 33 | 34 | if (e->type == TYPE_DIR) { 35 | // TODO: add directory support 36 | return true; 37 | } 38 | 39 | if (state->filesel_extension[0] != '\0') { 40 | dirext = strrchr(e->name, '.'); 41 | if (dirext == NULL || strcasecmp(state->filesel_extension, dirext)) { 42 | return true; 43 | } 44 | zoo_window_append(&state->window, e->name); 45 | } else { 46 | zoo_window_append(&state->window, e->name); 47 | } 48 | return true; 49 | } 50 | 51 | void zoo_ui_filesel_call(zoo_ui_state *state, const char *title, const char *extension, zoo_func_callback cb) { 52 | // TODO: add directory support 53 | zoo_io_path_driver *d_io = (zoo_io_path_driver *) state->zoo->d_io; 54 | 55 | zoo_ui_init_select_window(state, title); 56 | strcpy(state->filesel_extension, extension != NULL ? extension : ""); 57 | d_io->func_dir_scan(d_io, d_io->path, zoo_ui_filesel_dir_scan_cb, state); 58 | zoo_window_sort(state->zoo, &state->window); 59 | zoo_call_push_callback(&(state->zoo->call_stack), cb, state); 60 | zoo_window_open(state->zoo, &state->window); 61 | } -------------------------------------------------------------------------------- /src/ui/zoo_ui_input.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_ui_internal.h" 26 | #include "zoo_ui_input.h" 27 | 28 | void zoo_ui_input_init(zoo_ui_input_state *inp) { 29 | memset(inp, 0, sizeof(zoo_ui_input_state)); 30 | 31 | zoo_ui_input_key_map(inp, ZOO_ACTION_UP, ZOO_KEY_UP); 32 | zoo_ui_input_key_map(inp, ZOO_ACTION_DOWN, ZOO_KEY_DOWN); 33 | zoo_ui_input_key_map(inp, ZOO_ACTION_LEFT, ZOO_KEY_LEFT); 34 | zoo_ui_input_key_map(inp, ZOO_ACTION_RIGHT, ZOO_KEY_RIGHT); 35 | zoo_ui_input_key_map(inp, ZOO_ACTION_TORCH, 't'); 36 | zoo_ui_input_key_map(inp, ZOO_ACTION_OK, ZOO_KEY_ENTER); 37 | zoo_ui_input_key_map(inp, ZOO_ACTION_CANCEL, ZOO_KEY_ESCAPE); 38 | } 39 | 40 | void zoo_ui_input_key(zoo_state *zoo, zoo_ui_input_state *inp, uint16_t key, bool pressed) { 41 | int i; 42 | if (key == 0) return; 43 | 44 | for (i = 0; i < ZOO_ACTION_MAX; i++) { 45 | if (inp->action_to_kbd_button[i] == key) { 46 | zoo_input_action_set(&zoo->input, i, pressed); 47 | } 48 | } 49 | 50 | for (i = 0; i < ZOO_UI_KBDBUF_SIZE; i++) { 51 | if (inp->ui_kbd_buffer[i] == 0) { 52 | inp->ui_kbd_buffer[i] = key | (pressed ? 0 : ZOO_KEY_RELEASED); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | void zoo_ui_input_key_map(zoo_ui_input_state *inp, zoo_input_action action, uint16_t key) { 59 | inp->action_to_kbd_button[action] = key; 60 | } 61 | 62 | uint16_t zoo_ui_input_key_pop(zoo_ui_input_state *inp) { 63 | uint16_t key = inp->ui_kbd_buffer[0]; 64 | 65 | if (key != 0) { 66 | memmove(inp->ui_kbd_buffer, inp->ui_kbd_buffer + 1, sizeof(uint16_t) * (ZOO_UI_KBDBUF_SIZE - 1)); 67 | inp->ui_kbd_buffer[ZOO_UI_KBDBUF_SIZE - 1] = 0; 68 | } 69 | return key; 70 | } -------------------------------------------------------------------------------- /src/ui/zoo_ui_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef __ZOO_UI_INTERNAL_H__ 2 | #define __ZOO_UI_INTERNAL_H__ 3 | 4 | #include "../libzoo/zoo_internal.h" 5 | #include "zoo_ui.h" 6 | 7 | // all UI files are size-optimized 8 | ZOO_FILE_OPTIMIZE_SIZE 9 | 10 | struct s_zoo_ui_state; 11 | 12 | // zoo_ui_file_select.c 13 | 14 | void zoo_ui_filesel_call(struct s_zoo_ui_state *state, const char *title, const char *extension, zoo_func_callback cb); 15 | 16 | // zoo_ui_osk.c 17 | 18 | #ifdef ZOO_UI_OSK 19 | typedef struct { 20 | int8_t x; 21 | int8_t y; 22 | uint8_t normal; // if < 32 || >= 128, shifted has visual char; if 0, done 23 | uint8_t shifted; 24 | } zoo_osk_entry; 25 | 26 | typedef struct { 27 | const zoo_osk_entry *entries; 28 | 29 | uint8_t x; 30 | uint8_t y; 31 | int16_t e_pos; 32 | 33 | bool active; 34 | bool shifted; 35 | void *screen_copy; 36 | } zoo_osk_state; 37 | 38 | void zoo_osk_open(zoo_ui_state *ui, zoo_osk_state *osk, uint8_t x, uint8_t y); 39 | void zoo_osk_close(zoo_ui_state *ui, zoo_osk_state *osk); 40 | void zoo_osk_tick(zoo_ui_state *ui, zoo_osk_state *osk); 41 | #endif 42 | 43 | // zoo_ui_prompt_string.c 44 | 45 | typedef enum { 46 | ZOO_UI_PROMPT_NUMERIC, 47 | ZOO_UI_PROMPT_ALPHANUMERIC, 48 | ZOO_UI_PROMPT_ANY 49 | } zoo_ui_prompt_mode; 50 | 51 | typedef zoo_tick_retval (*zoo_ui_prompt_string_callback)(struct s_zoo_ui_state *state, const char *buffer, bool accepted); 52 | 53 | void zoo_ui_popup_prompt_string(zoo_ui_state *state, zoo_ui_prompt_mode mode, uint16_t x, uint16_t y, uint16_t width, 54 | const char *question, const char *answer, zoo_ui_prompt_string_callback cb); 55 | 56 | #define ZOO_UI_PROMPT_WIDTH 50 57 | 58 | typedef struct { 59 | struct s_zoo_ui_state *ui; 60 | 61 | uint16_t x, y, width; 62 | uint8_t arrow_color, text_color; 63 | zoo_ui_prompt_mode mode; 64 | char orig_buffer[ZOO_UI_PROMPT_WIDTH + 1]; 65 | char buffer[ZOO_UI_PROMPT_WIDTH + 1]; 66 | 67 | bool first_key_press; 68 | zoo_ui_prompt_string_callback callback; 69 | 70 | #ifdef ZOO_UI_OSK 71 | zoo_osk_state osk; 72 | #endif 73 | 74 | void *screen_copy; 75 | } zoo_ui_prompt_state; 76 | 77 | // zoo_ui_util.c 78 | 79 | void zoo_ui_init_select_window(struct s_zoo_ui_state *state, const char *title); 80 | 81 | // zoo_ui_debug.c 82 | 83 | #ifdef ZOO_DEBUG_MENU 84 | void zoo_ui_debug_printf(bool status, const char *format, ...); 85 | void zoo_ui_debug_menu(struct s_zoo_ui_state *state); 86 | #endif 87 | 88 | #endif /* __ZOO_UI_H__ */ -------------------------------------------------------------------------------- /src/ui/zoo_ui_osk.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "zoo_ui_internal.h" 26 | #include "zoo_ui_input.h" 27 | 28 | #define ZOO_OSK_SHIFT 128 29 | 30 | #define ZOO_OSK_WIDTH 33 31 | #define ZOO_OSK_HEIGHT 8 32 | #define ZOO_OSK_OX 4 33 | #define ZOO_OSK_OY 2 34 | 35 | static const zoo_osk_entry osk_entries_default[] = { 36 | {0, 0, '1', '!'}, 37 | {2, 0, '2', '@'}, 38 | {4, 0, '3', '#'}, 39 | {6, 0, '4', '$'}, 40 | {8, 0, '5', '%'}, 41 | {10, 0, '6', '^'}, 42 | {12, 0, '7', '&'}, 43 | {14, 0, '8', '*'}, 44 | {16, 0, '9', '('}, 45 | {18, 0, '0', ')'}, 46 | {20, 0, '-', '_'}, 47 | {22, 0, '=', '+'}, 48 | {24, 0, 8, 27}, 49 | {1, 1, 'q', 'Q'}, 50 | {3, 1, 'w', 'W'}, 51 | {5, 1, 'e', 'E'}, 52 | {7, 1, 'r', 'R'}, 53 | {9, 1, 't', 'T'}, 54 | {11, 1, 'y', 'Y'}, 55 | {13, 1, 'u', 'U'}, 56 | {15, 1, 'i', 'I'}, 57 | {17, 1, 'o', 'O'}, 58 | {19, 1, 'p', 'P'}, 59 | {21, 1, '[', '{'}, 60 | {23, 1, ']', '}'}, 61 | {0, 2, ZOO_OSK_SHIFT, 24}, 62 | {2, 2, 'a', 'A'}, 63 | {4, 2, 's', 'S'}, 64 | {6, 2, 'd', 'D'}, 65 | {8, 2, 'f', 'F'}, 66 | {10, 2, 'g', 'G'}, 67 | {12, 2, 'h', 'H'}, 68 | {14, 2, 'j', 'J'}, 69 | {16, 2, 'k', 'K'}, 70 | {18, 2, 'l', 'L'}, 71 | {20, 2, ';', ':'}, 72 | {22, 2, '\'', '"'}, 73 | {24, 2, 13, 26}, 74 | {3, 3, 'z', 'Z'}, 75 | {5, 3, 'x', 'X'}, 76 | {7, 3, 'c', 'C'}, 77 | {9, 3, 'v', 'V'}, 78 | {11, 3, 'b', 'B'}, 79 | {13, 3, 'n', 'N'}, 80 | {15, 3, 'm', 'M'}, 81 | {17, 3, ',', '<'}, 82 | {19, 3, '.', '>'}, 83 | {21, 3, '/', '?'}, 84 | {0, 0, 0, 0} 85 | }; 86 | 87 | static void zoo_osk_draw(zoo_state *state, zoo_osk_state *osk, bool redraw) { 88 | int i, ix, iy; 89 | const zoo_osk_entry *entry = osk->entries; 90 | zoo_video_driver *d_video = state->d_video; 91 | 92 | if (redraw) { 93 | zoo_window_draw_pattern(state, osk->x, osk->y, ZOO_OSK_WIDTH, 0x1F, ZOO_WINDOW_PATTERN_TOP); 94 | for (iy = 1; iy < ZOO_OSK_HEIGHT - 1; iy++) { 95 | zoo_window_draw_pattern(state, osk->x, osk->y + iy, ZOO_OSK_WIDTH, 0x1F, ZOO_WINDOW_PATTERN_INNER); 96 | } 97 | zoo_window_draw_pattern(state, osk->x, osk->y + ZOO_OSK_HEIGHT - 1, ZOO_OSK_WIDTH, 0x1F, ZOO_WINDOW_PATTERN_BOTTOM); 98 | } 99 | 100 | i = 0; 101 | for (; entry->normal != '\0'; entry++, i++) { 102 | ix = osk->x + ZOO_OSK_OX + entry->x; 103 | iy = osk->y + ZOO_OSK_OY + entry->y; 104 | d_video->func_write(d_video, ix, iy, osk->e_pos == i ? 0x71 : 0x1F, 105 | (entry->normal >= 32 && entry->normal < 128) ? (osk->shifted ? entry->shifted : entry->normal) : entry->shifted); 106 | } 107 | } 108 | 109 | void zoo_osk_tick(zoo_ui_state *ui, zoo_osk_state *osk) { 110 | int delta_x = 0; 111 | int delta_y = 0; 112 | int i, dist, min_dist, min_pos; 113 | int old_e_pos = osk->e_pos; 114 | const zoo_osk_entry *i_entry; 115 | const zoo_osk_entry *entry; 116 | 117 | if (!osk->active) return; 118 | 119 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_CANCEL)) { 120 | // ESCAPE 121 | zoo_ui_input_key(ui->zoo, &ui->input, ZOO_KEY_ESCAPE, true); 122 | zoo_ui_input_key(ui->zoo, &ui->input, ZOO_KEY_ESCAPE, false); 123 | return; 124 | } 125 | 126 | entry = &osk->entries[osk->e_pos]; 127 | 128 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_OK)) { 129 | if (entry->normal == ZOO_OSK_SHIFT) { 130 | osk->shifted = !osk->shifted; 131 | zoo_ui_input_key(ui->zoo, &ui->input, ZOO_KEY_SHIFT, osk->shifted); 132 | old_e_pos = -1; // force draw 133 | } else if (entry->normal >= 32 && entry->normal < 128) { 134 | zoo_ui_input_key(ui->zoo, &ui->input, osk->shifted ? entry->shifted : entry->normal, true); 135 | zoo_ui_input_key(ui->zoo, &ui->input, osk->shifted ? entry->shifted : entry->normal, false); 136 | } else { 137 | zoo_ui_input_key(ui->zoo, &ui->input, entry->normal, true); 138 | zoo_ui_input_key(ui->zoo, &ui->input, entry->normal, false); 139 | } 140 | } 141 | 142 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_LEFT)) delta_x--; 143 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_RIGHT)) delta_x++; 144 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_UP)) delta_y--; 145 | if (zoo_input_action_pressed(&ui->zoo->input, ZOO_ACTION_DOWN)) delta_y++; 146 | 147 | if (delta_y != 0) { 148 | min_dist = (1 << 30); 149 | min_pos = -1; 150 | for (i = 0, i_entry = osk->entries; i_entry->normal != '\0'; i++, i_entry++) { 151 | if (i == osk->e_pos) { 152 | continue; 153 | } 154 | if (zoo_signum(i_entry->y - entry->y) == zoo_signum(delta_y) 155 | && ((zoo_signum(i_entry->x - entry->x) == zoo_signum(delta_y) || entry->x == i_entry->x))) 156 | { 157 | dist = (zoo_abs(i_entry->y - entry->y) << 2) + zoo_abs(i_entry->x - entry->x); 158 | if (dist < min_dist) { 159 | min_dist = dist; 160 | min_pos = i; 161 | } 162 | } 163 | } 164 | if (min_pos >= 0) { 165 | osk->e_pos = min_pos; 166 | } 167 | } 168 | 169 | if (delta_x != 0) { 170 | min_dist = (1 << 30); 171 | min_pos = -1; 172 | for (i = 0, i_entry = osk->entries; i_entry->normal != '\0'; i++, i_entry++) { 173 | if (i == osk->e_pos) { 174 | continue; 175 | } 176 | if (i_entry->y == entry->y && zoo_signum(i_entry->x - entry->x) == zoo_signum(delta_x)) { 177 | dist = zoo_abs(i_entry->x - entry->x); 178 | if (dist < min_dist) { 179 | min_dist = dist; 180 | min_pos = i; 181 | } 182 | } 183 | } 184 | if (min_pos >= 0) { 185 | osk->e_pos = min_pos; 186 | } 187 | } 188 | 189 | if (osk->e_pos != old_e_pos) { 190 | zoo_osk_draw(ui->zoo, osk, false); 191 | } 192 | } 193 | 194 | void zoo_osk_open(zoo_ui_state *ui, zoo_osk_state *osk, uint8_t x, uint8_t y) { 195 | memset(osk, 0, sizeof(zoo_osk_state)); 196 | osk->x = x; 197 | osk->y = y; 198 | osk->entries = osk_entries_default; 199 | osk->active = true; 200 | 201 | zoo_ui_input_key(ui->zoo, &ui->input, ZOO_KEY_SHIFT, false); 202 | 203 | osk->screen_copy = zoo_store_display(ui->zoo, x, y, ZOO_OSK_WIDTH, ZOO_OSK_HEIGHT); 204 | zoo_osk_draw(ui->zoo, osk, true); 205 | } 206 | 207 | void zoo_osk_close(zoo_ui_state *ui, zoo_osk_state *osk) { 208 | zoo_ui_input_key(ui->zoo, &ui->input, ZOO_KEY_SHIFT, false); 209 | 210 | zoo_restore_display(ui->zoo, osk->screen_copy, ZOO_OSK_WIDTH, ZOO_OSK_HEIGHT, 211 | 0, 0, ZOO_OSK_WIDTH, ZOO_OSK_HEIGHT, 212 | osk->x, osk->y); 213 | zoo_free_display(ui->zoo, osk->screen_copy); 214 | 215 | osk->active = false; 216 | } -------------------------------------------------------------------------------- /src/ui/zoo_ui_prompt.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "zoo_ui_internal.h" 29 | 30 | static void zoo_ui_prompt_string_draw(zoo_state *zoo, zoo_ui_prompt_state *state) { 31 | int i, buf_len; 32 | zoo_video_driver *d_video = zoo->d_video; 33 | 34 | buf_len = strlen(state->buffer); 35 | for (i = 0; i < state->width; i++) { 36 | d_video->func_write(d_video, state->x + i, state->y, state->text_color, (i < buf_len) ? state->buffer[i] : ' '); 37 | d_video->func_write(d_video, state->x + i, state->y - 1, state->arrow_color, ' '); 38 | } 39 | d_video->func_write(d_video, state->x + state->width, state->y - 1, state->arrow_color, ' '); 40 | d_video->func_write(d_video, state->x + buf_len, state->y - 1, (state->arrow_color & 0xF0) | 0x0F, 31); 41 | } 42 | 43 | static zoo_tick_retval zoo_ui_prompt_string_cb(zoo_state *zoo, zoo_ui_prompt_state *state) { 44 | uint16_t key; 45 | int buf_len; 46 | bool accepted; 47 | bool changed = false; 48 | zoo_tick_retval ret_cb; 49 | 50 | #ifdef ZOO_UI_OSK 51 | zoo_osk_tick(state->ui, &state->osk); 52 | #endif 53 | 54 | while ((key = zoo_ui_input_key_pop(&state->ui->input)) != 0) { 55 | if (key & ZOO_KEY_RELEASED) continue; 56 | 57 | if (key >= 32 && key < 128) { 58 | if (state->first_key_press) { 59 | state->buffer[0] = '\0'; 60 | state->first_key_press = false; 61 | } 62 | 63 | accepted = false; 64 | switch (state->mode) { 65 | case ZOO_UI_PROMPT_NUMERIC: 66 | accepted = (key >= '0') && (key <= '9'); 67 | break; 68 | case ZOO_UI_PROMPT_ALPHANUMERIC: 69 | if ( 70 | ((zoo_toupper(key) >= 'A') && (zoo_toupper(key) <= 'Z')) 71 | || ((key >= '0') && key <= '9') || (key == '-') 72 | ) { 73 | accepted = true; 74 | key = zoo_toupper(key); 75 | } 76 | break; 77 | case ZOO_UI_PROMPT_ANY: 78 | accepted = true; 79 | break; 80 | } 81 | 82 | if (accepted) { 83 | buf_len = strlen(state->buffer); 84 | if (buf_len < state->width) { 85 | state->buffer[buf_len] = key; 86 | state->buffer[buf_len + 1] = '\0'; 87 | } 88 | changed = true; 89 | } 90 | } else if (key == ZOO_KEY_LEFT || key == ZOO_KEY_BACKSPACE) { 91 | buf_len = strlen(state->buffer); 92 | if (buf_len > 0) { 93 | state->buffer[buf_len - 1] = '\0'; 94 | changed = true; 95 | } 96 | } else if (key == ZOO_KEY_ENTER || key == ZOO_KEY_ESCAPE) { 97 | ret_cb = state->callback(state->ui, key == ZOO_KEY_ESCAPE ? state->orig_buffer : state->buffer, key != ZOO_KEY_ESCAPE); 98 | 99 | #ifdef ZOO_UI_OSK 100 | zoo_osk_close(state->ui, &state->osk); 101 | #endif 102 | 103 | zoo_restore_display(state->ui->zoo, state->screen_copy, 60, 25, 0, 0, 60, 25, 0, 0); 104 | zoo_free_display(state->ui->zoo, state->screen_copy); 105 | free(state); 106 | return EXIT; 107 | } 108 | 109 | state->first_key_press = false; 110 | } 111 | 112 | if (changed) { 113 | zoo_ui_prompt_string_draw(zoo, state); 114 | } 115 | 116 | return RETURN_NEXT_FRAME; 117 | } 118 | 119 | static void zoo_ui_prompt_state_init(zoo_ui_state *state, zoo_ui_prompt_state *prompt, const char *answer, uint16_t x, uint16_t y, uint16_t width) { 120 | memset(prompt, 0, sizeof(zoo_ui_prompt_state)); 121 | prompt->x = x; 122 | prompt->y = y; 123 | prompt->width = width; 124 | prompt->mode = ZOO_UI_PROMPT_ANY; 125 | prompt->ui = state; 126 | prompt->screen_copy = zoo_store_display(state->zoo, 0, 0, 60, 25); 127 | prompt->first_key_press = true; 128 | strncpy(prompt->orig_buffer, answer, width); 129 | prompt->orig_buffer[width] = '\0'; 130 | strcpy(prompt->buffer, prompt->orig_buffer); 131 | } 132 | 133 | void zoo_ui_popup_prompt_string(zoo_ui_state *state, zoo_ui_prompt_mode mode, uint16_t x, uint16_t y, uint16_t width, 134 | const char *question, const char *answer, zoo_ui_prompt_string_callback cb) 135 | { 136 | uint16_t inner_x = x + 7; 137 | uint16_t inner_y = y + 4; 138 | uint16_t inner_width = width - 14; 139 | uint8_t color = 0x4F; 140 | int question_len = strlen(question); 141 | int i; 142 | 143 | zoo_ui_prompt_state *prompt = malloc(sizeof(zoo_ui_prompt_state)); 144 | if (prompt == NULL) { 145 | cb(state, answer, false); 146 | return; 147 | } 148 | 149 | if (width > ZOO_UI_PROMPT_WIDTH) width = ZOO_UI_PROMPT_WIDTH; 150 | width = inner_width + 14; 151 | 152 | zoo_ui_prompt_state_init(state, prompt, answer, inner_x, inner_y, inner_width); 153 | prompt->arrow_color = prompt->text_color = color; 154 | prompt->mode = mode; 155 | prompt->callback = cb; 156 | 157 | zoo_window_draw_pattern(state->zoo, x, y , width, color, ZOO_WINDOW_PATTERN_TOP); 158 | zoo_window_draw_pattern(state->zoo, x, y + 1, width, color, ZOO_WINDOW_PATTERN_INNER); 159 | zoo_window_draw_pattern(state->zoo, x, y + 2, width, color, ZOO_WINDOW_PATTERN_SEPARATOR); 160 | zoo_window_draw_pattern(state->zoo, x, y + 3, width, color, ZOO_WINDOW_PATTERN_INNER); 161 | zoo_window_draw_pattern(state->zoo, x, y + 4, width, color, ZOO_WINDOW_PATTERN_INNER); 162 | zoo_window_draw_pattern(state->zoo, x, y + 5, width, color, ZOO_WINDOW_PATTERN_BOTTOM); 163 | 164 | for (i = 0; i < question_len; i++) { 165 | state->zoo->d_video->func_write(state->zoo->d_video, x + ((width - question_len) >> 1) + i, y + 1, color, question[i]); 166 | } 167 | 168 | // TODO: move elsewhere (general key buffer reset) 169 | while (zoo_ui_input_key_pop(&state->input) != 0); 170 | zoo_call_push_callback(&state->zoo->call_stack, (zoo_func_callback) zoo_ui_prompt_string_cb, prompt); 171 | 172 | zoo_ui_prompt_string_draw(state->zoo, prompt); 173 | 174 | #ifdef ZOO_UI_OSK 175 | zoo_osk_open(state, &prompt->osk, 12, 3); 176 | #endif 177 | } 178 | -------------------------------------------------------------------------------- /src/ui/zoo_ui_util.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 Adrian Siekierka 3 | * 4 | * Based on a reconstruction of code from ZZT, 5 | * Copyright 1991 Epic MegaGames, used with permission. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "zoo_ui_internal.h" 29 | 30 | void zoo_ui_init_select_window(zoo_ui_state *state, const char *title) { 31 | memset(&state->window, 0, sizeof(zoo_text_window)); 32 | strcpy(state->window.title, title); 33 | state->window.manual_close = true; 34 | state->window.hyperlink_as_select = true; 35 | } -------------------------------------------------------------------------------- /tools/bin2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from collections import OrderedDict 4 | from pathlib import Path 5 | import argparse 6 | import re 7 | import sys 8 | 9 | def main(args): 10 | if args.field_name is not None and len(args.input) > 1: 11 | raise Exception("Cannot use --field_name with more than one input file!") 12 | files = OrderedDict() 13 | for fn in args.input: 14 | file_key = args.field_name or ("_%s" % re.sub(r"[^a-zA-Z0-9]", "_", Path(fn).name)) 15 | with open(fn, "rb") as f: 16 | files[file_key] = bytearray(f.read()) 17 | # generate header file 18 | with open(args.outh, "w") as f: 19 | f.write("// autogenerated by bin2c.py\n") 20 | f.write("#include \n") 21 | for field_name, data in files.items(): 22 | f.write("\n") 23 | f.write("#define %s_size %d\n" % (field_name, len(data))) 24 | f.write("extern uint8_t %s[%s_size];\n" % (field_name, field_name)) 25 | # generate C file 26 | line_step = 16 27 | with open(args.outc, "w") as f: 28 | f.write("// autogenerated by bin2c.py\n") 29 | f.write("#include \n") 30 | for field_name, data in files.items(): 31 | f.write("\n") 32 | f.write("uint8_t %s[] = {\n" % field_name) 33 | for i in range(0, len(data), line_step): 34 | data_part = data[i:(i + line_step)] 35 | data_part_str = ", ".join([str(x) for x in data_part]) 36 | if (i + line_step) < len(data): 37 | f.write("\t%s, // %d\n" % (data_part_str, i)) 38 | else: 39 | f.write("\t%s // %d\n" % (data_part_str, i)) 40 | f.write("};\n") 41 | 42 | if __name__ == '__main__': 43 | args_parser = argparse.ArgumentParser( 44 | description="Convert binary files to a .C/.H pair" 45 | ) 46 | args_parser.add_argument("--field_name", required=False, type=str, help="Target field name (for one input file).") 47 | args_parser.add_argument("outc", help="Output C file.") 48 | args_parser.add_argument("outh", help="Output header file.") 49 | args_parser.add_argument("input", nargs="+", help="Input binary file.") 50 | args = args_parser.parse_args() 51 | main(args) -------------------------------------------------------------------------------- /tools/tok2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from collections import OrderedDict 4 | from pathlib import Path 5 | import argparse 6 | import re 7 | import sys 8 | 9 | toksets = {} 10 | 11 | def main(args): 12 | script_dir = Path(__file__).resolve().parent 13 | stem = Path(args.i).stem 14 | struct_type = "tok_entry_%s" % stem 15 | token_type = "uint8_t" 16 | 17 | with open(args.i, "r") as f: 18 | curr_tokset = None 19 | curr_tokid = 0 20 | for line in f.readlines(): 21 | if line.startswith("\t"): 22 | primary = True 23 | for tokname in line.strip().split(" "): 24 | toksets[curr_tokset][tokname] = {'id': curr_tokid, 'primary': primary} 25 | primary = False 26 | curr_tokid += 1 27 | else: 28 | # new tokset 29 | curr_tokset = line.strip() 30 | curr_tokid = 0 31 | toksets[curr_tokset] = {} 32 | with open(args.o, "w") as f: 33 | f.write("// autogenerated by tok2c.py\n\n") 34 | f.write("#include \n") 35 | f.write("#include \n") 36 | f.write("#include \n\n") 37 | # generate defines 38 | for tokset_name, tokset in toksets.items(): 39 | for tokset_entry_name, tokset_entry in tokset.items(): 40 | if tokset_entry['primary']: 41 | f.write("#define TOK_%s_%s %d\n" % (tokset_name, tokset_entry_name, tokset_entry['id'])) 42 | f.write("#define TOK_%s_INVALID %d\n" % (tokset_name, 255)) 43 | f.write("\n") 44 | # generate struct def 45 | f.write("typedef struct { const char *word; %s id; } %s;\n\n" % (token_type, struct_type)); 46 | # generate structs 47 | for tokset_name, tokset in toksets.items(): 48 | tokset_struct_name = "tok_%s_%s" % (stem, tokset_name.lower()) 49 | f.write("static const %s %s[] = {\n" % (struct_type, tokset_struct_name)) 50 | for tokset_entry_name, tokset_entry in sorted(tokset.items()): 51 | f.write("\t{\"%s\", %d},\n" % (tokset_entry_name, tokset_entry['id'])) 52 | f.write("\t{\"\", TOK_%s_INVALID}\n" % (tokset_name)) 53 | f.write("};\n\n") 54 | # copy code 55 | with open(script_dir.joinpath("tok2c_tpl.c")) as ff: 56 | tpl_code = ff.read() 57 | tpl_code = tpl_code.replace("%STEM%", stem) 58 | tpl_code = tpl_code.replace("%STRUCT_TYPE%", struct_type) 59 | tpl_code = tpl_code.replace("%TOKEN_TYPE%", token_type) 60 | f.write(tpl_code) 61 | 62 | if __name__ == '__main__': 63 | args_parser = argparse.ArgumentParser( 64 | description="Convert .tok files to .c files" 65 | ) 66 | args_parser.add_argument("i", help="Input file.") 67 | args_parser.add_argument("o", help="Output file.") 68 | args = args_parser.parse_args() 69 | main(args) 70 | -------------------------------------------------------------------------------- /tools/tok2c_tpl.c: -------------------------------------------------------------------------------- 1 | static %TOKEN_TYPE% %STEM%_search(const %STRUCT_TYPE% *table, const char *tok) { 2 | // TODO: optimize this 3 | while (table->word[0] != '\0') { 4 | if (table->word[0] == tok[0]) { 5 | if (!strcmp(tok + 1, table->word + 1)) { 6 | return table->id; 7 | } 8 | } 9 | table++; 10 | } 11 | return 255; 12 | } 13 | --------------------------------------------------------------------------------