From 094e3708e4bedb8bb9e43aa1ce317863fc5d5514 Mon Sep 17 00:00:00 2001 From: idolpx Date: Mon, 8 Jan 2024 11:58:15 -0600 Subject: [PATCH] initial commit --- .gitignore | 3 + CMakeLists.txt | 4 + deploy.py | 40 + include/README | 39 + include/ansi_codes.h | 131 +++ include/cbm_defines.h | 209 +++++ include/debug.h | 48 ++ lib/EdUrlParser/EdUrlParser.cpp | 291 +++++++ lib/EdUrlParser/EdUrlParser.h | 55 ++ lib/bus/bus.h | 71 ++ lib/bus/iec/iec.cpp | 980 +++++++++++++++++++++++ lib/bus/iec/iec.h | 582 ++++++++++++++ lib/device/iec/drive.cpp | 911 +++++++++++++++++++++ lib/device/iec/drive.h | 79 ++ lib/meatloaf/MIOException.h | 30 + lib/meatloaf/archive/archive_ml.cpp | 364 +++++++++ lib/meatloaf/archive/archive_ml.h | 181 +++++ lib/meatloaf/archive/cvt.h | 4 + lib/meatloaf/archive/lbr.h | 5 + lib/meatloaf/archive/lha.h | 0 lib/meatloaf/archive/lnx.h | 4 + lib/meatloaf/archive/lzh.h | 0 lib/meatloaf/archive/sda.h | 4 + lib/meatloaf/archive/sfx.h | 4 + lib/meatloaf/archive/spy.h | 4 + lib/meatloaf/archive/wr3.h | 0 lib/meatloaf/archive/wra.h | 4 + lib/meatloaf/archive/zipcode.h | 6 + lib/meatloaf/cartridge/crt.h | 4 + lib/meatloaf/cartridge/crt/easyfs.h | 4 + lib/meatloaf/cartridge/crt/normal.h | 0 lib/meatloaf/cbm_media.cpp | 105 +++ lib/meatloaf/cbm_media.h | 218 +++++ lib/meatloaf/container/d8b.cpp | 11 + lib/meatloaf/container/d8b.h | 112 +++ lib/meatloaf/container/dfi.cpp | 11 + lib/meatloaf/container/dfi.h | 204 +++++ lib/meatloaf/device/flash.cpp | 505 ++++++++++++ lib/meatloaf/device/flash.h | 167 ++++ lib/meatloaf/device/psram.cpp | 90 +++ lib/meatloaf/device/psram.h | 202 +++++ lib/meatloaf/device/sd.h | 49 ++ lib/meatloaf/disk/d40.h | 4 + lib/meatloaf/disk/d64.cpp | 468 +++++++++++ lib/meatloaf/disk/d64.h | 341 ++++++++ lib/meatloaf/disk/d71.cpp | 11 + lib/meatloaf/disk/d71.h | 118 +++ lib/meatloaf/disk/d80.cpp | 11 + lib/meatloaf/disk/d80.h | 103 +++ lib/meatloaf/disk/d81.cpp | 11 + lib/meatloaf/disk/d81.h | 113 +++ lib/meatloaf/disk/d82.cpp | 11 + lib/meatloaf/disk/d82.h | 122 +++ lib/meatloaf/disk/d90.cpp | 11 + lib/meatloaf/disk/d90.h | 157 ++++ lib/meatloaf/disk/dhd.h | 5 + lib/meatloaf/disk/dmk.h | 9 + lib/meatloaf/disk/dnp.cpp | 12 + lib/meatloaf/disk/dnp.h | 91 +++ lib/meatloaf/disk/dsk.cpp | 11 + lib/meatloaf/disk/dsk.h | 125 +++ lib/meatloaf/disk/dxm.h | 6 + lib/meatloaf/disk/f64.h | 2 + lib/meatloaf/disk/fdi.h | 0 lib/meatloaf/disk/g64.cpp | 11 + lib/meatloaf/disk/g64.h | 104 +++ lib/meatloaf/disk/hdd.h | 0 lib/meatloaf/disk/jvc.h | 6 + lib/meatloaf/disk/m2i.h | 0 lib/meatloaf/disk/nib.h | 4 + lib/meatloaf/disk/p64.h | 0 lib/meatloaf/disk/scp.h | 5 + lib/meatloaf/disk/vdk.h | 4 + lib/meatloaf/disk/woz.h | 6 + lib/meatloaf/disk/x64.h | 4 + lib/meatloaf/disk/z64.h | 0 lib/meatloaf/file/p00.cpp | 36 + lib/meatloaf/file/p00.h | 119 +++ lib/meatloaf/file/prg.h | 2 + lib/meatloaf/file/vsf.h | 5 + lib/meatloaf/iec_pipe.h | 152 ++++ lib/meatloaf/link/url.h | 47 ++ lib/meatloaf/link/webloc.h | 47 ++ lib/meatloaf/loaders/koa.h | 0 lib/meatloaf/loaders/psid.h | 0 lib/meatloaf/loaders/sid.h | 0 lib/meatloaf/make_unique.h | 42 + lib/meatloaf/meat_buffer.h | 689 ++++++++++++++++ lib/meatloaf/meat_io.cpp | 601 ++++++++++++++ lib/meatloaf/meat_io.h | 223 ++++++ lib/meatloaf/meat_stream.h | 138 ++++ lib/meatloaf/network/afp.h | 1 + lib/meatloaf/network/codenet.h | 6 + lib/meatloaf/network/fsp.h | 1 + lib/meatloaf/network/ftp.h | 1 + lib/meatloaf/network/http.cpp | 575 +++++++++++++ lib/meatloaf/network/http.h | 194 +++++ lib/meatloaf/network/ipfs.cpp | 22 + lib/meatloaf/network/ipfs.h | 122 +++ lib/meatloaf/network/nc.h | 3 + lib/meatloaf/network/nfs.h | 5 + lib/meatloaf/network/sftp.h | 3 + lib/meatloaf/network/smb.h | 3 + lib/meatloaf/network/tcp.h | 354 ++++++++ lib/meatloaf/network/telnet.h | 3 + lib/meatloaf/network/tftp.h | 3 + lib/meatloaf/network/tnfs.cpp | 428 ++++++++++ lib/meatloaf/network/tnfs.h | 167 ++++ lib/meatloaf/network/webdav.cpp | 228 ++++++ lib/meatloaf/network/webdav.h | 171 ++++ lib/meatloaf/network/ws.h | 176 ++++ lib/meatloaf/network/wss.h | 4 + lib/meatloaf/scanners/virus.h | 5 + lib/meatloaf/service/cs.cpp | 496 ++++++++++++ lib/meatloaf/service/cs.h | 309 +++++++ lib/meatloaf/service/gdrive.h | 6 + lib/meatloaf/service/ml.h | 46 ++ lib/meatloaf/tape/htap.h | 5 + lib/meatloaf/tape/t64.cpp | 218 +++++ lib/meatloaf/tape/t64.h | 120 +++ lib/meatloaf/tape/tap.cpp | 189 +++++ lib/meatloaf/tape/tap.h | 127 +++ lib/meatloaf/tape/tcrt.cpp | 231 ++++++ lib/meatloaf/tape/tcrt.h | 171 ++++ lib/meatloaf/wrappers/directory_stream.h | 110 +++ lib/meatloaf/wrappers/iec_buffer.cpp | 156 ++++ lib/meatloaf/wrappers/iec_buffer.h | 98 +++ lib/utils/U8Char.cpp | 171 ++++ lib/utils/U8Char.h | 39 + lib/utils/endianness.h | 15 + lib/utils/peoples_url_parser.cpp | 281 +++++++ lib/utils/peoples_url_parser.h | 80 ++ lib/utils/punycode.cpp | 316 ++++++++ lib/utils/punycode.h | 38 + lib/utils/string_utils.cpp | 570 +++++++++++++ lib/utils/string_utils.h | 70 ++ modify_path.py | 3 + platformio.ini | 91 +++ src/main.cpp | 42 + src/test_archive.cpp | 201 +++++ src/test_archive.h | 18 + src/test_meatloaf_filesystems.cpp | 58 ++ src/test_meatloaf_filesystems.h | 9 + src/test_parsers.cpp | 44 + src/test_parsers.h | 10 + src/test_punycode.cpp | 37 + src/test_punycode.h | 6 + test/README | 11 + 148 files changed, 16589 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 deploy.py create mode 100644 include/README create mode 100644 include/ansi_codes.h create mode 100644 include/cbm_defines.h create mode 100644 include/debug.h create mode 100644 lib/EdUrlParser/EdUrlParser.cpp create mode 100644 lib/EdUrlParser/EdUrlParser.h create mode 100644 lib/bus/bus.h create mode 100644 lib/bus/iec/iec.cpp create mode 100644 lib/bus/iec/iec.h create mode 100644 lib/device/iec/drive.cpp create mode 100644 lib/device/iec/drive.h create mode 100644 lib/meatloaf/MIOException.h create mode 100644 lib/meatloaf/archive/archive_ml.cpp create mode 100644 lib/meatloaf/archive/archive_ml.h create mode 100644 lib/meatloaf/archive/cvt.h create mode 100644 lib/meatloaf/archive/lbr.h create mode 100644 lib/meatloaf/archive/lha.h create mode 100644 lib/meatloaf/archive/lnx.h create mode 100644 lib/meatloaf/archive/lzh.h create mode 100644 lib/meatloaf/archive/sda.h create mode 100644 lib/meatloaf/archive/sfx.h create mode 100644 lib/meatloaf/archive/spy.h create mode 100644 lib/meatloaf/archive/wr3.h create mode 100644 lib/meatloaf/archive/wra.h create mode 100644 lib/meatloaf/archive/zipcode.h create mode 100644 lib/meatloaf/cartridge/crt.h create mode 100644 lib/meatloaf/cartridge/crt/easyfs.h create mode 100644 lib/meatloaf/cartridge/crt/normal.h create mode 100644 lib/meatloaf/cbm_media.cpp create mode 100644 lib/meatloaf/cbm_media.h create mode 100644 lib/meatloaf/container/d8b.cpp create mode 100644 lib/meatloaf/container/d8b.h create mode 100644 lib/meatloaf/container/dfi.cpp create mode 100644 lib/meatloaf/container/dfi.h create mode 100644 lib/meatloaf/device/flash.cpp create mode 100644 lib/meatloaf/device/flash.h create mode 100644 lib/meatloaf/device/psram.cpp create mode 100644 lib/meatloaf/device/psram.h create mode 100755 lib/meatloaf/device/sd.h create mode 100644 lib/meatloaf/disk/d40.h create mode 100644 lib/meatloaf/disk/d64.cpp create mode 100644 lib/meatloaf/disk/d64.h create mode 100644 lib/meatloaf/disk/d71.cpp create mode 100644 lib/meatloaf/disk/d71.h create mode 100644 lib/meatloaf/disk/d80.cpp create mode 100644 lib/meatloaf/disk/d80.h create mode 100644 lib/meatloaf/disk/d81.cpp create mode 100644 lib/meatloaf/disk/d81.h create mode 100644 lib/meatloaf/disk/d82.cpp create mode 100644 lib/meatloaf/disk/d82.h create mode 100644 lib/meatloaf/disk/d90.cpp create mode 100644 lib/meatloaf/disk/d90.h create mode 100644 lib/meatloaf/disk/dhd.h create mode 100644 lib/meatloaf/disk/dmk.h create mode 100644 lib/meatloaf/disk/dnp.cpp create mode 100644 lib/meatloaf/disk/dnp.h create mode 100644 lib/meatloaf/disk/dsk.cpp create mode 100644 lib/meatloaf/disk/dsk.h create mode 100644 lib/meatloaf/disk/dxm.h create mode 100644 lib/meatloaf/disk/f64.h create mode 100644 lib/meatloaf/disk/fdi.h create mode 100644 lib/meatloaf/disk/g64.cpp create mode 100644 lib/meatloaf/disk/g64.h create mode 100644 lib/meatloaf/disk/hdd.h create mode 100644 lib/meatloaf/disk/jvc.h create mode 100644 lib/meatloaf/disk/m2i.h create mode 100644 lib/meatloaf/disk/nib.h create mode 100644 lib/meatloaf/disk/p64.h create mode 100644 lib/meatloaf/disk/scp.h create mode 100644 lib/meatloaf/disk/vdk.h create mode 100644 lib/meatloaf/disk/woz.h create mode 100644 lib/meatloaf/disk/x64.h create mode 100644 lib/meatloaf/disk/z64.h create mode 100644 lib/meatloaf/file/p00.cpp create mode 100644 lib/meatloaf/file/p00.h create mode 100644 lib/meatloaf/file/prg.h create mode 100644 lib/meatloaf/file/vsf.h create mode 100644 lib/meatloaf/iec_pipe.h create mode 100644 lib/meatloaf/link/url.h create mode 100644 lib/meatloaf/link/webloc.h create mode 100644 lib/meatloaf/loaders/koa.h create mode 100644 lib/meatloaf/loaders/psid.h create mode 100644 lib/meatloaf/loaders/sid.h create mode 100644 lib/meatloaf/make_unique.h create mode 100644 lib/meatloaf/meat_buffer.h create mode 100644 lib/meatloaf/meat_io.cpp create mode 100644 lib/meatloaf/meat_io.h create mode 100644 lib/meatloaf/meat_stream.h create mode 100755 lib/meatloaf/network/afp.h create mode 100644 lib/meatloaf/network/codenet.h create mode 100755 lib/meatloaf/network/fsp.h create mode 100755 lib/meatloaf/network/ftp.h create mode 100644 lib/meatloaf/network/http.cpp create mode 100755 lib/meatloaf/network/http.h create mode 100644 lib/meatloaf/network/ipfs.cpp create mode 100644 lib/meatloaf/network/ipfs.h create mode 100644 lib/meatloaf/network/nc.h create mode 100755 lib/meatloaf/network/nfs.h create mode 100755 lib/meatloaf/network/sftp.h create mode 100755 lib/meatloaf/network/smb.h create mode 100644 lib/meatloaf/network/tcp.h create mode 100755 lib/meatloaf/network/telnet.h create mode 100755 lib/meatloaf/network/tftp.h create mode 100644 lib/meatloaf/network/tnfs.cpp create mode 100644 lib/meatloaf/network/tnfs.h create mode 100644 lib/meatloaf/network/webdav.cpp create mode 100755 lib/meatloaf/network/webdav.h create mode 100644 lib/meatloaf/network/ws.h create mode 100644 lib/meatloaf/network/wss.h create mode 100644 lib/meatloaf/scanners/virus.h create mode 100644 lib/meatloaf/service/cs.cpp create mode 100644 lib/meatloaf/service/cs.h create mode 100644 lib/meatloaf/service/gdrive.h create mode 100644 lib/meatloaf/service/ml.h create mode 100644 lib/meatloaf/tape/htap.h create mode 100644 lib/meatloaf/tape/t64.cpp create mode 100644 lib/meatloaf/tape/t64.h create mode 100644 lib/meatloaf/tape/tap.cpp create mode 100644 lib/meatloaf/tape/tap.h create mode 100644 lib/meatloaf/tape/tcrt.cpp create mode 100644 lib/meatloaf/tape/tcrt.h create mode 100644 lib/meatloaf/wrappers/directory_stream.h create mode 100644 lib/meatloaf/wrappers/iec_buffer.cpp create mode 100644 lib/meatloaf/wrappers/iec_buffer.h create mode 100644 lib/utils/U8Char.cpp create mode 100644 lib/utils/U8Char.h create mode 100644 lib/utils/endianness.h create mode 100644 lib/utils/peoples_url_parser.cpp create mode 100644 lib/utils/peoples_url_parser.h create mode 100644 lib/utils/punycode.cpp create mode 100644 lib/utils/punycode.h create mode 100644 lib/utils/string_utils.cpp create mode 100644 lib/utils/string_utils.h create mode 100644 modify_path.py create mode 100644 platformio.ini create mode 100644 src/main.cpp create mode 100755 src/test_archive.cpp create mode 100755 src/test_archive.h create mode 100644 src/test_meatloaf_filesystems.cpp create mode 100644 src/test_meatloaf_filesystems.h create mode 100644 src/test_parsers.cpp create mode 100644 src/test_parsers.h create mode 100644 src/test_punycode.cpp create mode 100644 src/test_punycode.h create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..477dc94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.pio +.vscode +bin \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6a76671 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16.0) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +list(APPEND COMPONENT_DIRS components) +project(Meatloaf_idf) \ No newline at end of file diff --git a/deploy.py b/deploy.py new file mode 100644 index 0000000..255ddea --- /dev/null +++ b/deploy.py @@ -0,0 +1,40 @@ +# Part of ESPEasy build toolchain. +# +# Combines separate bin files with their respective offsets into a single file +# This single file must then be flashed to an ESP32 node with 0 offset. +# +# Original implementation: Bartłomiej Zimoń (@uzi18) +# Maintainer: Gijs Noorlander (@TD-er) +# +# Special thanks to @Jason2866 (Tasmota) for helping debug flashing to >4MB flash +# Thanks @jesserockz (esphome) for adapting to use esptool.py with merge_bin +# +# Typical layout of the generated file: +# Offset | File +# - 0x1000 | ~\.platformio\packages\framework-arduinoespressif32\tools\sdk\esp32\bin\bootloader_dout_40m.bin +# - 0x8000 | ~\ESPEasy\.pio\build\\partitions.bin +# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin +# - 0x10000 | ~\ESPEasy\.pio\build\/.bin + +Import("env") + +platform = env.PioPlatform() + +import os, stat, shutil + + +# Create the 'bin' folder if it doesn't exist +if not os.path.exists('bin'): + os.makedirs('bin') + +def export_bin(source, target, env): + program = env.subst("$BUILD_DIR/${PROGNAME}") + new_file_name = f"bin/libMeatloaf" + if (os.path.exists(new_file_name)): + os.remove(new_file_name) + print(f"Exporting [{program}] to [{new_file_name}]") + shutil.copyfile(program, new_file_name) + os.chmod(new_file_name, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.system(new_file_name) + +env.AddPostAction("$BUILD_DIR/${PROGNAME}", export_bin) diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/ansi_codes.h b/include/ansi_codes.h new file mode 100644 index 0000000..24b6b0a --- /dev/null +++ b/include/ansi_codes.h @@ -0,0 +1,131 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * For more information, please refer to + * + * https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a + * + */ + +/* + Color Codes, Escapes & Languages + + I was able to use colors in my terminal by using a variety of different escape values. + As the conversation implies above, different languages require different escapes, + furthermore; there are several different sequences that are implemented for ANSI escapes, + and they can vary quite a bit. Some escapes have a cleaner sequence than others. + Personally I like the \e way of writing an escape, as it is clean and simple. + However, I couldn't get it to work anywhere, save the BASH scripting language. + + Each escape works with its adjacent language + + \x1b 👉‍ Node.js + \x1b 👉‍ Node.js w/ TS + \033 👉‍ GNU Cpp + \033 👉‍ ANSI C + \e 👉‍ BASH +*/ + +//Regular text +#define ANSI_BLACK "\e[0;30m" +#define ANSI_RED "\e[0;31m" +#define ANSI_GREEN "\e[0;32m" +#define ANSI_YELLOW "\e[0;33m" +#define ANSI_BLUE "\e[0;34m" +#define ANSI_MAGENTA "\e[0;35m" +#define ANSI_CYAN "\e[0;36m" +#define ANSI_WHITE "\e[0;37m" + +//Regular bold text +#define ANSI_BLACK_BOLD "\e[1;30m" +#define ANSI_RED_BOLD "\e[1;31m" +#define ANSI_GREEN_BOLD "\e[1;32m" +#define ANSI_YELLOW_BOLD "\e[1;33m" +#define ANSI_BLUE_BOLD "\e[1;34m" +#define ANSI_MAGENTA_BOLD "\e[1;35m" +#define ANSI_CYAN_BOLD "\e[1;36m" +#define ANSI_WHITE_BOLD "\e[1;37m" + +//Regular dim text +#define ANSI_BLACK_DIM "\e[2;30m" +#define ANSI_RED_DIM "\e[2;31m" +#define ANSI_GREEN_DIM "\e[2;32m" +#define ANSI_YELLOW_DIM "\e[2;33m" +#define ANSI_BLUE_DIM "\e[2;34m" +#define ANSI_MAGENTA_DIM "\e[2;35m" +#define ANSI_CYAN_DIM "\e[2;36m" +#define ANSI_WHITE_DIM "\e[2;37m" + +//Regular italics text +#define ANSI_BLACK_ITALICS "\e[3;30m" +#define ANSI_RED_ITALICS "\e[3;31m" +#define ANSI_GREEN_ITALICS "\e[3;32m" +#define ANSI_YELLOW_ITALICS "\e[3;33m" +#define ANSI_BLUE_ITALICS "\e[3;34m" +#define ANSI_MAGENTA_ITALICS "\e[3;35m" +#define ANSI_CYAN_ITALICS "\e[3;36m" +#define ANSI_WHITE_ITALICS "\e[3;37m" + +//Regular underline text +#define ANSI_BLACK_UNDERLINE "\e[4;30m" +#define ANSI_RED_UNDERLINE "\e[4;31m" +#define ANSI_GREEN_UNDERLINE "\e[4;32m" +#define ANSI_YELLOW_UNDERLINE "\e[4;33m" +#define ANSI_BLUE_UNDERLINE "\e[4;34m" +#define ANSI_MAGENTA_UNDERLINE "\e[4;35m" +#define ANSI_CYAN_UNDERLINE "\e[4;36m" +#define ANSI_WHITE_UNDERLINE "\e[4;37m" + +//Regular reversed text +#define ANSI_BLACK_REVERSED "\e[7;30m" +#define ANSI_RED_REVERSED "\e[7;31m" +#define ANSI_GREEN_REVERSED "\e[7;32m" +#define ANSI_YELLOW_REVERSED "\e[7;33m" +#define ANSI_BLUE_REVERSED "\e[7;34m" +#define ANSI_MAGENTA_REVERSED "\e[7;35m" +#define ANSI_CYAN_REVERSED "\e[7;36m" +#define ANSI_WHITE_REVERSED "\e[7;37m" + +//Regular background +#define ANSI_BLACK_BACKGROUND "\e[40m" +#define ANSI_RED_BACKGROUND "\e[41m" +#define ANSI_GREEN_BACKGROUND "\e[42m" +#define ANSI_YELLOW_BACKGROUND "\e[43m" +#define ANSI_BLUE_BACKGROUND "\e[44m" +#define ANSI_MAGENTA_BACKGROUND "\e[45m" +#define ANSI_CYAN_BACKGROUND "\e[46m" +#define ANSI_WHITE_BACKGROUND "\e[47m" + +//High intensty background +#define ANSI_BLACK_HIGH_INTENSITY_BACKGROUND "\e[0;100m" +#define ANSI_RED_HIGH_INTENSITY_BACKGROUND "\e[0;101m" +#define ANSI_GREEN_HIGH_INTENSITY_BACKGROUND "\e[0;102m" +#define ANSI_YELLOW_HIGH_INTENSITY_BACKGROUND "\e[0;103m" +#define ANSI_BLUE_HIGH_INTENSITY_BACKGROUND "\e[0;104m" +#define ANSI_MAGENTA_HIGH_INTENSITY_BACKGROUND "\e[0;105m" +#define ANSI_CYAN_HIGH_INTENSITY_BACKGROUND "\e[0;106m" +#define ANSI_WHITE_HIGH_INTENSITY_BACKGROUND "\e[0;107m" + +//High intensty text +#define ANSI_BLACK_HIGH_INTENSITY "\e[0;90m" +#define ANSI_RED_HIGH_INTENSITY "\e[0;91m" +#define ANSI_GREEN_HIGH_INTENSITY "\e[0;92m" +#define ANSI_YELLOW_HIGH_INTENSITY "\e[0;93m" +#define ANSI_BLUE_HIGH_INTENSITY "\e[0;94m" +#define ANSI_MAGENTA_HIGH_INTENSITY "\e[0;95m" +#define ANSI_CYAN_HIGH_INTENSITY "\e[0;96m" +#define ANSI_WHITE_HIGH_INTENSITY "\e[0;97m" + +//Bold high intensity text +#define ANSI_BLACK_BOLD_HIGH_INTENSITY "\e[1;90m" +#define ANSI_RED_BOLD_HIGH_INTENSITY "\e[1;91m" +#define ANSI_GREEN_BOLD_HIGH_INTENSITY "\e[1;92m" +#define ANSI_YELLOW_BOLD_HIGH_INTENSITY "\e[1;93m" +#define ANSI_BLUE_BOLD_HIGH_INTENSITY "\e[1;94m" +#define ANSI_MAGENTA_BOLD_HIGH_INTENSITY "\e[1;95m" +#define ANSI_CYAN_BOLD_HIGH_INTENSITY "\e[1;96m" +#define ANSI_WHITE_BOLD_HIGH_INTENSITY "\e[1;97m" + +//Reset +#define ANSI_RESET "\e[0m" +#define ANSI_RESET_NL "\e[0m\r\n" \ No newline at end of file diff --git a/include/cbm_defines.h b/include/cbm_defines.h new file mode 100644 index 0000000..4fe8178 --- /dev/null +++ b/include/cbm_defines.h @@ -0,0 +1,209 @@ +// Meatloaf - A Commodore 64/128 multi-device emulator +// https://github.com/idolpx/meatloaf +// Copyright(C) 2020 James Johnston +// +// Meatloaf is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Meatloaf is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Meatloaf. If not, see . + + +#ifndef CBMDEFINES_H +#define CBMDEFINES_H + +#include + +// The base pointer of basic. +#define CBM_BASIC_START 0x0401 + +// 1541 RAM and ROM memory map definitions. +#define CBM_1541_RAM_OFFSET 0 +#define CBM_1541_RAM_SIZE (1024 * 2) +#define CBM_1541_VIA1_OFFSET 0x1800 +#define CBM_1541_VIA1_SIZE 0x10 +#define CBM_1541_VIA2_OFFSET 0x1C00 +#define CBM_1541_VIA2_SIZE 0x10 +#define CBM_1541_ROM_OFFSET 0xC000 +#define CBM_1541_ROM_SIZE (1024 * 16) + +// Back arrow character code. +#define CBM_DOLLAR_SIGN '$' +#define CBM_EXCLAMATION_MARKS "!!" + +#define CBM_ARROW_LEFT "\x5F" +#define CBM_ARROW_UP "\x5E" +#define CBM_CRSR_LEFT "\x9d" +#define CBM_DEL_DEL "\x14\x14" + +#define CBM_HOME "\x13" +#define CBM_CLEAR "\x93" +#define CBM_INSERT "\x94" +#define CBM_DELETE "\x14" +#define CBM_RETURN "\x0D" + +#define CBM_CURSOR_DOWN "\x11" +#define CBM_CURSOR_RIGHT "\x1D" +#define CBM_CURSOR_UP "\x91" +#define CBM_CURSOR_LEFT "\x9D" + +#define CBM_RUN "\x83" +#define CBM_STOP "\x03" + +#define CBM_WHITE "\x05" +#define CBM_RED "\x1C" +#define CBM_GREEN "\x1E" +#define CBM_BLUE "\x1F" +#define CBM_ORANGE "\x81" +#define CBM_BLACK "\x90" +#define CBM_BROWN "\x95" +#define CBM_PINK "\x96" +#define CBM_DARK_GREY "\x97" +#define CBM_GREY "\x98" +#define CBM_LIGHT_GREEN "\x99" +#define CBM_LIGHT_BLUE "\x9A" +#define CBM_LIGHT_GREY "\x9B" +#define CBM_PURPLE "\x9C" +#define CBM_YELLOW "\x9E" +#define CBM_CYAN "\x9F" + +#define CBM_REVERSE_ON "\x12" +#define CBM_REVERSE_OFF "\x92" + +#define CBM_CS_UPPPER "\x0E" +#define CBM_CS_GFX "\x8E" + +#define CBM_SCREEN_ROWS 25 +#define CBM_SCREEN_COLS 40 + +// Device OPEN channels. +// Special channels. +enum IECChannels +{ + CHANNEL_LOAD = 0, + CHANNEL_SAVE = 1, + CHANNEL_COMMAND = 15 +}; + +typedef enum +{ + ErrOK = 0, + ErrFilesScratched, // Files scratched response, not an error condition. + ErrBlockHeaderNotFound = 20, + ErrSyncCharNotFound, + ErrDataBlockNotFound, + ErrChecksumInData, + ErrByteDecoding, + ErrWriteVerify, + ErrWriteProtectOn, + ErrChecksumInHeader, + ErrDataExtendsNextBlock, + ErrDiskIdMismatch, + ErrSyntaxError, + ErrInvalidCommand, + ErrLongLine, + ErrInvalidFilename, + ErrNoFileGiven, // The file name was left out of a command or the DOS does not recognize it as such. + // Typically, a colon or equal character has been left out of the command + ErrCommandNotFound = 39, // This error may result if the command sent to command channel (secondary address 15) is unrecognizedby the DOS. + ErrRecordNotPresent = 50, + ErrOverflowInRecord, + ErrFileTooLarge, + ErrFileOpenForWrite = 60, + ErrFileNotOpen, + ErrFileNotFound, + ErrFileExists, + ErrFileTypeMismatch, + ErrNoBlock, + ErrIllegalTrackOrSector, + ErrIllegalSystemTrackOrSector, + ErrNoChannelAvailable = 70, + ErrDirectoryError, + ErrDiskFullOrDirectoryFull, + ErrIntro, // power up message or write attempt with DOS mismatch + ErrDriveNotReady, // typically in this emulation could also mean: not supported on this file system. + ErrSerialComm = 97, // something went sideways with serial communication to the file server. + ErrNotImplemented = 98, // The command or specific operation is not yet implemented in this device. + ErrUnknownError = 99, + ErrCount +} IOErrorMessage; + + +// BIT Flags +#define CLEAR 0x0000 // clear all flags +#define CLEAR_LOW 0xFF00 // clear low byte +#define ERROR (1 << 0) // if this flag is set, something went wrong +#define ATN_PULLED (1 << 1) // might be set by iec_receive +#define EOI_RECVD (1 << 2) +#define EMPTY_STREAM (1 << 3) +#define COMMAND_RECVD (1 << 4) + +// Detected Protocols +#define FAST_SERIAL_ACTIVE (1 << 8) +#define PARALLEL_ACTIVE (1 << 9) +#define SAUCEDOS_ACTIVE (1 << 10) +#define JIFFYDOS_ACTIVE (1 << 11) +#define WIC64_ACTIVE (1 << 13) + +// IEC protocol timing consts in microseconds (us) +// IEC-Disected p10-11 // Description // 1541 C64 min typical max // Notes +#define TIMEOUT_Tat 1000 // ATN RESPONSE (REQUIRED) - - 1000us (If maximum time exceeded, device not present error.) +#define TIMING_Th 60 // LISTENER HOLD-OFF 65us 39us 0 - infinte +#define TIMING_Tne 40 // NON-EOI RESPONSE TO RFD - 40us 200us (If maximum time exceeded, EOI response required.) +#define TIMEOUT_Tne 250 +#define TIMING_Ts 70 // BIT SET-UP TALKER 71us 20us 70us - +#define TIMING_Ts1 57 // BIT SET-UP LISTENER PRE 57us 47us +#define TIMING_Ts2 28 // BIT SET-UP LISTENER POST 28us 24us +#define TIMING_Tv 20 // DATA VALID VIC20 76us 26us 20us 20us - (Tv and Tpr minimum must be 60μ s for external device to be a talker. ) +#define TIMING_Tv64 76 // DATA VALID C64 +#define TIMING_Tf 45 // FRAME HANDSHAKE 0 20us 1000us (If maximum time exceeded, frame error.) +#define TIMEOUT_Tf 1000 +#define TIMING_Tr 20 // FRAME TO RELEASE OF ATN 20us - - +#define TIMING_Tbb 100 // BETWEEN BYTES TIME 100us - - +#define TIMING_Tye 250 // EOI RESPONSE TIME 200us 250us - +#define TIMING_Tei 60 // EOI RESPONSE HOLD TIME 60us - - (Tei minimum must be 80μ s for external device to be a listener.) +#define TIMING_Try 30 // TALKER RESPONSE LIMIT 0 30us 60us +#define TIMEOUT_Try 60 +#define TIMING_Tpr 60 // BYTE-ACKNOWLEDGE 20us 30us - (Tv and Tpr minimum must be 60μ s for external device to be a talker.) +#define TIMING_Ttk 20 // TALK-ATTENTION RELEASE 20us 20us 30us 100us +#define TIMEOUT_Ttk 100 +#define TIMING_Tdc 20 // TALK-ATTENTION ACKNOWLEDGE 20us 0 - - +#define TIMING_Tda 80 // TALK-ATTENTION ACK. HOLD 80us - - +#define TIMING_Tfr 60 // EOI ACKNOWLEDGE 60us - - + +#define TIMING_EMPTY 512 // SIGNAL EMPTY STREAM +#define TIMING_SYNC 100 // SYNC WITH ATN +#define TIMING_STABLE 20 // WAIT FOR BUS TO BE STABLE +#define TIMING_DELAY 70 // DELAY AFTER ATN + +#define TIMING_VIC20_DETECT 40 // VIC20 DETECTED WHEN HOST BIT TIME IS LESS THAN 40us +#define TIMING_PROTOCOL_DETECT 218 // SAUCEDOS/JIFFYDOS CAPABLE DELAY +#define TIMING_PROTOCOL_ACK 101 // SAUCEDOS/JIFFYDOS ACK RESPONSE + +// See timeoutWait +#define TIMEOUT_DEFAULT 1000 // 1ms +#define TIMED_OUT -1 +#define FOREVER 5000000 // 0 + +#ifndef IEC_INVERTED_LINES +// Not Inverted +#define PULLED true +#define RELEASED false +#define LOW 0x00 +#define HIGH 0x01 +#else +// Inverted +#define PULLED false +#define RELEASED true +#define LOW 0x01 +#define HIGH 0x00 +#endif + +#endif // CBMDEFINES_H diff --git a/include/debug.h b/include/debug.h new file mode 100644 index 0000000..3b33341 --- /dev/null +++ b/include/debug.h @@ -0,0 +1,48 @@ +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#include + +#include "ansi_codes.h" + +// __PLATFORMIO_BUILD_DEBUG__ is set when build_type is set to debug in platformio.ini +#if defined(__PLATFORMIO_BUILD_DEBUG__) || defined(DBUG2) +#define DEBUG +#endif + +#ifdef UNIT_TESTS +#undef DEBUG +#endif + +#ifndef TEST_NATIVE +#include "../lib/hardware/fnUART.h" +#define Serial fnUartDebug +#else +#define Serial.printf printf +#endif + +/* + Debugging Macros +*/ +#ifdef DEBUG +#ifdef TEST_NATIVE + #define Debug_print(...) printf( __VA_ARGS__ ) + #define Debug_printf(...) printf( __VA_ARGS__ ) + #define Debug_println(...) printf( __VA_ARGS__ ) + #define Debug_printv(format, ...) {printf( ANSI_YELLOW "[%s:%u] %s(): " ANSI_GREEN_BOLD format ANSI_RESET "\r\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);} +#else + #define Debug_print(...) fnUartDebug.print( __VA_ARGS__ ) + #define Debug_printf(...) fnUartDebug.printf( __VA_ARGS__ ) + #define Debug_println(...) fnUartDebug.println( __VA_ARGS__ ) + #define Debug_printv(format, ...) {fnUartDebug.printf( ANSI_YELLOW "[%s:%u] %s(): " ANSI_GREEN_BOLD format ANSI_RESET "\r\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__);} +#endif + #define HEAP_CHECK(x) Debug_printf("HEAP CHECK %s " x "\r\n", heap_caps_check_integrity_all(true) ? "PASSED":"FAILED") +#else + #define Debug_print(...) + #define Debug_printf(...) + #define Debug_println(...) + #define Debug_printv(format, ...) + #define HEAP_CHECK(x) +#endif + +#endif // _DEBUG_H_ diff --git a/lib/EdUrlParser/EdUrlParser.cpp b/lib/EdUrlParser/EdUrlParser.cpp new file mode 100644 index 0000000..dd2bf8f --- /dev/null +++ b/lib/EdUrlParser/EdUrlParser.cpp @@ -0,0 +1,291 @@ +/* + * EdUrlParser.cpp + * + * Created on: Nov 25, 2014 + * Author: netmind + */ + + +#include "EdUrlParser.h" + + +#define CHECK_LEN_END(POS, LEN) if(POS>=LEN) {_url_errorno=100;goto __PARSE_END;} +#define WALK_SP(POS, LEN, BUF) for(;POS= len) + goto __PARSE_END; + if (buf[pos] == '%') { + CHECK_REMAIN_END(pos, len, 3); + + char c; + if(false == EdUrlParser::toChar(buf + pos + 1, &c)) + { + _url_errorno = 200; + goto __PARSE_END; + } + + decstr.push_back(c); + pos += 3; + per = pos; + + if (pos >= len) + goto __PARSE_END; + } else if (buf[pos] == '+') { + decstr.push_back(' '); + pos++; + per = pos; + } + } + __PARSE_END: if (_url_errorno != 0) + return ""; + return decstr; + +} + +string EdUrlParser::urlEncode(const string &s) { + const char *ptr = s.c_str(); + string enc; + char c; + char phex[3] = { '%' }; + for (size_t i = 0; i < s.size(); i++) { + c = ptr[i]; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '*' + || c == '.') { + enc.push_back(c); + } else if (c == ' ') { + enc.push_back('+'); + } else { + toHex(phex + 1, c); + enc.append(phex, 0, 3); + } + } + return enc; +} + +void EdUrlParser::toHex(char* desthex, char c) { + static char hextable[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + desthex[0] = hextable[c >> 4]; + desthex[1] = hextable[c & 0x0f]; +} + +// #prgamas are to ignore warnings about variables being set and not used +//__BEGIN_IGNORE_UNUSEDVARS +int EdUrlParser::parsePath(vector* folders, string pathstr) { + int _url_errorno = 0; + int path_pos = 0; + size_t pos = 0; + size_t len = pathstr.size(); + const char* str = pathstr.c_str(); + string name; + + for (pos = 0;;) { + WALK_CHAR(pos, str, '/'); + path_pos = pos; + CHECK_LEN_END(pos, len); + WALK_UNTIL(pos, len, str, '/'); + name = pathstr.substr(path_pos, pos - path_pos); + folders->push_back(name); + } + __PARSE_END: return folders->size(); +} +//__END_IGNORE_UNUSEDVARS + +//__BEGIN_IGNORE_UNUSEDVARS +void EdUrlParser::parse() { + int _url_errorno = 0; + const char *str = mRawUrl.c_str(); + + size_t pos, len; + int scheme_pos, host_pos, port_pos, path_pos, param_pos, tag_pos; + pos = 0; + len = mRawUrl.size(); + WALK_SP(pos, len, str); // remove preceding spaces. + if (str[pos] == '/') { + goto __PARSE_HOST; + } + + // start protocol scheme + scheme_pos = pos; + WALK_UNTIL(pos, len, str, ':'); + CHECK_LEN_END(pos, len); + scheme = mRawUrl.substr(scheme_pos, pos - scheme_pos); + CHECK_REMAIN_END(pos, len, 3); + WALK_CHAR(pos, str, ':'); + WALK_CHAR(pos, str, '/'); + + // start host address + __PARSE_HOST: + WALK_CHAR(pos, str, '/'); + host_pos = pos; + WALK_UNTIL3(pos, len, str, ':', '/', '?'); + if (pos < len) { + hostName = mRawUrl.substr(host_pos, pos - host_pos); + if (str[pos] == ':') + goto __PARSE_PORT; + if (str[pos] == '/') + goto __PARSE_PATH; + if (str[pos] == '?') + goto __PARSE_PARAM; + } else { + hostName = mRawUrl.substr(host_pos, pos - host_pos); + } + + __PARSE_PORT: + WALK_CHAR(pos, str, ':'); + port_pos = pos; + WALK_UNTIL2(pos, len, str, '/', '?'); + port = mRawUrl.substr(port_pos, pos - port_pos); + CHECK_LEN_END(pos, len); + if (str[pos] == '?') + goto __PARSE_PARAM; + __PARSE_PATH: path_pos = pos; + WALK_UNTIL(pos, len, str, '?'); + path = mRawUrl.substr(path_pos, pos - path_pos); + CHECK_LEN_END(pos, len); + __PARSE_PARAM: + WALK_CHAR(pos, str, '?'); + param_pos = pos; + WALK_UNTIL(pos, len, str, '#'); + query = mRawUrl.substr(param_pos, pos - param_pos); + CHECK_LEN_END(pos, len); + + // start parsing fragment + WALK_CHAR(pos, str, '#'); + tag_pos = pos; + fragment = mRawUrl.substr(tag_pos, len - tag_pos); + __PARSE_END: return; +} +//__END_IGNORE_UNUSEDVARS + +EdUrlParser* EdUrlParser::parseUrl(const string &urlstr) { + EdUrlParser *url = new EdUrlParser; + url->mRawUrl = urlstr; + url->parse(); + return url; +} + +bool EdUrlParser::toChar(const char* hex, char *result) { + unsigned char nible[2]; + unsigned char c, base; + for (int i = 0; i < 2; i++) { + c = hex[i]; + if (c >= '0' && c <= '9') { + base = '0'; + } else if (c >= 'A' && c <= 'F') { + base = 'A' - 10; + } else if (c >= 'a' && c <= 'f') { + base = 'a' - 10; + } else { + return false; + } + nible[i] = c - base; + } + *result = ((nible[0] << 4) | nible[1]); + return true; +} + +size_t EdUrlParser::parseKeyValueMap(unordered_map *kvmap, const string &rawstr, bool strict) { + return parseKeyValue(rawstr, __kv_callback_map, kvmap, strict); +} + +size_t EdUrlParser::parseKeyValueList(vector< query_kv_t > *kvvec, const string &rawstr, bool strict) { + return parseKeyValue(rawstr, __kv_callback_vec, kvvec, strict); +} + +size_t EdUrlParser::parseKeyValue(const string &rawstr, __kv_callback kvcb, void* obj, bool strict) { + + int _url_errorno = 0; + const char *str = rawstr.c_str(); + size_t pos, len, item_len; + pos = 0; + len = rawstr.size(); + + string key, val; + size_t key_pos; + WALK_SP(pos, len, str); + CHECK_LEN_END(pos, len); + key_pos = pos; + item_len = 0; + for(;;) { + WALK_UNTIL2(pos, len, str, '=', '&'); + if(pos >= len || str[pos] == '&') { + // Be careful for boundary check error to be caused. !!! + // *** Do not access str[] any more in this block. !!! + + val = rawstr.substr(key_pos, pos-key_pos); + + if(strict == true) { + if(key.empty() == false && val.empty()==false) { + kvcb(obj, key, val); + item_len++; + } + } else if(!(key.empty()==true && val.empty()==true)){ + kvcb(obj, key, val); + item_len++; + } + + key.clear();val.clear(); + if(pos >= len) goto __PARSE_END; + pos++; + key_pos = pos; + } + else if(str[pos] == '=') { + key = rawstr.substr(key_pos, pos-key_pos); + pos++; + key_pos = pos; + } + } + __PARSE_END: + if(_url_errorno != 0 ) + return -1; + return item_len; +} + + +int __kv_callback_map(void* list, string k, string v) { + auto *map = (unordered_map*)list; + (*map)[k] = v; + return map->size(); +} + +int __kv_callback_vec(void* list, string k, string v) { + auto *vec = (vector*)list; + query_kv_t t ={k, v}; + vec->push_back(t); + return vec->size(); +} + +bool EdUrlParser::isValidUrl() +{ + return !scheme.empty() && !(path.empty() && port.empty()); +} \ No newline at end of file diff --git a/lib/EdUrlParser/EdUrlParser.h b/lib/EdUrlParser/EdUrlParser.h new file mode 100644 index 0000000..1b8c7e5 --- /dev/null +++ b/lib/EdUrlParser/EdUrlParser.h @@ -0,0 +1,55 @@ +/* + * EdUrlParser.h + * + * Created on: Nov 25, 2014 + * Author: netmind + */ + +#ifndef EDURLPARSER_H_ +#define EDURLPARSER_H_ + +#include +#include +#include +#include + +using namespace std; + +typedef struct { + string key; + string val; +} query_kv_t; + +typedef int (*__kv_callback)(void* list, string k, string v); + +class EdUrlParser { +private: + EdUrlParser(); +public: + virtual ~EdUrlParser(); + static EdUrlParser* parseUrl(const string &urlstr); + static int parsePath(vector *pdirlist, string pathstr); + static string urlDecode(const string &str); + static bool toChar(const char* hex, char *result); + static string urlEncode(const string &s); + static void toHex(char *desthex, char c); + static size_t parseKeyValueMap(unordered_map *kvmap, const string &str, bool strict=true); + static size_t parseKeyValueList(vector< query_kv_t > *kvmap, const string &rawstr, bool strict=true); + static size_t parseKeyValue(const string &rawstr, __kv_callback kvcb, void* obj, bool strict); + bool isValidUrl(); + +private: + void parse(); + +public: + string mRawUrl; + string scheme; + string hostName; + string port; + string path; + string query; + string fragment; + string toString() { return scheme + "://" + hostName + (port.empty() ? "" : (":" + port)) + path + (query.empty() ? "" : "?" + query) + (fragment.empty() ? "" : "#" + fragment); } +}; + +#endif /* EDURLPARSER_H_ */ \ No newline at end of file diff --git a/lib/bus/bus.h b/lib/bus/bus.h new file mode 100644 index 0000000..b352668 --- /dev/null +++ b/lib/bus/bus.h @@ -0,0 +1,71 @@ +#ifndef BUS_H +#define BUS_H + +#ifdef BUILD_ATARI +#include "sio/sio.h" +#define SYSTEM_BUS SIO +#endif + +#ifdef BUILD_IEC +#include "iec/iec.h" +// #include "iec/parallel.h" +// #include "iec/ieee-488.h" +#define SYSTEM_BUS IEC +#endif + +#ifdef BUILD_ADAM +#include "adamnet/adamnet.h" +#define SYSTEM_BUS AdamNet +#endif + +#ifdef BUILD_LYNX +#include "comlynx/comlynx.h" +#define SYSTEM_BUS ComLynx +#endif + +#ifdef NEW_TARGET +#include "new/adamnet.h" +#define SYSTEM_BUS AdamNet +#endif + +#ifdef BUILD_APPLE +#include "iwm/iwm.h" +#define SYSTEM_BUS IWM +#endif + +#ifdef BUILD_MAC +#include "mac/mac.h" +#define SYSTEM_BUS MAC +#endif + +#ifdef BUILD_S100 +#include "s100spi/s100spi.h" +#define SYSTEM_BUS s100Bus +#endif + +#ifdef BUILD_RS232 +#include "rs232/rs232.h" +#define SYSTEM_BUS RS232 +#endif + +#ifdef BUILD_CX16 +#include "cx16_i2c/cx16_i2c.h" +#define SYSTEM_BUS CX16 +#endif + +#ifdef BUILD_RC2014 +#include "rc2014bus/rc2014bus.h" +#define SYSTEM_BUS rc2014Bus +#endif + +#ifdef BUILD_H89 +#include "h89/h89.h" +#define SYSTEM_BUS H89Bus +#endif + +#ifdef BUILD_COCO +#include "drivewire/drivewire.h" +#define SYSTEM_BUS DRIVEWIRE +#endif + +#endif // BUS_H diff --git a/lib/bus/iec/iec.cpp b/lib/bus/iec/iec.cpp new file mode 100644 index 0000000..a5f03e7 --- /dev/null +++ b/lib/bus/iec/iec.cpp @@ -0,0 +1,980 @@ +//#ifdef BUILD_IEC + +#include "iec.h" + +#include +#include + +#include "../../include/debug.h" +//#include "../../include/pinmap.h" +#include "../../include/cbm_defines.h" +//#include "led.h" +//#include "led_strip.h" +//#include "protocol/cpbstandardserial.h" +#include "string_utils.h" +//#include "utils.h" + +//using namespace Protocol; + +systemBus IEC; + +static void cbm_on_attention_isr_handler(void *arg) +{ + // systemBus *b = (systemBus *)arg; + + // //b->pull(PIN_IEC_SRQ); + + // // Go to listener mode and get command + // b->release(PIN_IEC_CLK_OUT); + // b->pull(PIN_IEC_DATA_OUT); + + // b->flags = CLEAR; + // b->flags |= ATN_PULLED; + // b->bus_state = BUS_ACTIVE; + + // //b->release(PIN_IEC_SRQ); +} + +/** + * Static callback function for the interrupt rate limiting timer. It sets the interruptProceed + * flag to true. This is set to false when the interrupt is serviced. + */ +static void onTimer(void *info) +{ + // systemBus *parent = (systemBus *)info; + // //portENTER_CRITICAL_ISR(&parent->timerMux); + // parent->interruptSRQ = !parent->interruptSRQ; + // //portEXIT_CRITICAL_ISR(&parent->timerMux); +} + +static void ml_iec_intr_task(void* arg) +{ + // while ( true ) + // { + // // if ( IEC.enabled ) + // // { + // IEC.service(); + // if ( IEC.bus_state < BUS_ACTIVE ) + // taskYIELD(); + // // } + // } +} + +void init_pin(int pin) +{ + // gpio_set_direction(pin, GPIO_MODE_INPUT); + // gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY); + // gpio_set_level(pin, LOW); + // return; +} + + +// true => PULL => LOW +void systemBus::pull ( int _pin ) +{ + // int _reg = GPIO_ENABLE_REG; + // if (_pin > 31) + // { + // _reg = GPIO_ENABLE1_REG, _pin -= 32; + // } + // REG_SET_BIT(_reg, 1 << _pin); // GPIO_MODE_OUTPUT +} + +// false => RELEASE => HIGH +void systemBus::release ( int _pin ) +{ + // int _reg = GPIO_ENABLE_REG; + // if (_pin > 31) + // { + // _reg = GPIO_ENABLE1_REG, _pin -= 32; + // } + // REG_CLR_BIT(_reg, 1 << _pin); // GPIO_MODE_INPUT +} + +bool systemBus::status ( int _pin ) +{ +// #ifndef IEC_SPLIT_LINES +// release ( _pin ); +// #endif + +// return gpio_get_level ( ( gpio_num_t ) _pin ) ? RELEASED : PULLED; +} + +// int IRAM_ATTR systemBus::status() +// { +// int data = 0; + +// gpio_config_t io_config = +// { +// .pin_bit_mask = BIT(PIN_IEC_CLK_IN) | BIT(PIN_IEC_DATA_IN) | BIT(PIN_IEC_SRQ) | BIT(PIN_IEC_RESET), +// .mode = GPIO_MODE_INPUT, +// .pull_up_en = GPIO_PULLUP_ENABLE, +// .intr_type = GPIO_INTR_DISABLE, +// }; + +// ESP_ERROR_CHECK(gpio_config(&io_config)); +// uint64_t io_data = REG_READ(GPIO_IN_REG); + +// //data |= (0 & (io_data & (1 << PIN_IEC_ATN))); +// data |= (1 & (io_data & (1 << PIN_IEC_CLK_IN))); +// data |= (2 & (io_data & (1 << PIN_IEC_DATA_IN))); +// data |= (3 & (io_data & (1 << PIN_IEC_SRQ))); +// data |= (4 & (io_data & (1 << PIN_IEC_RESET))); + +// return data; +// } + +void systemBus::setup() +{ +// Debug_printf("IEC systemBus::setup()\r\n"); + +// flags = CLEAR; +// protocol = selectProtocol(); +// release(PIN_IEC_CLK_OUT); +// release(PIN_IEC_DATA_OUT); +// release(PIN_IEC_SRQ); + +// // initial pin modes in GPIO +// init_pin(PIN_IEC_ATN); +// init_pin(PIN_IEC_CLK_OUT); +// init_pin(PIN_IEC_DATA_OUT); +// init_pin(PIN_IEC_SRQ); +// #ifdef IEC_HAS_RESET +// init_pin(PIN_IEC_RESET); +// #endif + +// // Start task +// //xTaskCreate(ml_iec_intr_task, "ml_iec_intr_task", 2048, NULL, 10, NULL); +// xTaskCreatePinnedToCore(ml_iec_intr_task, "ml_iec_intr_task", 4096, NULL, 20, NULL, 1); + +// // Setup interrupt for ATN +// gpio_config_t io_conf = { +// .pin_bit_mask = ( 1ULL << PIN_IEC_ATN ), // bit mask of the pins that you want to set +// .mode = GPIO_MODE_INPUT, // set as input mode +// .pull_up_en = GPIO_PULLUP_DISABLE, // disable pull-up mode +// .pull_down_en = GPIO_PULLDOWN_DISABLE, // disable pull-down mode +// .intr_type = GPIO_INTR_NEGEDGE // interrupt of falling edge +// }; +// //configure GPIO with the given settings +// gpio_config(&io_conf); +// gpio_isr_handler_add((gpio_num_t)PIN_IEC_ATN, cbm_on_attention_isr_handler, this); + +// // Start SRQ timer service +// timer_start(); +} + + +void systemBus::service() +{ +// // pull( PIN_IEC_SRQ ); + +// // Disable Interrupt +// // gpio_intr_disable((gpio_num_t)PIN_IEC_ATN); + +// // TODO IMPLEMENT + +// if (bus_state < BUS_ACTIVE) +// { +// // Handle SRQ for devices +// for (auto devicep : _daisyChain) +// { +// for (unsigned char i=0;i<16;i++) +// devicep->poll_interrupt(i); +// } + +// return; +// } + +// #ifdef IEC_HAS_RESET + +// // Check if CBM is sending a reset (setting the RESET line high). This is typically +// // when the CBM is reset itself. In this case, we are supposed to reset all states to initial. +// bool pin_reset = status(PIN_IEC_RESET); +// if (pin_reset == PULLED) +// { +// if (status(PIN_IEC_ATN) == PULLED) +// { +// // If RESET & ATN are both PULLED then CBM is off +// bus_state = BUS_OFFLINE; +// // gpio_intr_enable((gpio_num_t)PIN_IEC_ATN); +// return; +// } + +// Debug_printf("IEC Reset! reset[%d]\r\n", pin_reset); +// data.init(); // Clear bus data +// releaseLines(); +// bus_state = BUS_IDLE; +// Debug_printv("bus init"); + +// // Reset virtual devices +// reset_all_our_devices(); +// // gpio_intr_enable((gpio_num_t)PIN_IEC_ATN); +// return; +// } + +// #endif + +// // Command or Data Mode +// do +// { +// // Exit if bus is offline +// if (bus_state == BUS_OFFLINE) +// break; + +// if (bus_state == BUS_ACTIVE) +// { +// pull ( PIN_IEC_SRQ ); + +// //release ( PIN_IEC_CLK_OUT ); +// //pull ( PIN_IEC_DATA_OUT ); + +// //flags = CLEAR; + +// // Read bus command bytes +// //Debug_printv("command"); +// read_command(); + +// release ( PIN_IEC_SRQ ); +// } + +// if (bus_state == BUS_PROCESS) +// { +// // Sometimes ATN isn't released immediately. Wait for ATN to be +// // released before trying to read payload. Long ATN delay (>1.5ms) +// // seems to occur more frequently with VIC-20. +// // protocol->timeoutWait(PIN_IEC_ATN, RELEASED, FOREVER, false); + +// //Debug_printv("data"); +// pull ( PIN_IEC_SRQ ); +// if (data.secondary == IEC_OPEN || data.secondary == IEC_REOPEN) +// { +// // Switch to detected protocol +// //pull ( PIN_IEC_SRQ ); +// protocol = selectProtocol(); +// //release ( PIN_IEC_SRQ ); +// } + +// // Data Mode - Get Command or Data +// if (data.primary == IEC_LISTEN) +// { +// //Debug_printv("calling deviceListen()\r\n"); +// deviceListen(); +// } +// else if (data.primary == IEC_TALK) +// { +// //Debug_printv("calling deviceTalk()\r\n"); +// deviceTalk(); +// } + +// // Queue control codes and command in specified device +// device_state_t device_state = deviceById(data.device)->queue_command(data); + +// fnLedManager.set(eLed::LED_BUS, true); + +// //Debug_printv("bus[%d] device[%d]", bus_state, device_state); +// device_state = deviceById(data.device)->process(); +// if ( device_state < DEVICE_ACTIVE ) +// { +// // for (auto devicep : _daisyChain) +// // { +// // if ( devicep->device_state > DEVICE_IDLE ) +// // devicep->process(); +// // } +// data.init(); +// //Debug_printv("bus init"); +// } + +// //Debug_printv("bus[%d] device[%d] flags[%d]", bus_state, device_state, flags); +// bus_state = BUS_IDLE; + +// // Switch back to standard serial +// detected_protocol = PROTOCOL_SERIAL; +// protocol = selectProtocol(); +// release ( PIN_IEC_SRQ ); +// } + +// if ( status ( PIN_IEC_ATN ) ) +// bus_state = BUS_ACTIVE; + +// } while( bus_state > BUS_IDLE ); + +// // Cleanup and Re-enable Interrupt +// releaseLines(); +// //gpio_intr_enable((gpio_num_t)PIN_IEC_ATN); + +// //Debug_printv ( "primary[%.2X] secondary[%.2X] bus[%d] flags[%d]", data.primary, data.secondary, bus_state, flags ); +// //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); + +// Debug_printv("exit"); +// Debug_printv("bus[%d] flags[%d]", bus_state, flags); +// //release( PIN_IEC_SRQ ); + +// //fnLedStrip.stopRainbow(); +// //fnLedManager.set(eLed::LED_BUS, false); +} + +void systemBus::read_command() +{ +// // ATN was pulled, read bus command bytes +// // Sometimes the C64 pulls ATN but doesn't pull CLOCK right away +// //pull( PIN_IEC_SRQ ); +// if ( protocol->timeoutWait ( PIN_IEC_CLK_IN, PULLED, TIMING_DELAY, false ) == TIMED_OUT ) +// { +// Debug_printv ( "ATN/Clock delay" ); +// return; // return error because timeout +// } +// //release( PIN_IEC_SRQ ); + +// //pull( PIN_IEC_SRQ ); +// uint8_t c = receiveByte(); +// //release( PIN_IEC_SRQ ); + +// // Check for error +// if ( flags & ERROR ) +// { +// Debug_printv("Error reading command. flags[%d]", flags); +// if (c == 0xFFFFFFFF) +// bus_state = BUS_OFFLINE; +// else +// bus_state = BUS_ERROR; +// } +// else if ( flags & EMPTY_STREAM) +// { +// bus_state = BUS_IDLE; +// } +// else +// { +// if ( flags & JIFFYDOS_ACTIVE ) +// { +// Debug_printf(" IEC: [JD][%.2X]", c); +// detected_protocol = PROTOCOL_JIFFYDOS; +// } +// else +// { +// Debug_printf(" IEC: [%.2X]", c); +// } + +// // Decode command byte +// uint8_t command = c & 0x60; +// if (c == IEC_UNLISTEN) +// command = IEC_UNLISTEN; +// if (c == IEC_UNTALK) +// command = IEC_UNTALK; + +// //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); +// //Debug_printv ("command[%.2X]", command); + +// switch (command) +// { +// // case IEC_GLOBAL: +// // data.primary = IEC_GLOBAL; +// // data.device = c ^ IEC_GLOBAL; +// // bus_state = BUS_IDLE; +// // Debug_printf(" (00 GLOBAL %.2d COMMAND)\r\n", data.device); +// // break; + +// case IEC_LISTEN: +// data.primary = IEC_LISTEN; +// data.device = c ^ IEC_LISTEN; +// data.secondary = IEC_REOPEN; // Default secondary command +// data.channel = CHANNEL_COMMAND; // Default channel +// data.payload = ""; +// bus_state = BUS_ACTIVE; +// Debug_printf(" (20 LISTEN %.2d DEVICE)\r\n", data.device); +// break; + +// case IEC_UNLISTEN: +// data.primary = IEC_UNLISTEN; +// bus_state = BUS_PROCESS; +// Debug_printf(" (3F UNLISTEN)\r\n"); +// break; + +// case IEC_TALK: +// data.primary = IEC_TALK; +// data.device = c ^ IEC_TALK; +// data.secondary = IEC_REOPEN; // Default secondary command +// data.channel = CHANNEL_COMMAND; // Default channel +// bus_state = BUS_ACTIVE; +// Debug_printf(" (40 TALK %.2d DEVICE)\r\n", data.device); +// break; + +// case IEC_UNTALK: +// data.primary = IEC_UNTALK; +// data.secondary = 0x00; +// bus_state = BUS_PROCESS; +// Debug_printf(" (5F UNTALK)\r\n"); +// break; + +// default: + +// //pull ( PIN_IEC_SRQ ); +// std::string secondary; +// bus_state = BUS_PROCESS; + +// command = c & 0xF0; +// switch ( command ) +// { +// case IEC_OPEN: +// data.secondary = IEC_OPEN; +// data.channel = c ^ IEC_OPEN; +// secondary = "OPEN"; +// break; + +// case IEC_REOPEN: +// data.secondary = IEC_REOPEN; +// data.channel = c ^ IEC_REOPEN; +// secondary = "DATA"; +// break; + +// case IEC_CLOSE: +// data.secondary = IEC_CLOSE; +// data.channel = c ^ IEC_CLOSE; +// secondary = "CLOSE"; +// break; + +// default: +// bus_state = BUS_IDLE; +// } + +// // *** IMPORTANT! This helps keep us in sync! +// protocol->wait( TIMING_SYNC, false); +// //release ( PIN_IEC_SRQ ); + +// Debug_printf(" (%.2X %s %.2d CHANNEL)\r\n", data.secondary, secondary.c_str(), data.channel); +// } +// } + +// // Is this command for us? +// if ( !isDeviceEnabled( data.device ) ) +// // if (!deviceById(data.device) || !deviceById(data.device)->device_active) +// { +// bus_state = BUS_IDLE; +// } + +// // If the bus is idle then release the lines +// if ( bus_state < BUS_ACTIVE ) +// { +// data.init(); +// releaseLines(); +// return; +// } + +// #ifdef PARALLEL_BUS +// // Switch to Parallel if detected +// if ( PARALLEL.bus_state == PBUS_PROCESS ) +// { +// if ( data.primary == IEC_LISTEN || data.primary == IEC_TALK ) +// detected_protocol = PROTOCOL_SPEEDDOS; +// else if ( data.primary == IEC_OPEN || data.primary == IEC_REOPEN ) +// detected_protocol = PROTOCOL_DOLPHINDOS; + +// // Switch to parallel protocol +// protocol = selectProtocol(); + +// if ( data.primary == IEC_LISTEN ) +// PARALLEL.setMode( MODE_RECEIVE ); +// else +// PARALLEL.setMode( MODE_SEND ); + +// // Acknowledge parallel mode +// PARALLEL.handShake(); +// } +// #endif + +// //Debug_printv ( "code[%.2X] primary[%.2X] secondary[%.2X] bus[%d] flags[%d]", c, data.primary, data.secondary, bus_state, flags ); +// //Debug_printv ( "device[%d] channel[%d]", data.device, data.channel); + +// // Delay to stabalize bus +// // protocol->wait( TIMING_STABLE, false ); + +// //release( PIN_IEC_SRQ ); +} + +void systemBus::read_payload() +{ + // // Record the command string until ATN is PULLED + // std::string listen_command = ""; + + // // ATN might get pulled right away if there is no command string to send + // //pull ( PIN_IEC_SRQ ); + + // // Sometimes ATN isn't released immediately. Wait for ATN to be + // // released before trying to read payload. Long ATN delay (>1.5ms) + // // seems to occur more frequently with VIC-20. + // protocol->timeoutWait(PIN_IEC_ATN, RELEASED, FOREVER, false); + + // while (IEC.status(PIN_IEC_ATN) != PULLED) + // { + // //pull ( PIN_IEC_SRQ ); + // int8_t c = protocol->receiveByte(); + // //Debug_printv("c[%2X]", c); + // //release ( PIN_IEC_SRQ ); + + // if (flags & EMPTY_STREAM || flags & ERROR) + // { + // Debug_printv("flags[%2X]", flags); + // bus_state = BUS_ERROR; + // return; + // } + + // if (c != 0xFFFFFFFF && c != 0x0D) + // { + // listen_command += (uint8_t)c; + // } + + // if (flags & EOI_RECVD) + // { + // data.payload = listen_command; + // break; + // } + // } + + // bus_state = BUS_IDLE; +} + +/** + * Start the Interrupt rate limiting timer + */ +void systemBus::timer_start() +{ + // esp_timer_create_args_t tcfg; + // tcfg.arg = this; + // tcfg.callback = onTimer; + // tcfg.dispatch_method = esp_timer_dispatch_t::ESP_TIMER_TASK; + // tcfg.name = nullptr; + // esp_timer_create(&tcfg, &rateTimerHandle); + // esp_timer_start_periodic(rateTimerHandle, timerRate * 1000); +} + +/** + * Stop the Interrupt rate limiting timer + */ +void systemBus::timer_stop() +{ + // // Delete existing timer + // if (rateTimerHandle != nullptr) + // { + // Debug_println("Deleting existing rateTimer\r\n"); + // esp_timer_stop(rateTimerHandle); + // esp_timer_delete(rateTimerHandle); + // rateTimerHandle = nullptr; + // } +} + +std::shared_ptr systemBus::selectProtocol() +{ +// //Debug_printv("protocol[%d]", detected_protocol); + +// switch(detected_protocol) +// { +// #ifdef MEATLOAF_MAX +// case PROTOCOL_SAUCEDOS: +// { +// auto p = std::make_shared(); +// return std::static_pointer_cast(p); +// } +// #endif +// case PROTOCOL_JIFFYDOS: +// { +// auto p = std::make_shared(); +// return std::static_pointer_cast(p); +// } +// #ifdef PARALLEL_BUS +// case PROTOCOL_DOLPHINDOS: +// { +// auto p = std::make_shared(); +// return std::static_pointer_cast(p); +// } +// #endif +// default: +// { +// #ifdef PARALLEL_BUS +// PARALLEL.bus_state = PBUS_IDLE; +// #endif +// auto p = std::make_shared(); +// return std::static_pointer_cast(p); +// } +// } +} + +systemBus virtualDevice::get_bus() +{ + return IEC; +} + +device_state_t virtualDevice::process() +{ + // switch ((bus_command_t)commanddata.primary) + // { + // case bus_command_t::IEC_LISTEN: + // device_state = DEVICE_LISTEN; + // break; + // case bus_command_t::IEC_UNLISTEN: + // device_state = DEVICE_PROCESS; + // break; + // case bus_command_t::IEC_TALK: + // device_state = DEVICE_TALK; + // break; + // default: + // break; + // } + + // switch ((bus_command_t)commanddata.secondary) + // { + // case bus_command_t::IEC_OPEN: + // payload = commanddata.payload; + // break; + // case bus_command_t::IEC_CLOSE: + // payload.clear(); + // std::queue().swap(response_queue); + // pt.clear(); + // pt.shrink_to_fit(); + // break; + // case bus_command_t::IEC_REOPEN: + // if (device_state == DEVICE_TALK) + // { + // } + // else if (device_state == DEVICE_LISTEN) + // { + // payload = commanddata.payload; + // } + // break; + // default: + // break; + // } + + // return device_state; +} + +void virtualDevice::iec_talk_command_buffer_status() +{ + // char reply[80]; + // std::string s; + + // //fnSystem.delay_microseconds(100); + + // if (!status_override.empty()) + // { + // Debug_printv("sending explicit response."); + // IEC.sendBytes(status_override, true); + // status_override.clear(); + // status_override.shrink_to_fit(); + // } + // else + // { + // snprintf(reply, 80, "%u,%s,%u,%u", iecStatus.error, iecStatus.msg.c_str(), iecStatus.connected, iecStatus.channel); + // s = std::string(reply); + // // s = mstr::toPETSCII2(s); + // Debug_printv("sending status: %s\r\n", reply); + // IEC.sendBytes(s, true); + // } +} + +void virtualDevice::dumpData() +{ + Debug_printf("%9s: %02X\r\n", "Primary", commanddata.primary); + Debug_printf("%9s: %02u\r\n", "Device", commanddata.device); + Debug_printf("%9s: %02X\r\n", "Secondary", commanddata.secondary); + Debug_printf("%9s: %02u\r\n", "Channel", commanddata.channel); + Debug_printf("%9s: %s\r\n", "Payload", commanddata.payload.c_str()); +} + +void systemBus::assert_interrupt() +{ + // if (interruptSRQ) + // IEC.pull(PIN_IEC_SRQ); + // else + // IEC.release(PIN_IEC_SRQ); +} + +int8_t systemBus::receiveByte() +{ + int8_t b = 0; //protocol->receiveByte(); +#ifdef DATA_STREAM + Debug_printf("%.2X ", (uint8_t)b); +#endif + if (b == -1) + { + if (!(IEC.flags & ATN_PULLED)) + { + IEC.flags |= ERROR; + Debug_printv("error"); + } + } + return b; +} + +std::string systemBus::receiveBytes() +{ + std::string s; + + // do + // { + // int8_t b = receiveByte(); + // if(b > -1) + // s += b; + // }while(!(flags & EOI_RECVD)); + return s; +} + +bool systemBus::sendByte(const char c, bool eoi) +{ + // if (!protocol->sendByte(c, eoi)) + // { + // if (!(IEC.flags & ATN_PULLED)) + // { + // IEC.flags |= ERROR; + // Debug_printv("error"); + // return false; + // } + // } + +//#ifdef DATA_STREAM + if (eoi) + Debug_printf("%.2X[eoi] ", c); + else + Debug_printf("%.2X ", c); +//#endif + + return true; +} + +bool systemBus::sendBytes(const char *buf, size_t len, bool eoi) +{ + bool success = false; + + for (size_t i = 0; i < len; i++) + { + if (i == (len - 1) && eoi) + success = sendByte(buf[i], true); + else + success = sendByte(buf[i], false); + } + + return success; +} + +bool systemBus::sendBytes(std::string s, bool eoi) +{ + return sendBytes(s.c_str(), s.size(), eoi); +} + +void systemBus::process_cmd() +{ + // fnLedManager.set(eLed::LED_BUS, true); + + // TODO implement + + // fnLedManager.set(eLed::LED_BUS, false); +} + +void systemBus::process_queue() +{ + // TODO IMPLEMENT +} + +void systemBus::deviceListen() +{ + // // If the command is SECONDARY and it is not to expect just a small command on the command channel, then + // // we're into something more heavy. Otherwise read it all out right here until UNLISTEN is received. + // if (data.secondary == IEC_REOPEN && data.channel != CHANNEL_COMMAND) + // { + // // A heapload of data might come now, too big for this context to handle so the caller handles this, we're done here. + // // Debug_printf(" (%.2X SECONDARY) (%.2X CHANNEL)\r\n", data.primary, data.channel); + // Debug_printf("REOPEN on non-command channel.\r\n"); + // bus_state = BUS_ACTIVE; + // } + + // // OPEN or DATA + // else if (data.secondary == IEC_OPEN || data.secondary == IEC_REOPEN) + // { + // read_payload(); + // Debug_printf("{%s}\r\n", data.payload.c_str()); + // } + + // // CLOSE Named Channel + // else if (data.secondary == IEC_CLOSE) + // { + // // Debug_printf(" (E0 CLOSE) (%d CHANNEL)\r\n", data.channel); + // bus_state = BUS_PROCESS; + // } + + // // Unknown + // else + // { + // Debug_printf(" OTHER (%.2X COMMAND) (%.2X CHANNEL) ", data.secondary, data.channel); + // bus_state = BUS_ERROR; + // } +} + +void systemBus::deviceTalk(void) +{ + // // Now do bus turnaround + // //pull(PIN_IEC_SRQ); + // if (!turnAround()) + // { + // Debug_printv("error flags[%d]", flags); + // bus_state = BUS_ERROR; + // return; + // } + // //release(PIN_IEC_SRQ); + + // We have recieved a CMD and we should talk now: + bus_state = BUS_PROCESS; +} + +bool systemBus::turnAround() +{ + /* + TURNAROUND + An unusual sequence takes place following ATN if the computer wishes the remote device to + become a talker. This will usually take place only after a Talk command has been sent. + Immediately after ATN is RELEASED, the selected device will be behaving like a listener. After all, it's + been listening during the ATN cycle, and the computer has been a talker. At this instant, we + have "wrong way" logic; the device is holding down the Data line, and the computer is holding the + Clock line. We must turn this around. Here's the sequence: + the computer quickly realizes what's going on, and pulls the Data line to true (it's already there), as + well as releasing the Clock line to false. The device waits for this: when it sees the Clock line go + true [sic], it releases the Data line (which stays true anyway since the computer is now holding it down) + and then pulls down the Clock line. We're now in our starting position, with the talker (that's the + device) holding the Clock true, and the listener (the computer) holding the Data line true. The + computer watches for this state; only when it has gone through the cycle correctly will it be ready + to receive data. And data will be signalled, of course, with the usual sequence: the talker releases + the Clock line to signal that it's ready to send. + */ + // Debug_printf("IEC turnAround: "); + + // // Wait until the computer releases the ATN line + // if (protocol->timeoutWait(PIN_IEC_ATN, RELEASED, FOREVER) == TIMED_OUT) + // { + // Debug_printf("Wait until the computer releases the ATN line"); + // flags |= ERROR; + // return false; // return error because timeout + // } + // release ( PIN_IEC_DATA_OUT ); + + // // Delay after ATN is RELEASED + // protocol->wait( ( TIMING_Ttk + TIMING_Tda ), 0, false ); + // pull ( PIN_IEC_CLK_OUT ); + + // // Debug_println("turnaround complete"); + return true; +} // turnAround + +void systemBus::reset_all_our_devices() +{ + // TODO iterate through our bus and send reset to each device. +} + +void systemBus::setBitTiming(std::string set, int p1, int p2, int p3, int p4) +{ + // uint8_t i = 0; // Send + // if (mstr::equals(set, "r")) i = 1; + // if (p1) protocol->bit_pair_timing[i][0] = p1; + // if (p2) protocol->bit_pair_timing[i][1] = p2; + // if (p3) protocol->bit_pair_timing[i][2] = p3; + // if (p4) protocol->bit_pair_timing[i][3] = p4; + + // Debug_printv("i[%d] timing[%d][%d][%d][%d]", i, + // protocol->bit_pair_timing[i][0], + // protocol->bit_pair_timing[i][1], + // protocol->bit_pair_timing[i][2], + // protocol->bit_pair_timing[i][3]); +} + +void systemBus::releaseLines(bool wait) +{ + // // Release lines + // release(PIN_IEC_CLK_OUT); + // release(PIN_IEC_DATA_OUT); + + // // Wait for ATN to release and quit + // if (wait) + // { + // Debug_printv("Waiting for ATN to release"); + // protocol->timeoutWait(PIN_IEC_ATN, RELEASED, FOREVER); + // } +} + +void systemBus::senderTimeout() +{ + // releaseLines(); + // this->bus_state = BUS_ERROR; + + // protocol->wait( TIMING_EMPTY ); +} // senderTimeout + +void systemBus::addDevice(virtualDevice *pDevice, int device_id) +{ + if (!pDevice) + { + Debug_printf("systemBus::addDevice() pDevice == nullptr! returning.\r\n"); + return; + } + + // TODO, add device shortcut pointer logic like others + Debug_printf("Device #%02d Ready!\r\n", device_id); + + pDevice->_devnum = device_id; + _daisyChain.push_front(pDevice); + enabledDevices |= 1UL << device_id; +} + +void systemBus::remDevice(virtualDevice *pDevice) +{ + if (!pDevice) + { + Debug_printf("system Bus::remDevice() pDevice == nullptr! returning\r\n"); + return; + } + + _daisyChain.remove(pDevice); + enabledDevices &= ~ ( 1UL << pDevice->_devnum ); +} + +bool systemBus::isDeviceEnabled ( const uint8_t device_id ) +{ + return ( enabledDevices & ( 1 << device_id ) ); +} // isDeviceEnabled + + + +void systemBus::changeDeviceId(virtualDevice *pDevice, int device_id) +{ + if (!pDevice) + { + Debug_printf("systemBus::changeDeviceId() pDevice == nullptr! returning.\r\n"); + return; + } + + for (auto devicep : _daisyChain) + { + if (devicep == pDevice) + devicep->_devnum = device_id; + } +} + +virtualDevice *systemBus::deviceById(int device_id) +{ + for (auto devicep : _daisyChain) + { + if (devicep->_devnum == device_id) + return devicep; + } + return nullptr; +} + +void systemBus::shutdown() +{ + shuttingDown = true; + + for (auto devicep : _daisyChain) + { + Debug_printf("Shutting down device #%02d\r\n", devicep->id()); + devicep->shutdown(); + } + Debug_printf("All devices shut down.\r\n"); +} + + +//#endif /* BUILD_IEC */ diff --git a/lib/bus/iec/iec.h b/lib/bus/iec/iec.h new file mode 100644 index 0000000..9a9582b --- /dev/null +++ b/lib/bus/iec/iec.h @@ -0,0 +1,582 @@ +#ifndef IEC_H +#define IEC_H + +// This code uses code from the Meatloaf Project: +// Meatloaf - A Commodore 64/128 multi-device emulator +// https://github.com/idolpx/meatloaf +// Copyright(C) 2020 James Johnston +// +// Meatloaf is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Meatloaf is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Meatloaf. If not, see . + +// +// https://www.pagetable.com/?p=1135 +// http://unusedino.de/ec64/technical/misc/c1541/romlisting.html#E85B +// https://eden.mose.org.uk/gitweb/?p=rom-reverse.git;a=blob;f=src/vic-1541-sfd.asm;hb=HEAD +// https://www.pagetable.com/docs/Inside%20Commodore%20DOS.pdf +// http://www.ffd2.com/fridge/docs/1541dis.html#E853 +// http://unusedino.de/ec64/technical/aay/c1541/ +// http://unusedino.de/ec64/technical/aay/c1581/ +// + +#include +#include +// #include +// #include +#include +#include +#include +#include +#include +//#include +//#include "fnSystem.h" + +// #include "protocol/_protocol.h" +// #include "protocol/jiffydos.h" +// #ifdef MEATLOAF_MAX +// #include "protocol/saucedos.h" +// #endif +// #ifdef PARALLEL_BUS +// #include "protocol/dolphindos.h" +// #endif + +//#include + +#include "../../../include/debug.h" +#include "../../../include/cbm_defines.h" +//#include "../../../include/pinmap.h" + + +/** + * @brief The command frame + */ +union cmdFrame_t +{ + struct + { + uint8_t device; + uint8_t comnd; + uint8_t aux1; + uint8_t aux2; + uint8_t cksum; + }; + struct + { + uint32_t commanddata; + uint8_t checksum; + } __attribute__((packed)); +}; + +// Return values for service: +typedef enum +{ + BUS_OFFLINE = -3, // Bus is empty + BUS_RESET = -2, // The bus is in a reset state (RESET line). + BUS_ERROR = -1, // A problem occoured, reset communication + BUS_IDLE = 0, // Nothing recieved of our concern + BUS_ACTIVE = 1, // ATN is pulled and a command byte is expected + BUS_PROCESS = 2, // A command is ready to be processed +} bus_state_t; + +/** + * @enum bus command + */ +typedef enum +{ + IEC_GLOBAL = 0x00, // 0x00 + cmd (global command) + IEC_LISTEN = 0x20, // 0x20 + device_id (LISTEN) (0-30) + IEC_UNLISTEN = 0x3F, // 0x3F (UNLISTEN) + IEC_TALK = 0x40, // 0x40 + device_id (TALK) (0-30) + IEC_UNTALK = 0x5F, // 0x5F (UNTALK) + IEC_REOPEN = 0x60, // 0x60 + channel (OPEN CHANNEL) (0-15) + IEC_REOPEN_JD = 0x61, // 0x61 + channel (OPEN CHANNEL) (0-15) - JIFFYDOS LOAD + IEC_CLOSE = 0xE0, // 0xE0 + channel (CLOSE NAMED CHANNEL) (0-15) + IEC_OPEN = 0xF0 // 0xF0 + channel (OPEN NAMED CHANNEL) (0-15) +} bus_command_t; + +typedef enum +{ + DEVICE_ERROR = -1, + DEVICE_IDLE = 0, // Ready and waiting + DEVICE_ACTIVE = 1, + DEVICE_LISTEN = 2, // A command is recieved and data is coming to us + DEVICE_TALK = 3, // A command is recieved and we must talk now + DEVICE_PROCESS = 4, // Execute device command +} device_state_t; + +typedef enum { + PROTOCOL_SERIAL, + PROTOCOL_FAST_SERIAL, + PROTOCOL_SAUCEDOS, + PROTOCOL_JIFFYDOS, + PROTOCOL_EPYXFASTLOAD, + PROTOCOL_WARPSPEED, + PROTOCOL_SPEEDDOS, + PROTOCOL_DOLPHINDOS, + PROTOCOL_WIC64, + PROTOCOL_IEEE488 +} bus_protocol_t; + +//using namespace Protocol; + +/** + * @class IECData + * @brief the IEC command data passed to devices + */ +class IECData +{ +public: + /** + * @brief the primary command byte + */ + uint8_t primary = 0; + /** + * @brief the primary device number + */ + uint8_t device = 0; + /** + * @brief the secondary command byte + */ + uint8_t secondary = 0; + /** + * @brief the secondary command channel + */ + uint8_t channel = 0; + /** + * @brief the device command + */ + std::string payload = ""; + + /** + * @brief clear and initialize IEC command data + */ + void init(void) + { + primary = 0; + device = 0; + secondary = 0; + channel = 0; + payload = ""; + } +}; + +/** + * @class Forward declaration of System Bus + */ +class systemBus; + +/** + * @class virtualDevice + * @brief All #FujiNet devices derive from this. + */ +class virtualDevice +{ +private: + +protected: + friend systemBus; /* Because we connect to it. */ + + /** + * @brief The device number (ID) + */ + int _devnum; + + /** + * @brief The passed in command frame, copied. + */ + cmdFrame_t cmdFrame; + + /** + * @brief The current device command + */ + std::string payload; + + /** + * @brief The current device command in raw PETSCII. Used when payload is converted to ASCII for Basic commands + */ + std::string payloadRaw; + + /** + * @brief pointer to the current command data + */ + IECData commanddata; + + /** + * @brief current device state. + */ + device_state_t device_state; + + /** + * @brief response queue (e.g. INPUT) + * @deprecated remove as soon as it's out of fuji. + */ + std::queue response_queue; + + /** + * @brief status override (for binary commands) + */ + std::string status_override; + + /** + * @brief tokenized payload + */ + std::vector pt; + std::vector pti; + + /** + * @brief The status information to send back on cmd input + * @param bw = # of bytes waiting + * @param msg = most recent status message + * @param connected = is most recent channel connected? + * @param channel = channel of most recent status msg. + */ + struct _iecStatus + { + uint8_t error; + std::string msg; + bool connected; + int channel; + } iecStatus; + + /** + * @brief Get device ready to handle next phase of command. + */ + device_state_t queue_command(const IECData &data) + { + commanddata = data; + + if (commanddata.primary == IEC_LISTEN) + device_state = DEVICE_LISTEN; + else if (commanddata.primary == IEC_TALK) + device_state = DEVICE_TALK; + + return device_state; + } + + /** + * @brief All IEC devices repeatedly call this routine to fan out to other methods for each command. + * This is typcially implemented as a switch() statement. + * @return new device state. + */ + virtual device_state_t process(); + + /** + * @brief poll whether interrupt should be wiggled + * @param c secondary channel (0-15) + */ + virtual void poll_interrupt(unsigned char c) {} + + /** + * @brief Dump the current IEC frame to terminal. + */ + void dumpData(); + + /** + * @brief If response queue is empty, Return 1 if ANY receive buffer has data in it, else 0 + */ + virtual void iec_talk_command_buffer_status(); + + // Optional shutdown/reboot cleanup routine + virtual void shutdown(){}; + +public: + /** + * @brief get the IEC device Number (1-31) + * @return The device number registered for this device + */ + int id() { return _devnum; }; + + /** + * @brief Is this virtualDevice holding the virtual disk drive used to boot CONFIG? + */ + bool is_config_device = false; + + /** + * @brief is device active (turned on?) + */ + bool device_active = true; + + /** + * The spinlock for the ESP32 hardware timers. Used for interrupt rate limiting. + */ +// portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; + + /** + * @brief Get the systemBus object that this virtualDevice is attached to. + */ + systemBus get_bus(); +}; + +/** + * @class systemBus + * @brief the system bus that all virtualDevices attach to. + */ +class systemBus +{ +private: + /** + * @brief The chain of devices on the bus. + */ + std::forward_list _daisyChain; + + /** + * @brief Number of devices on bus + */ + int _num_devices = 0; + + /** + * @brief the active device being process()'ed + */ + virtualDevice *_activeDev = nullptr; + + /** + * @brief is device shutting down? + */ + bool shuttingDown = false; + + /** + * @brief the detected bus protocol + */ + bus_protocol_t detected_protocol = PROTOCOL_SERIAL; // default is IEC Serial + + /** + * @brief the active bus protocol + */ + std::shared_ptr protocol = nullptr; + + /** + * @brief Switch to detected bus protocol + */ + std::shared_ptr selectProtocol(); + + /** + * IEC LISTEN received + */ + void deviceListen(); + + /** + * IEC TALK requested + */ + void deviceTalk(); + + /** + * BUS TURNAROUND (switch from listener to talker) + */ + bool turnAround(); + + /** + * @brief called to process the next command + */ + void process_cmd(); + + /** + * @brief called to process a queue item (such as disk swap) + */ + void process_queue(); + + /** + * @brief called to read bus command bytes + */ + void read_command(); + + /** + * @brief called to read bus payload bytes + */ + void read_payload(); + + /** + * ESP timer handle for the Interrupt rate limiting timer + */ + // esp_timer_handle_t rateTimerHandle = nullptr; + + /** + * Timer Rate for interrupt timer + */ + int timerRate = 100; + + /** + * @brief Start the Interrupt rate limiting timer + */ + void timer_start(); + + /** + * @brief Stop the Interrupt rate limiting timer + */ + void timer_stop(); + +public: + /** + * @brief bus flags + */ + uint16_t flags = CLEAR; + + /** + * @brief current bus state + */ + bus_state_t bus_state; + + /** + * Toggled by the rate limiting timer to indicate that the SRQ interrupt should + * be pulsed. + */ + bool interruptSRQ = false; + + /** + * @brief data about current bus transaction + */ + IECData data; + + /** + * @brief Enabled device bits + */ + uint32_t enabledDevices; + + /** + * @brief called in main.cpp to set up the bus. + */ + void setup(); + + /** + * @brief Run one iteration of the bus service loop + */ + void service(); + + /** + * @brief Called to pulse the PROCEED interrupt, rate limited by the interrupt timer. + */ + void assert_interrupt(); + + /** + * @brief Release the bus lines, we're done. + */ + void releaseLines(bool wait = false); + + /** + * @brief Set 2bit fast loader pair timing + * @param set Send 's', Receive 'r' + * @param p1 Pair 1 + * @param p2 Pair 2 + * @param p3 Pair 3 + * @param p4 Pair 4 + */ + void setBitTiming(std::string set, int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0); + + /** + * @brief send single byte + * @param c byte to send + * @param eoi Send EOI? + * @return true on success, false on error + */ + bool sendByte(const char c, bool eoi = false); + + /** + * @brief Send bytes to bus + * @param buf buffer to send + * @param len length of buffer + * @param eoi Send EOI? + * @return true on success, false on error + */ + bool sendBytes(const char *buf, size_t len, bool eoi = false); + + /** + * @brief Send string to bus + * @param s std::string to send + * @param eoi Send EOI? + * @return true on success, false on error + */ + bool sendBytes(std::string s, bool eoi = false); + + /** + * @brief Receive Byte from bus + * @return Byte received from bus, or -1 for error + */ + int8_t receiveByte(); + + /** + * @brief Receive String from bus + * @return std::string received from bus + */ + std::string receiveBytes(); + + /** + * @brief called in response to RESET pin being asserted. + */ + void reset_all_our_devices(); + + /** + * @brief called from main shutdown to clean up the device. + */ + void shutdown(); + + /** + * @brief Return number of devices on bus. + * @return # of devices on bus. + */ + int numDevices() { return _num_devices; }; + + /** + * @brief Add device to bus. + * @param pDevice Pointer to virtualDevice + * @param device_id The ID to assign to virtualDevice + */ + void addDevice(virtualDevice *pDevice, int device_id); + + /** + * @brief Remove device from bus + * @param pDevice pointer to virtualDevice + */ + void remDevice(virtualDevice *pDevice); + + /** + * @brief Check if device is enabled + * @param deviceNumber The device ID to check + */ + bool isDeviceEnabled ( const uint8_t device_id ); + + /** + * @brief Return pointer to device given ID + * @param device_id ID of device to return. + * @return pointer to virtualDevice + */ + virtualDevice *deviceById(int device_id); + + /** + * @brief Change ID of a particular virtualDevice + * @param pDevice pointer to virtualDevice + * @param device_id new device ID + */ + void changeDeviceId(virtualDevice *pDevice, int device_id); + + /** + * @brief Are we shutting down? + * @return value of shuttingDown + */ + bool getShuttingDown() { return shuttingDown; } + + /** + * @brief signal to bus that we timed out. + */ + void senderTimeout(); + + + void pull ( int _pin ); + void release ( int _pin ); + bool status ( int _pin ); + //int status(); +}; +/** + * @brief Return + */ +extern systemBus IEC; + +#endif /* IEC_H */ diff --git a/lib/device/iec/drive.cpp b/lib/device/iec/drive.cpp new file mode 100644 index 0000000..bf848d8 --- /dev/null +++ b/lib/device/iec/drive.cpp @@ -0,0 +1,911 @@ + +#include "drive.h" + +#include + +#include "../../include/debug.h" +#include "../../include/cbm_defines.h" + +#include "bus.h" + +#include "string_utils.h" + + + +// used to start working with a stream, registering it as underlying stream of some +// IEC channel on some IEC device +bool iecDrive::registerStream ( uint8_t channel ) +{ + // Debug_printv("dc_basepath[%s]", device_config.basepath().c_str()); + // Debug_printv("_file[%s]", _file.c_str()); + + // TODO: Determine mode and create the proper stream + std::ios_base::openmode mode = std::ios_base::in; + + Debug_printv("_base[%s]", _base->url.c_str()); + _base.reset( MFSOwner::File( _base->url ) ); + + std::shared_ptr new_stream; + + // LOAD / GET / INPUT + if ( channel == CHANNEL_LOAD ) + { + if ( !_base->exists() ) + return false; + + Debug_printv("LOAD \"%s\"", _base->url.c_str()); + new_stream = std::shared_ptr(_base->meatStream()); + } + + // SAVE / PUT / PRINT / WRITE + else if ( channel == CHANNEL_SAVE ) + { + Debug_printv("SAVE \"%s\"", _base->url.c_str()); + // CREATE STREAM HERE FOR OUTPUT + new_stream = std::shared_ptr(_base->meatStream()); + new_stream->open(); + } + else + { + Debug_printv("OTHER \"%s\"", _base->url.c_str()); + new_stream = std::shared_ptr(_base->meatStream()); + } + + + if ( new_stream == nullptr ) + { + return false; + } + + if( !new_stream->isOpen() ) + { + Debug_printv("Error creating stream"); + return false; + } + else + { + // Close the stream if it is already open + closeStream( channel ); + } + + + //size_t key = ( IEC.data.device * 100 ) + IEC.data.channel; + + // // Check to see if a stream is open on this device/channel already + // auto found = streams.find(key); + // if ( found != streams.end() ) + // { + // Debug_printv( "Stream already registered on this device/channel!" ); + // return false; + // } + + // Add stream to streams + auto newPair = std::make_pair ( channel, new_stream ); + streams.insert ( newPair ); + + Debug_printv("Stream created. key[%d]", channel); + return true; +} + +std::shared_ptr iecDrive::retrieveStream ( uint8_t channel ) +{ + Debug_printv("Stream key[%d]", channel); + + if ( streams.find ( channel ) != streams.end() ) + { + Debug_printv("Stream retrieved. key[%d]", channel); + return streams.at ( channel ); + } + else + { + Debug_printv("Error! Trying to recall not-registered stream!"); + return nullptr; + } +} + +bool iecDrive::closeStream ( uint8_t channel, bool close_all ) +{ + auto found = streams.find(channel); + + if ( found != streams.end() ) + { + //Debug_printv("Stream closed. key[%d]", key); + auto closingStream = (*found).second; + closingStream->close(); + return streams.erase ( channel ); + } + + return false; +} + +uint16_t iecDrive::retrieveLastByte ( uint8_t channel ) +{ + if ( streamLastByte.find ( channel ) != streamLastByte.end() ) + { + return streamLastByte.at ( channel ); + } + else + { + return 999; + } +} + +void iecDrive::storeLastByte( uint8_t channel, char last) +{ + auto newPair = std::make_pair ( channel, (uint16_t)last ); + streamLastByte.insert ( newPair ); +} + +void iecDrive::flushLastByte( uint8_t channel ) +{ + auto newPair = std::make_pair ( channel, (uint16_t)999 ); + streamLastByte.insert ( newPair ); +} + + + +// send single basic line, including heading basic pointer and terminating zero. +uint16_t iecDrive::sendLine(uint16_t blocks, const char *format, ...) +{ + // Debug_printv("bus[%d]", IEC.bus_state); + + // // Exit if ATN is PULLED while sending + // // Exit if there is an error while sending + // if ( IEC.bus_state == BUS_ERROR ) + // { + // // Save file pointer position + // //streamUpdate(basicPtr); + // //setDeviceStatus(74); + // return 0; + // } + + // Format our string + va_list args; + va_start(args, format); + char text[vsnprintf(NULL, 0, format, args) + 1]; + vsnprintf(text, sizeof text, format, args); + va_end(args); + + return sendLine(blocks, text); +} + +uint16_t iecDrive::sendLine(uint16_t blocks, char *text) +{ + Debug_printf("%d %s ", blocks, text); + + // // Exit if ATN is PULLED while sending + // // Exit if there is an error while sending + // if ( IEC.flags & ERROR ) return 0; + + // Get text length + uint8_t len = strlen(text); + + // Send that pointer + // No basic line pointer is used in the directory listing set to 0x0101 + IEC.sendByte(0x01); // IEC.sendByte(basicPtr bitand 0xFF); + IEC.sendByte(0x01); // IEC.sendByte(basicPtr >> 8); + + // Send blocks + IEC.sendByte(blocks bitand 0xFF); + IEC.sendByte(blocks >> 8); + + // Send line contents + for (uint8_t i = 0; i < len; i++) + { + if ( !IEC.sendByte(text[i]) ) return 0; + } + + // Finish line + IEC.sendByte(0); + + Debug_println(""); + + return len + 5; +} // sendLine + +uint16_t iecDrive::sendHeader(std::string header, std::string id) +{ + uint16_t byte_count = 0; + bool sent_info = false; + + std::string url = _base->host; + url = mstr::toPETSCII2(url); + std::string path = _base->pathToFile(); + path = mstr::toPETSCII2(path); + std::string archive = _base->media_archive; + archive = mstr::toPETSCII2(archive); + std::string image = _base->media_image; + image = mstr::toPETSCII2(image); + Debug_printv("path[%s] size[%d]", path.c_str(), path.size()); + + // Send List HEADER + uint8_t space_cnt = 0; + space_cnt = (16 - header.size()) / 2; + space_cnt = (space_cnt > 8 ) ? 0 : space_cnt; + + //Debug_printv("header[%s] id[%s] space_cnt[%d]", header.c_str(), id.c_str(), space_cnt); + + byte_count += sendLine(0, CBM_REVERSE_ON "\"%*s%s%*s\" %s", space_cnt, "", header.c_str(), space_cnt, "", id.c_str()); + if ( IEC.flags & ERROR ) return 0; + + //byte_count += sendLine(basicPtr, 0, "\x12\"%*s%s%*s\" %.02d 2A", space_cnt, "", PRODUCT_ID, space_cnt, "", device_config.device()); + //byte_count += sendLine(basicPtr, 0, CBM_REVERSE_ON "%s", header.c_str()); + + // Send Extra INFO + if (url.size()) + { + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[URL]"); + if ( IEC.flags & ERROR ) return 0; + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, url.c_str()); + if ( IEC.flags & ERROR ) return 0; + sent_info = true; + } + if (path.size() > 1) + { + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[PATH]"); + if ( IEC.flags & ERROR ) return 0; + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, path.c_str()); + if ( IEC.flags & ERROR ) return 0; + sent_info = true; + } + if (archive.size() > 1) + { + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[ARCHIVE]"); + if ( IEC.flags & ERROR ) return 0; + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, archive.c_str()); + if ( IEC.flags & ERROR ) return 0; + } + if (image.size()) + { + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, "[IMAGE]"); + if ( IEC.flags & ERROR ) return 0; + byte_count += sendLine(0, "%*s\"%-*s\" NFO", 0, "", 19, image.c_str()); + if ( IEC.flags & ERROR ) return 0; + sent_info = true; + } + if (sent_info) + { + byte_count += sendLine(0, "%*s\"-------------------\" NFO", 0, ""); + if ( IEC.flags & ERROR ) return 0; + } + + // // If SD Card is available ad we are at the root path show it as a directory at the top + // if (fnSDFAT.running() && _base->url.size() < 2) + // { + // byte_count += sendLine(0, "%*s\"SD\" DIR", 3, ""); + // if ( IEC.flags & ERROR ) return 0; + // } + + return byte_count; +} + +uint16_t iecDrive::sendFooter() +{ + uint16_t blocks_free; + uint16_t byte_count = 0; + uint64_t bytes_free = _base->getAvailableSpace(); + + if ( _base->size() ) + { + blocks_free = _base->media_blocks_free; + byte_count = sendLine(blocks_free, "BLOCKS FREE."); + } + else + { + // We are not in a media file so let's show BYTES FREE instead + blocks_free = 0; + byte_count = sendLine(blocks_free, CBM_DELETE CBM_DELETE "%sBYTES FREE.", mstr::formatBytes(bytes_free).c_str() ); + } + + return byte_count; +} + +void iecDrive::sendListing() +{ + Debug_printf("sendListing: [%s]\r\n=================================\r\n", _base->url.c_str()); + + uint16_t byte_count = 0; + std::string extension = "dir"; + + std::unique_ptr entry = std::unique_ptr( _base->getNextFileInDir() ); + + if(entry == nullptr) { + closeStream( commanddata.channel ); + + bool isOpen = registerStream(commanddata.channel); + if(isOpen) + { + sendFile(); + } + else + { + IEC.senderTimeout(); // File Not Found + } + + return; + } + + //fnLedStrip.startRainbow(300); + + // Send load address + IEC.sendByte(CBM_BASIC_START & 0xff); + IEC.sendByte((CBM_BASIC_START >> 8) & 0xff); + byte_count += 2; + + // If there has been a error don't try to send any more bytes + if ( IEC.flags & ERROR ) + { + Debug_printv(":("); + return; + } + + Debug_println(""); + + // Send Listing Header + if (_base->media_header.size() == 0) + { + // Send device default listing header + char buf[7] = { '\0' }; + sprintf(buf, "%.02d 2A", IEC.data.device); + byte_count += sendHeader(PRODUCT_ID, buf); + if ( IEC.flags & ERROR ) return; + } + else + { + // Send listing header from media file + byte_count += sendHeader(_base->media_header.c_str(), _base->media_id.c_str()); + if ( IEC.flags & ERROR ) return; + } + + // Send Directory Items + while(entry != nullptr) + { + if (!entry->isDirectory()) + { + // Get extension + if (entry->extension.length()) + { + extension = entry->extension; + } + else + { + extension = "prg"; + } + } + else + { + extension = "dir"; + } + + // Don't show hidden folders or files + //Debug_printv("size[%d] name[%s]", entry->size(), entry->name.c_str()); + std::string name = entry->name; + if ( !entry->isPETSCII ) + { + name = mstr::toPETSCII2( entry->name ); + extension = mstr::toPETSCII2(extension); + } + mstr::rtrimA0(name); + mstr::replaceAll(name, "\\", "/"); + + //uint32_t s = entry->size(); + //uint32_t block_cnt = s / _base->media_block_size; + uint32_t block_cnt = entry->blocks(); + // Debug_printv( "size[%d] blocks[%d] blocksz[%d]", s, block_cnt, _base->media_block_size ); + //if ( s > 0 && s < _base->media_block_size ) + // block_cnt = 1; + + uint8_t block_spc = 3; + if (block_cnt > 9) + block_spc--; + if (block_cnt > 99) + block_spc--; + if (block_cnt > 999) + block_spc--; + + uint8_t space_cnt = 21 - (name.size() + 5); + if (space_cnt > 21) + space_cnt = 0; + + if (name[0]!='.') + { + // Exit if ATN is PULLED while sending + // Exit if there is an error while sending + if ( IEC.bus_state == BUS_ERROR ) + { + // Save file pointer position + // streamUpdate(byte_count); + //setDeviceStatus(74); + return; + } + + byte_count += sendLine(block_cnt, "%*s\"%s\"%*s %s", block_spc, "", name.c_str(), space_cnt, "", extension.c_str()); + if ( IEC.flags & ERROR ) return; + } + + entry.reset(_base->getNextFileInDir()); + + //fnLedManager.toggle(eLed::LED_BUS); + } + + // Send Listing Footer + byte_count += sendFooter(); + if ( IEC.flags & ERROR ) return; + + // End program with two zeros after last line. Last zero goes out as EOI. + IEC.sendByte(0); + IEC.sendByte(0, true); + //closeStream(); + + Debug_printf("\r\n=================================\r\n%d bytes sent\r\n", byte_count); + + //fnLedManager.set(eLed::LED_BUS, false); + //fnLedStrip.stopRainbow(); +} // sendListing + + + +// bool iecDrive::sendFile() +// { +// size_t count = 0; +// bool success_rx = true; +// bool success_tx = true; + +// uint8_t b; // byte +// uint8_t nb; // next byte +// size_t bi = 0; +// size_t load_address = 0; +// size_t sys_address = 0; + +// //iecStream.open(&IEC); + +// #ifdef DATA_STREAM +// char ba[9]; +// ba[8] = '\0'; +// #endif + +// // std::shared_ptr istream = std::static_pointer_cast(currentStream); +// auto istream = retrieveStream(commanddata.channel); +// if ( istream == nullptr ) +// { +// Debug_printv("Stream not found!"); +// IEC.senderTimeout(); // File Not Found +// //closeStream(commanddata.channel); +// _base.reset( MFSOwner::File( _base->base() ) ); +// return false; +// } + +// if ( !_base->isDirectory() ) +// { +// if ( istream->has_subdirs ) +// { +// PeoplesUrlParser u; +// u.parseUrl( istream->url ); +// Debug_printv( "Subdir Change Directory Here! istream[%s] > base[%s]", istream->url.c_str(), u.base().c_str() ); +// _last_file = u.name; +// _base.reset( MFSOwner::File( u.base() ) ); +// } +// else +// { +// auto f = MFSOwner::File( istream->url ); +// Debug_printv( "Change Directory Here! istream[%s] > base[%s]", istream->url.c_str(), f->streamFile->url.c_str() ); +// _base.reset( f->streamFile ); +// } + +// } + +// uint32_t len = istream->size(); +// uint32_t avail = istream->available(); +// if ( !len ) +// len = -1; + +// //fnLedStrip.startRainbow(300); + +// if( IEC.data.channel == CHANNEL_LOAD ) +// { +// // Get/Send file load address +// count = 2; +// istream->read(&b, 1); +// success_tx = IEC.sendByte(b); +// load_address = b & 0x00FF; // low byte +// istream->read(&b, 1); +// success_tx = IEC.sendByte(b); +// load_address = load_address | b << 8; // high byte +// sys_address = load_address; +// Debug_printv( "load_address[$%.4X] sys_address[%d]", load_address, sys_address ); + +// // Get SYSLINE +// } + +// // Read byte +// success_rx = istream->read(&b, 1); +// //Debug_printv("b[%02X] success[%d]", b, success_rx); + +// Debug_printf("sendFile: [$%.4X]\r\n=================================\r\n", load_address); +// while( success_rx && !istream->error() ) +// { +// // Read next byte +// success_rx = istream->read(&nb, 1); + +// //Debug_printv("b[%02X] nb[%02X] success_rx[%d] error[%d]", b, nb, success_rx, istream->error()); +// #ifdef DATA_STREAM +// if (bi == 0) +// { +// Debug_printf(":%.4X ", load_address); +// load_address += 8; +// } +// #endif +// // Send Byte +// if ( count + 1 == avail || !success_rx ) +// { +// //Debug_printv("b[%02X] EOI %i", b, count); +// success_tx = IEC.sendByte(b, true); // indicate end of file. +// if ( !success_tx ) +// Debug_printv("tx fail"); + +// break; +// } +// else +// { +// success_tx = IEC.sendByte(b); +// if ( !success_tx ) +// { +// Debug_printv("tx fail"); +// //break; +// } + +// } +// b = nb; // byte = next byte +// count++; + +// #ifdef DATA_STREAM +// // Show ASCII Data +// if (b < 32 || b >= 127) +// ba[bi++] = 46; +// else +// ba[bi++] = b; + +// if(bi == 8) +// { +// uint32_t t = (count * 100) / len; +// Debug_printf(" %s (%d %d%%) [%d]\r\n", ba, count, t, avail); +// bi = 0; +// } +// #else +// uint32_t t = (count * 100) / len; +// Debug_printf("\rTransferring %d%% [%d, %d] ", t, count, avail); +// #endif + +// // Exit if ATN is PULLED while sending +// //if ( IEC.status ( PIN_IEC_ATN ) == PULLED ) +// if ( IEC.flags & ATN_PULLED ) +// { +// //Debug_printv("ATN pulled while sending. i[%d]", i); + +// // Save file pointer position +// istream->seek(istream->position() - 2); +// //success_rx = true; +// break; +// } + +// // // Toggle LED +// // if (i % 50 == 0) +// // { +// // fnLedManager.toggle(eLed::LED_BUS); +// // } +// } + +// #ifdef DATA_STREAM +// if (bi) +// { +// uint32_t t = (count * 100) / len; +// ba[bi] = 0; +// Debug_printf(" %s (%d %d%%) [%d]\r\n", ba, count, t, avail); +// bi = 0; +// } +// #endif + +// Debug_printf("\r\n=================================\r\n%d bytes sent of %d [SYS%d]\r\n", count, avail, sys_address); + +// //Debug_printv("len[%d] avail[%d] success_rx[%d]", len, avail, success_rx); + +// //fnLedManager.set(eLed::LED_BUS, false); +// //fnLedStrip.stopRainbow(); + +// if ( istream->error() ) +// { +// Debug_println("sendFile: Transfer aborted!"); +// IEC.senderTimeout(); +// closeStream(commanddata.channel); +// } + +// return success_rx; +// } // sendFile + +bool iecDrive::sendFile() +{ + size_t count = 0; + bool success_rx = true; + bool success_tx = true; + + uint8_t b; // byte + uint8_t nb; // next byte + size_t bi = 0; + size_t load_address = 0; + size_t sys_address = 0; + + //iecStream.open(&IEC); + +#ifdef DATA_STREAM + char ba[9]; + ba[8] = '\0'; +#endif + + // std::shared_ptr istream = std::static_pointer_cast(currentStream); + auto istream = retrieveStream(commanddata.channel); + if ( istream == nullptr ) + { + Debug_printv("Stream not found!"); + IEC.senderTimeout(); // File Not Found + _last_file = ""; + _base.reset( MFSOwner::File( _base->base() ) ); + return false; + } + + if ( !_base->isDirectory() ) + { + if ( istream->has_subdirs ) + { + PeoplesUrlParser *u = PeoplesUrlParser::parseUrl( istream->url ); + Debug_printv( "Subdir Change Directory Here! istream[%s] > base[%s]", istream->url.c_str(), u->base().c_str() ); + _last_file = u->name; + _base.reset( MFSOwner::File( u->base() ) ); + } + else + { + auto f = MFSOwner::File( istream->url ); + Debug_printv( "Change Directory Here! istream[%s] > base[%s]", istream->url.c_str(), f->streamFile->url.c_str() ); + _base.reset( f->streamFile ); + } + } + + bool eoi = false; + uint32_t len = istream->size(); + uint32_t avail = istream->available(); + + //fnLedStrip.startRainbow(300); + Debug_printv("len[%d] avail[%d]", len, avail); + + if( commanddata.channel == CHANNEL_LOAD ) + { + // Get/Send file load address + count = 2; + istream->read(&b, 1); + success_tx = IEC.sendByte(b); + load_address = b & 0x00FF; // low byte + istream->read(&b, 1); + success_tx = IEC.sendByte(b); + load_address = load_address | b << 8; // high byte + sys_address = load_address; + Debug_printv( "load_address[$%.4X] sys_address[%d]", load_address, sys_address ); + + // Get SYSLINE + } + + // Read byte + success_rx = istream->read(&b, 1); + //Debug_printv("b[%02X] success[%d]", b, success_rx); + + Debug_printf("sendFile: [$%.4X]\r\n=================================\r\n", load_address); + while( success_rx && !istream->error() ) + { + count = istream->position(); + avail = istream->available(); + + //Debug_printv("b[%02X] nb[%02X] success_rx[%d] error[%d]", b, nb, success_rx, istream->error()); +#ifdef DATA_STREAM + if (bi == 0) + { + Debug_printf(":%.4X ", load_address); + load_address += 8; + } +#endif + + // Send Byte + //IEC.pull(PIN_IEC_SRQ); + success_tx = IEC.sendByte(b, eoi); + if ( !success_tx ) + { + Debug_printv("tx fail"); + //IEC.release(PIN_IEC_SRQ); + return false; + } + //IEC.release(PIN_IEC_SRQ); + + // Read next byte + success_rx = istream->read(&nb, 1); + + // Is this the last byte in the stream? + if ( istream->eos() ) + eoi = true; + + b = nb; // byte = next byte + + uint32_t t = (count * 100) / len; +#ifdef DATA_STREAM + // Show ASCII Data + if (b < 32 || b >= 127) + ba[bi++] = 46; + else + ba[bi++] = b; + + if(bi == 8) + { + Debug_printf(" %s (%d %d%%) [%d]\r\n", ba, count, t, avail); + bi = 0; + } +#else + Debug_printf("\rTransferring %d%% [%d, %d] ", t, count, avail); +#endif + + // Exit if ATN is PULLED while sending + //if ( IEC.status ( PIN_IEC_ATN ) == PULLED ) + if ( IEC.flags & ATN_PULLED || istream->error() ) + { + //Debug_printv("ATN pulled while sending. i[%d]", i); + + // Save file pointer position + istream->seek(istream->position() - 2); + //success_rx = true; + break; + } + + // // Toggle LED + // if (i % 50 == 0) + // { + // fnLedManager.toggle(eLed::LED_BUS); + // } + } + +#ifdef DATA_STREAM + uint32_t t = (count * 100) / len; + ba[bi++] = 0; + Debug_printf(" %s (%d %d%%) [%d]\r\n", ba, count, t, avail); +#endif + + Debug_printf("\r\n=================================\r\n%d bytes sent of %d [SYS%d]\r\n", count, avail, sys_address); + + //Debug_printv("len[%d] avail[%d] success_rx[%d]", len, avail, success_rx); + + //fnLedManager.set(eLed::LED_BUS, false); + //fnLedStrip.stopRainbow(); + + if ( istream->error() ) + { + Debug_println("sendFile: Transfer aborted!"); + IEC.senderTimeout(); + closeStream(commanddata.channel); + } + + return success_rx; +} // sendFile + + +bool iecDrive::saveFile() +{ + size_t i = 0; + bool success = true; + bool done = false; + + size_t bi = 0; + size_t load_address = 0; + size_t b_len = 1; + uint8_t b[b_len]; + uint8_t ll[b_len]; + uint8_t lh[b_len]; + +#ifdef DATA_STREAM + char ba[9]; + ba[8] = '\0'; +#endif + + auto ostream = retrieveStream(commanddata.channel); + + if ( ostream == nullptr ) { + Debug_printv("couldn't open a stream for writing"); + IEC.senderTimeout(); // File Not Found + return false; + } + else + { + // Stream is open! Let's save this! + + // wait - what??? If stream position == x you don't have to seek(x)!!! + // if ( ostream->position() > 0 ) + // { + // // // Position file pointer + // // ostream->seek(currentStream.cursor); + // } + // else + //fnLedStrip.startRainbow(300); + { + // Get file load address + ll[0] = IEC.receiveByte(); + load_address = *ll & 0x00FF; // low byte + lh[0] = IEC.receiveByte(); + load_address = load_address | *lh << 8; // high byte + } + + + Debug_printv("saveFile: [$%.4X]\r\n=================================\r\n", load_address); + + // Recieve bytes until a EOI is detected + do + { + // Save Load Address + if (i == 0) + { + Debug_print("["); + ostream->write(ll, b_len); + ostream->write(lh, b_len); + i += 2; + Debug_println("]"); + } + +#ifdef DATA_STREAM + if (bi == 0) + { + Debug_printf(":%.4X ", load_address); + load_address += 8; + } +#endif + + b[0] = IEC.receiveByte(); + // if(ostream->isText()) + // ostream->putPetsciiAsUtf8(b[0]); + // else + ostream->write(b, b_len); + i++; + + uint16_t f = IEC.flags; + done = (f & EOI_RECVD) or (f & ERROR); + + // Exit if ATN is PULLED while sending + if ( f & ATN_PULLED ) + { + // Save file pointer position + // streamUpdate(ostream->position()); + //setDeviceStatus(74); + break; + } + +#ifdef DATA_STREAM + // Show ASCII Data + if (b[0] < 32 || b[0] >= 127) + ba[bi++] = 46; + else + ba[bi++] = b[0]; + + if(bi == 8) + { + Debug_printf(" %s (%d)\r\n", ba, i); + bi = 0; + } +#endif + // // Toggle LED + // if (0 == i % 50) + // { + // fnLedManager.toggle(eLed::LED_BUS); + // } + } while (not done); + } + // ostream->close(); // nor required, closes automagically + + Debug_printf("=================================\r\n%d bytes saved\r\n", i); + //fnLedManager.set(eLed::LED_BUS, false); + //fnLedStrip.stopRainbow(); + + // TODO: Handle errorFlag + + return success; +} // saveFile diff --git a/lib/device/iec/drive.h b/lib/device/iec/drive.h new file mode 100644 index 0000000..5403e58 --- /dev/null +++ b/lib/device/iec/drive.h @@ -0,0 +1,79 @@ + +#include +#include +#include + +#include "meat_io.h" +#include "meat_stream.h" + + +class IECData +{ +public: + /** + * @brief the primary command byte + */ + uint8_t primary = 0; + /** + * @brief the primary device number + */ + uint8_t device = 0; + /** + * @brief the secondary command byte + */ + uint8_t secondary = 0; + /** + * @brief the secondary command channel + */ + uint8_t channel = 0; + /** + * @brief the device command + */ + std::string payload = ""; + + /** + * @brief clear and initialize IEC command data + */ + void init(void) + { + primary = 0; + device = 0; + secondary = 0; + channel = 0; + payload = ""; + } +}; + +class iecDrive +{ +protected: + std::unique_ptr _base; // Always points to current directory/image + std::string _last_file; // Always points to last loaded file + +private: + + IECData commanddata; + + // Named Channel functions + //std::shared_ptr currentStream; + bool registerStream (uint8_t channel); + std::shared_ptr retrieveStream ( uint8_t channel ); + bool closeStream ( uint8_t channel, bool close_all = false ); + uint16_t retrieveLastByte ( uint8_t channel ); + void storeLastByte( uint8_t channel, char last); + void flushLastByte( uint8_t channel ); + + // send single basic line, including heading basic pointer and terminating zero. + uint16_t sendLine(uint16_t blocks, const char *format, ...); + uint16_t sendLine(uint16_t blocks, char *text); + uint16_t sendHeader(std::string header, std::string id); + uint16_t sendFooter(); + + std::unordered_map> streams; + std::unordered_map streamLastByte; + +public: + void sendListing(); + bool sendFile(); + bool saveFile(); +}; \ No newline at end of file diff --git a/lib/meatloaf/MIOException.h b/lib/meatloaf/MIOException.h new file mode 100644 index 0000000..e2292fa --- /dev/null +++ b/lib/meatloaf/MIOException.h @@ -0,0 +1,30 @@ +#ifndef MEATLOAF_EXCEPTION +#define MEATLOAF_EXCEPTION + +#include + +// truned off by default: https://github.com/platformio/platform-ststm32/issues/402 + +// PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS - whre do I set this? +// https://github.com/esp8266/Arduino/blob/master/tools/platformio-build.py + +struct IOException : public std::exception { + const char * what () const throw () { + return "IO"; + } +}; + +struct IllegalStateException : public IOException { + const char * what () const throw () { + return "Illegal State"; + } +}; + +struct FileNotFoundException : public IOException { + const char * what () const throw () { + return "Not found"; + } +}; + + +#endif // MEATLOAF_EXCEPTION \ No newline at end of file diff --git a/lib/meatloaf/archive/archive_ml.cpp b/lib/meatloaf/archive/archive_ml.cpp new file mode 100644 index 0000000..bb854db --- /dev/null +++ b/lib/meatloaf/archive/archive_ml.cpp @@ -0,0 +1,364 @@ + +#include "archive_ml.h" + +#include +#include + +#include "meat_io.h" + +/* Returns pointer and size of next block of data from archive. */ +// The read callback returns the number of bytes read, zero for end-of-file, or a negative failure code as above. +// It also returns a pointer to the block of data read. +// https://github.com/libarchive/libarchive/wiki/LibarchiveIO +// +// This callback is just a way to get bytes from srcStream into libarchive for processing +ssize_t cb_read(struct archive *a, void *userData, const void **buff) +{ + ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + // 1. we have to call srcStr.read(...) + ssize_t bc = streamData->srcStream->read(streamData->srcBuffer, ArchiveStream::buffSize); + //std::string dump((char*)streamData->srcBuffer, bc); + //Debug_printv("Libarch pulling data from src MStream, got bytes:%d", bc); + //Debug_printv("Dumping bytes: %s", dump.c_str()); + // 2. set *buff to the bufer read in 1. + *buff = streamData->srcBuffer; + // 3. return read bytes count + return bc; +} + +/* +It must return the number of bytes actually skipped, or a negative failure code if skipping cannot be done. +It can skip fewer bytes than requested but must never skip more. +Only positive/forward skips will ever be requested. +If skipping is not provided or fails, libarchive will call the read() function and simply ignore any data that it does not need. + +* Skips at most request bytes from archive and returns the skipped amount. +* This may skip fewer bytes than requested; it may even skip zero bytes. +* If you do skip fewer bytes than requested, libarchive will invoke your +* read callback and discard data as necessary to make up the full skip. +*/ +// https://github.com/libarchive/libarchive/wiki/LibarchiveIO +int64_t cb_skip(struct archive *a, void *userData, int64_t request) +{ + Debug_printv("bytes[%d]", request); + ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + + if (streamData->srcStream->isOpen()) + { + bool rc = streamData->srcStream->seek(request, SEEK_CUR); + return (rc) ? request : ARCHIVE_WARN; + } + else + { + Debug_printv("ERROR! skip failed"); + return ARCHIVE_FATAL; + } +} + +int64_t cb_seek(struct archive *a, void *userData, int64_t offset, int whence) +{ + Debug_printv("offset[%d] whence[%d] (0=begin, 1=curr, 2=end)", offset, whence); + ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + + if (streamData->srcStream->isOpen()) + { + bool rc = streamData->srcStream->seek(offset, whence); + return (rc) ? offset : ARCHIVE_WARN; + } + else + { + Debug_printv("ERROR! seek failed"); + return ARCHIVE_FATAL; + } +} + +int cb_close(struct archive *a, void *userData) +{ + ArchiveStreamData *src_str = (ArchiveStreamData *)userData; + + Debug_printv("Libarch wants to close, but we do nothing here..."); + + // do we want to close srcStream here??? + return (ARCHIVE_OK); +} + +int cb_open(struct a *arch, void *userData) +{ + // maybe we can use open for something? Check if stream is open? + return (ARCHIVE_OK); +} + + +/******************************************************** + * Streams implementations + ********************************************************/ + +ArchiveStream::ArchiveStream(std::shared_ptr srcStr) +{ + // it should be possible to to pass a password parameter here and somehow + // call archive_passphrase_callback(password) from here, right? + streamData.srcStream = srcStr; + a = archive_read_new(); + archive_read_support_filter_all(a); + archive_read_support_format_all(a); + streamData.srcBuffer = new uint8_t[buffSize]; + open(); +} + +ArchiveStream::~ArchiveStream() +{ + close(); + if (streamData.srcBuffer != nullptr) + delete[] streamData.srcBuffer; + Debug_printv("Stream destructor OK!"); +} + +bool ArchiveStream::open() +{ + if (!is_open) + { + // TODO enable seek only if the stream is random access + archive_read_set_read_callback(a, cb_read); + archive_read_set_skip_callback(a, cb_skip); + archive_read_set_seek_callback(a, cb_seek); + archive_read_set_close_callback(a, cb_close); + // archive_read_set_open_callback(mpa->arch, cb_open); - what does it do? + archive_read_set_callback_data(a, &streamData); + Debug_printv("== BEGIN Calling open1 on archive instance =========================="); + int r = archive_read_open1(a); + Debug_printv("== END opening archive result=%d! (OK should be 0!) =======================================", r); + + //int r = archive_read_open2(a, &streamData, NULL, myRead, myskip, myclose); + if (r == ARCHIVE_OK) + is_open = true; + } + return is_open; +}; + +void ArchiveStream::close() +{ + if (is_open) + { + archive_read_close(a); + archive_read_free(a); + is_open = false; + } + Debug_printv("Close called"); +} + +bool ArchiveStream::isOpen() +{ + return is_open; +}; + +std::vector leftovers; + +uint32_t ArchiveStream::read(uint8_t *buf, uint32_t size) +{ + Debug_printv("calling read size[%d]", size); + const void *incomingBuffer; + size_t incomingSize; + int64_t offset; + + int r = archive_read_data_block(a, &incomingBuffer, &incomingSize, &offset); + Debug_printv("r[%d]", r); + if ( r == ARCHIVE_EOF ) + return 0; + + if ( r < ARCHIVE_OK ) + return 0; + + // 'buff' contains the data of the current block + // 'size' is the size of the current block + + std::vector incomingVector((uint8_t*)incomingBuffer, (uint8_t*)incomingBuffer + incomingSize); + // concatenate intermediate buffer with incomingVector + leftovers.insert(leftovers.end(), incomingVector.begin(), incomingVector.end()); + + if(leftovers.size() <= size) { + // ok, we can fit everything that was left and new data to our buffer + auto size = leftovers.size(); + _position += size; + leftovers.clear(); + } + else { + // ok, so we can only write up to size and we have to keep leftovers for next time + std::copy(leftovers.begin(), leftovers.begin() + size, buf); + std::vector leftovers2(leftovers.begin() + size, leftovers.end()); + leftovers = leftovers2; + _position += size; + } + _position += size; + + Debug_printv("size[%d] position[%d]", size, _position); + + return size; +} + +uint32_t ArchiveStream::write(const uint8_t *buf, uint32_t size) +{ + return -1; +} + +// For files with a browsable random access directory structure +// d64, d74, d81, dnp, etc. +bool ArchiveStream::seekPath(std::string path) +{ + Debug_printv("seekPath called for path: %s", path.c_str()); + + if ( seekEntry( path ) ) + { + Debug_printv("entry[%s]", archive_entry_pathname(entry)); + return true; + } + + return false; +} + + +bool ArchiveStream::seekEntry( std::string filename ) +{ + Debug_printv( "filename[%s] size[%d]", filename.c_str(), filename.size()); + + // Read Directory Entries + if ( filename.size() > 0 ) + { + bool found = false; + bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); + while ( archive_read_next_header(a, &entry) == ARCHIVE_OK ) + { + std::string entryPath = ""; //archive_entry_sourcepath(entry); + std::string entryFilename = archive_entry_pathname(entry); + + Debug_printv("path[%s] filename[%s] entry.filename[%.16s]", entryPath.c_str(), filename.c_str(), entryFilename.c_str()); + + // Check filetype + if ( archive_entry_filetype(entry) != AE_IFDIR ) + { + // Read Entry From Stream + if (filename == "*") // Match first entry + { + filename = entryFilename; + found = true; + } + else if ( filename == entryFilename ) // Match exact + { + found = true; + } + else if ( wildcard ) + { + if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match + { + // Set filename to this filename + Debug_printv( "Found! file[%s] -> entry[%s]", filename.c_str(), entryFilename.c_str() ); + found = true; + } + } + + if ( found ) + { + _size = archive_entry_size(entry); + + return true; + } + } + } + + Debug_printv( "Not Found! file[%s]", filename.c_str() ); + } + + return false; +} + +// // For files with no directory structure +// // tap, crt, tar +// std::string ArchiveStream::seekNextEntry() +// { +// struct archive_entry *entry; +// if (archive_read_next_header(a, &entry) == ARCHIVE_OK) +// return archive_entry_pathname(entry); +// else +// return ""; +// }; + + +bool ArchiveStream::seek(uint32_t pos) +{ + return streamData.srcStream->seek(pos); +} + +/******************************************************** + * Files implementations + ********************************************************/ + +MStream *ArchiveContainerFile::getDecodedStream(std::shared_ptr containerIstream) +{ + // TODO - we can get password from this URL and pass it as a parameter to this constructor + Debug_printv("calling getDecodedStream for ArchiveContainerFile, we should return open stream"); + auto stream = new ArchiveStream(containerIstream); + return stream; +} + +// archive file is always a directory +bool ArchiveContainerFile::isDirectory() +{ + //Debug_printv("pathInStream[%s]", pathInStream.c_str()); + if ( pathInStream == "" ) + return true; + else + return false; +}; + +bool ArchiveContainerFile::rewindDirectory() +{ + dirIsOpen = true; + + return prepareDirListing(); +} + +MFile *ArchiveContainerFile::getNextFileInDir() +{ + if(!dirIsOpen) + rewindDirectory(); + + struct archive_entry *entry; + + Debug_printv("getNextFileInDir calling archive_read_next_header"); + if (archive_read_next_header(getArchive(), &entry) == ARCHIVE_OK) + { + std::string fileName = archive_entry_pathname(entry); + auto file = MFSOwner::File(streamFile->url + "/" + fileName); + file->_size = archive_entry_size(entry); + file->_exists = true; + return file; + } + else + { + //Debug_printv( "END OF DIRECTORY"); + dirStream->close(); + dirIsOpen = false; + return nullptr; + } +} + +bool ArchiveContainerFile::prepareDirListing() +{ + if (dirStream.get() != nullptr) + { + dirStream->close(); + } + + Debug_printv("w prepare dir listing"); + + dirStream = std::shared_ptr(this->getSourceStream()); + + if(dirStream->isOpen()) + { + return true; + } + else + { + Debug_printv("opening Archive for dir nok"); + return false; + } +} diff --git a/lib/meatloaf/archive/archive_ml.h b/lib/meatloaf/archive/archive_ml.h new file mode 100644 index 0000000..0457371 --- /dev/null +++ b/lib/meatloaf/archive/archive_ml.h @@ -0,0 +1,181 @@ +// // .7Z, .ARC, .ARK, .BZ2, .GZ, .LHA, .LZH, .LZX, .RAR, .TAR, .TGZ, .XAR, .ZIP - libArchive for Meatloaf! +// // +// // https://stackoverflow.com/questions/22543179/how-to-use-libarchive-properly +// // https://libarchive.org/ + +// #ifndef MEATLOAF_ARCHIVE +// #define MEATLOAF_ARCHIVE + +// #include +// #include + +// #include "meat_io.h" + +// #include "../../../include/debug.h" + +// // TODO: check how we can use archive_seek_callback, archive_passphrase_callback etc. to our benefit! + +// /* Returns pointer and size of next block of data from archive. */ +// // The read callback returns the number of bytes read, zero for end-of-file, or a negative failure code as above. +// // It also returns a pointer to the block of data read. +// ssize_t cb_read(struct archive *a, void *__src_stream, const void **buff); + +// /* +// It must return the number of bytes actually skipped, or a negative failure code if skipping cannot be done. +// It can skip fewer bytes than requested but must never skip more. +// Only positive/forward skips will ever be requested. +// If skipping is not provided or fails, libarchive will call the read() function and simply ignore any data that it does not need. + +// * Skips at most request bytes from archive and returns the skipped amount. +// * This may skip fewer bytes than requested; it may even skip zero bytes. +// * If you do skip fewer bytes than requested, libarchive will invoke your +// * read callback and discard data as necessary to make up the full skip. +// */ +// int64_t cb_skip(struct archive *a, void *__src_stream, int64_t request); + +// int64_t cb_seek(struct archive *a, void *userData, int64_t offset, int whence); +// int cb_close(struct archive *a, void *__src_stream); + + +// /******************************************************** +// * Streams implementations +// ********************************************************/ + +// class ArchiveStreamData { +// public: +// uint8_t *srcBuffer = nullptr; +// std::shared_ptr srcStream = nullptr; // a stream that is able to serve bytes of this archive +// }; + +// class ArchiveStream : public MStream +// { +// bool is_open = false; + +// public: +// static const size_t buffSize = 256; // 4096; +// ArchiveStreamData streamData; +// struct archive *a; +// struct archive_entry *entry; + +// ArchiveStream(std::shared_ptr srcStr); +// ~ArchiveStream(); + +// bool open() override; +// void close() override; +// bool isOpen() override; + +// uint32_t read(uint8_t *buf, uint32_t size) override; +// uint32_t write(const uint8_t *buf, uint32_t size) override; + +// // For files with a browsable random access directory structure +// // d64, d74, d81, dnp, etc. +// bool seekPath(std::string path) override; + +// // For files with no directory structure +// // tap, crt, tar +// //std::string seekNextEntry() override; + +// virtual bool seek(uint32_t pos) override; + +// bool isRandomAccess() override { return true; }; +// bool seekEntry( std::string filename ); + +// protected: + + +// private: + +// }; + +// /******************************************************** +// * Files implementations +// ********************************************************/ + +// class ArchiveContainerFile : public MFile +// { +// struct archive *getArchive() { +// ArchiveStream* as = (ArchiveStream*)dirStream.get(); +// return as->a; +// } + +// std::shared_ptr dirStream = nullptr; // a stream that is able to serve bytes of this archive + +// public: +// ArchiveContainerFile(std::string path) : MFile(path) +// { +// // media_header = name; +// media_archive = name; +// }; + +// ~ArchiveContainerFile() +// { +// if (dirStream.get() != nullptr) +// { +// dirStream->close(); +// } +// } + +// std::string basepath = ""; + +// //MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override; // has to return OPENED stream +// MStream* getDecodedStream(std::shared_ptr src) override; + +// // archive file is always a directory +// bool isDirectory(); +// bool rewindDirectory() override; +// MFile *getNextFileInDir() override; + +// bool mkDir() override { return false; }; +// bool remove() override { return true; } +// bool rename(std::string dest) { return true; } + +// time_t getLastWrite() override { return 0; } +// time_t getCreationTime() override { return 0; } + +// private: +// bool prepareDirListing(); + +// bool isDir = true; +// bool dirIsOpen = false; +// }; + +// /******************************************************** +// * FS implementations +// ********************************************************/ + +// class ArchiveContainerFileSystem : public MFileSystem +// { +// MFile *getFile(std::string path) +// { +// return new ArchiveContainerFile(path); +// }; + +// public: +// ArchiveContainerFileSystem() : MFileSystem("arch") {} + +// bool handles(std::string fileName) +// { +// return byExtension( +// { +// ".7z", +// ".arc", +// ".ark", +// ".bz2", +// ".gz", +// ".lha", +// ".lzh", +// ".lzx", +// ".rar", +// ".tar", +// ".tgz", +// ".xar", +// ".zip" +// }, +// fileName +// ); +// } + +// private: +// }; + +// #endif // MEATLOAF_ARCHIVE \ No newline at end of file diff --git a/lib/meatloaf/archive/cvt.h b/lib/meatloaf/archive/cvt.h new file mode 100644 index 0000000..500016c --- /dev/null +++ b/lib/meatloaf/archive/cvt.h @@ -0,0 +1,4 @@ +// .CVT - A special type of GEOS file, used for moving them around (ConVerT containers) +// +// https://ist.uwaterloo.ca/~schepers/formats/CVT.TXT +// diff --git a/lib/meatloaf/archive/lbr.h b/lib/meatloaf/archive/lbr.h new file mode 100644 index 0000000..f5a2888 --- /dev/null +++ b/lib/meatloaf/archive/lbr.h @@ -0,0 +1,5 @@ +// .LBR - LiBRary containers +// +// https://ist.uwaterloo.ca/~schepers/formats/LBR.TXT +// https://github.com/talas/lbrtool +// \ No newline at end of file diff --git a/lib/meatloaf/archive/lha.h b/lib/meatloaf/archive/lha.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/archive/lnx.h b/lib/meatloaf/archive/lnx.h new file mode 100644 index 0000000..87f6ba2 --- /dev/null +++ b/lib/meatloaf/archive/lnx.h @@ -0,0 +1,4 @@ +// .LNX - LyNX containers +// +// https://ist.uwaterloo.ca/~schepers/formats/LNX.TXT +// diff --git a/lib/meatloaf/archive/lzh.h b/lib/meatloaf/archive/lzh.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/archive/sda.h b/lib/meatloaf/archive/sda.h new file mode 100644 index 0000000..ea0fb19 --- /dev/null +++ b/lib/meatloaf/archive/sda.h @@ -0,0 +1,4 @@ +// .SDA, .ARC - Self-Dissolving Archive +// +// https://ist.uwaterloo.ca/~schepers/formats/SDA.TXT +// \ No newline at end of file diff --git a/lib/meatloaf/archive/sfx.h b/lib/meatloaf/archive/sfx.h new file mode 100644 index 0000000..b2b3977 --- /dev/null +++ b/lib/meatloaf/archive/sfx.h @@ -0,0 +1,4 @@ +// .SFX, .LHA - Self-Extracting Archive +// +// https://ist.uwaterloo.ca/~schepers/formats/SFX.TXT +// diff --git a/lib/meatloaf/archive/spy.h b/lib/meatloaf/archive/spy.h new file mode 100644 index 0000000..8be5894 --- /dev/null +++ b/lib/meatloaf/archive/spy.h @@ -0,0 +1,4 @@ +// .SPY - SPYne containers +// +// https://ist.uwaterloo.ca/~schepers/formats/SPYNE.TXT +// diff --git a/lib/meatloaf/archive/wr3.h b/lib/meatloaf/archive/wr3.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/archive/wra.h b/lib/meatloaf/archive/wra.h new file mode 100644 index 0000000..81b0967 --- /dev/null +++ b/lib/meatloaf/archive/wra.h @@ -0,0 +1,4 @@ +// .WRA, .WR3 - Wraptor and Wraptor 3 +// +// https://ist.uwaterloo.ca/~schepers/formats/WRA-WR3.TXT +// diff --git a/lib/meatloaf/archive/zipcode.h b/lib/meatloaf/archive/zipcode.h new file mode 100644 index 0000000..acba79f --- /dev/null +++ b/lib/meatloaf/archive/zipcode.h @@ -0,0 +1,6 @@ +// ZipCode - Disk, File, SixPack +// +// https://ist.uwaterloo.ca/~schepers/formats/ZIP_DISK.TXT +// https://ist.uwaterloo.ca/~schepers/formats/ZIP_FILE.TXT +// https://ist.uwaterloo.ca/~schepers/formats/ZIP_SIX.TXT +// \ No newline at end of file diff --git a/lib/meatloaf/cartridge/crt.h b/lib/meatloaf/cartridge/crt.h new file mode 100644 index 0000000..e25804c --- /dev/null +++ b/lib/meatloaf/cartridge/crt.h @@ -0,0 +1,4 @@ +// .CRT - The CRT cartridge image format +// https://vice-emu.sourceforge.io/vice_17.html#SEC369 +// https://ist.uwaterloo.ca/~schepers/formats/CRT.TXT +// diff --git a/lib/meatloaf/cartridge/crt/easyfs.h b/lib/meatloaf/cartridge/crt/easyfs.h new file mode 100644 index 0000000..683ac06 --- /dev/null +++ b/lib/meatloaf/cartridge/crt/easyfs.h @@ -0,0 +1,4 @@ +// EasyFlash 3 Cart File System +// https://skoe.de/easyflash/develdocs/ +// https://bitbucket.org/skoe/easyflash/src/master/ +// diff --git a/lib/meatloaf/cartridge/crt/normal.h b/lib/meatloaf/cartridge/crt/normal.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/cbm_media.cpp b/lib/meatloaf/cbm_media.cpp new file mode 100644 index 0000000..e6ce007 --- /dev/null +++ b/lib/meatloaf/cbm_media.cpp @@ -0,0 +1,105 @@ +#include "cbm_media.h" + +// Utility Functions + +std::string CBMImageStream::decodeType(uint8_t file_type, bool show_hidden) +{ + //bool hide = false; + std::string type = file_type_label[ file_type & 0b00000111 ]; + //if ( file_type == 0 ) + // hide = true; + + switch ( file_type & 0b11000000 ) + { + case 0xC0: // Bit 6: Locked flag (Set produces "<" locked files) + type += "<"; + //hide = false; + break; + + case 0x00: + type += "*"; // Bit 7: Closed flag (Not set produces "*", or "splat" files) + //hide = true; + break; + } + + return type; +} + +/******************************************************** + * Istream impls + ********************************************************/ + +// std::string CBMImageStream::seekNextEntry() { +// // Implement this to skip a queue of file streams to start of next file and return its name +// // this will cause the next read to return bytes of "next" file in D64 image +// // might not have sense in this case, as D64 is kinda random access, not a stream. +// return ""; +// }; + +bool CBMImageStream::open() { + // return true if we were able to read the image and confirmed it is valid. + // it's up to you in what state the stream will be after open. Could be either: + // 1. EOF-like state (0 available) and the state will be cleared only after succesful seekNextEntry or seekPath + // 2. non-EOF-like state, and ready to send bytes of first file, because you did immediate seekNextEntry here + + return false; +}; + +void CBMImageStream::close() { + +}; + +uint32_t CBMImageStream::seekFileSize( uint8_t start_track, uint8_t start_sector ) +{ + // Calculate file size + seekSector(start_track, start_sector); + + size_t blocks = 0; + do + { + //Debug_printv("t[%d] s[%d]", t, s); + containerStream->read(&start_track, 1); + containerStream->read(&start_sector, 1); + blocks++; + if ( start_track > 0 ) + if ( !seekSector( start_track, start_sector ) ) + break; + } while ( start_track > 0 ); + blocks--; + return (blocks * (block_size - 2)) + start_sector - 1; +}; + + + +uint32_t CBMImageStream::write(const uint8_t *buf, uint32_t size) { + return -1; +} + +uint32_t CBMImageStream::read(uint8_t* buf, uint32_t size) { + uint32_t bytesRead = 0; + + //Debug_printv("seekCalled[%d]", seekCalled); + + if(seekCalled) { + // if we have the stream set to a specific file already, either via seekNextEntry or seekPath, return bytes of the file here + // or set the stream to EOF-like state, if whle file is completely read. + bytesRead = readFile(buf, size); + + } + else { + // seekXXX not called - just pipe image bytes, so it can be i.e. copied verbatim + bytesRead = containerStream->read(buf, size); + } + + _position += bytesRead; + + return bytesRead; +}; + +bool CBMImageStream::isOpen() { + + return _is_open; +}; + + +std::unordered_map ImageBroker::repo; diff --git a/lib/meatloaf/cbm_media.h b/lib/meatloaf/cbm_media.h new file mode 100644 index 0000000..ad19144 --- /dev/null +++ b/lib/meatloaf/cbm_media.h @@ -0,0 +1,218 @@ + +#ifndef MEATLOAF_CBM_MEDIA +#define MEATLOAF_CBM_MEDIA + +#include "meat_io.h" + +#include +#include +#include +#include + +#include "../../include/debug.h" + +#include "string_utils.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class CBMImageStream: public MStream { + +public: + CBMImageStream(std::shared_ptr is) { + containerStream = is; + _is_open = true; + has_subdirs = false; + } + + // MStream methods + bool open() override; + void close() override; + + ~CBMImageStream() { + //Debug_printv("close"); + close(); + } + + // MStream methods + bool isBrowsable() override { return false; }; + bool isRandomAccess() override { return true; }; + + // read = (size) => this.containerStream.read(size); + virtual uint8_t read() { + uint8_t b = 0; + containerStream->read( &b, 1 ); + return b; + } + // readUntil = (delimiter = 0x00) => this.containerStream.readUntil(delimiter); + virtual std::string readUntil( uint8_t delimiter = 0x00 ) + { + uint8_t b = 0, r = 0; + std::string bytes = ""; + do + { + r = containerStream->read( &b, 1 ); + if ( b != delimiter ) + bytes += b; + else + break; + } while ( r ); + + return bytes; + } + // readString = (size) => this.containerStream.readString(size); + virtual std::string readString( uint8_t size ) + { + uint8_t b[size]; + if ( containerStream->read( b, size ) ) + return std::string((char *)b); + + return std::string(); + } + // readStringUntil = (delimiter = 0x00) => this.containerStream.readStringUntil(delimiter); + virtual std::string readStringUntil( uint8_t delimiter = '\0' ) + { + uint8_t b[1]; + std::stringstream ss; + while( containerStream->read( b, 1 ) ) + { + if ( b[0] == delimiter ) + ss << b; + } + return ss.str(); + } + + // seek = (offset) => this.containerStream.seek(offset + this.media_header_size); + bool seek(uint32_t offset) override { return containerStream->seek(offset + media_header_size); } + // seekCurrent = (offset) => this.containerStream.seekCurrent(offset); + bool seekCurrent(uint32_t offset) { return containerStream->seek(offset); } + + bool seekPath(std::string path) override { return false; }; + std::string seekNextEntry() override { return ""; }; + + virtual uint32_t seekFileSize( uint8_t start_track, uint8_t start_sector ); + + uint32_t read(uint8_t* buf, uint32_t size) override; + uint32_t write(const uint8_t *buf, uint32_t size) override; + void reset() override { + seekCalled = false; + _position = 0; + _size = block_size; + //m_load_address = {0, 0}; + } + + bool isOpen() override; + std::string url; + +protected: + + bool seekCalled = false; + std::shared_ptr containerStream; + + bool _is_open = false; + + CBMImageStream* decodedStream; + + bool show_hidden = false; + + size_t media_header_size = 0x00; + size_t entry_index = 0; // Currently selected directory entry + size_t entry_count = -1; // Directory list entry count (-1 unknown) + + enum open_modes { OPEN_READ, OPEN_WRITE, OPEN_APPEND, OPEN_MODIFY }; + std::string file_type_label[12] = { "DEL", "SEQ", "PRG", "USR", "REL", "CBM", "DIR", "SUS", "NAT", "CMD", "CFS", "???" }; + + virtual void seekHeader() = 0; + virtual bool seekNextImageEntry() = 0; + void resetEntryCounter() { + entry_index = 0; + } + + // Disks + virtual uint16_t blocksFree() { return 0; }; + virtual uint8_t speedZone( uint8_t track) { return 0; }; + + virtual bool seekEntry( std::string filename ) { return false; }; + virtual bool seekEntry( uint16_t index ) { return false; }; + + virtual uint16_t readFile(uint8_t* buf, uint16_t size) = 0; + std::string decodeType(uint8_t file_type, bool show_hidden = false); + +private: + + // Commodore Media + // FILE + friend class P00File; + + // FLOPPY DISK + friend class D64File; + friend class D71File; + friend class D80File; + friend class D81File; + friend class D82File; + + // HARD DRIVE + friend class DNPFile; + friend class D90File; + + // MEDIA ARCHIVE + friend class D8BFile; + friend class DFIFile; + + // CASSETTE TAPE + friend class T64File; + friend class TCRTFile; + + // CARTRIDGE + friend class CRTFile; +}; + + + +/******************************************************** + * Utility implementations + ********************************************************/ +class ImageBroker { + static std::unordered_map repo; +public: + template static T* obtain(std::string url) { + // obviously you have to supply STREAMFILE.url to this function! + if(repo.find(url)!=repo.end()) { + return (T*)repo.at(url); + } + + // create and add stream to broker if not found + auto newFile = MFSOwner::File(url); + T* newStream = (T*)newFile->getSourceStream(); + + // Are we at the root of the pathInStream? + if ( newFile->pathInStream == "") + { + Debug_printv("DIRECTORY [%s]", url.c_str()); + } + else + { + Debug_printv("SINGLE FILE [%s]", url.c_str()); + } + + repo.insert(std::make_pair(url, newStream)); + delete newFile; + return newStream; + } + + static CBMImageStream* obtain(std::string url) { + return obtain(url); + } + + static void dispose(std::string url) { + if(repo.find(url)!=repo.end()) { + auto toDelete = repo.at(url); + repo.erase(url); + delete toDelete; + } + } +}; + +#endif // MEATLOAF_CBM_MEDIA diff --git a/lib/meatloaf/container/d8b.cpp b/lib/meatloaf/container/d8b.cpp new file mode 100644 index 0000000..491a3d0 --- /dev/null +++ b/lib/meatloaf/container/d8b.cpp @@ -0,0 +1,11 @@ +#include "d8b.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D8BFile::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new D8BIStream(containerIstream); +} diff --git a/lib/meatloaf/container/d8b.h b/lib/meatloaf/container/d8b.h new file mode 100644 index 0000000..0919dc1 --- /dev/null +++ b/lib/meatloaf/container/d8b.h @@ -0,0 +1,112 @@ +// .D8B - Backbit D8B disk format +// +// https://www.backbit.io/downloads/Docs/BackBit%20Cartridge%20Documentation.pdf#page=20 +// https://github.com/evietron/BackBit-Tool +// + +#ifndef MEATLOAF_MEDIA_D8B +#define MEATLOAF_MEDIA_D8B + +#include "meat_io.h" +#include "disk/d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D8BIStream : public D64IStream { + // override everything that requires overriding here + +public: + D8BIStream(std::shared_ptr is) : D64IStream(is) + { + // D8B Partition Info + std::vector b = { + { + 1, // track + 1, // sector + 0x00, // offset + 1, // start_track + 40, // end_track + 18 // byte_count + } + }; + + Partition p = { + 1, // track + 0, // sector + 0x04, // header_offset + 1, // directory_track + 4, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 136 }; + + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + case 1392640: // 136 sectors per track (deprecated) + break; + + case 1474560: // 144 sectors per track + sectorsPerTrack = { 144 }; + break; + } + }; + + // virtual std::unordered_map info() override { + // return { + // {"System", "Commodore"}, + // {"Format", "D8B"}, + // {"Media Type", "ARCHIVE"}, + // {"Tracks", getTrackCount()}, + // {"Sectors / Blocks", this.getSectorCount()}, + // {"Sector / Block Size", std::string(block_size)}, + // {"Format", "Backbit Archive"} + // }; + // }; + +protected: + +private: + friend class D8BFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D8BFile: public D64File { +public: + D8BFile(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D8BFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D8BFile(path); + } + + bool handles(std::string fileName) override { + return byExtension(".d8b", fileName); + } + + D8BFileSystem(): MFileSystem("d8b") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D8B */ diff --git a/lib/meatloaf/container/dfi.cpp b/lib/meatloaf/container/dfi.cpp new file mode 100644 index 0000000..37dbdf4 --- /dev/null +++ b/lib/meatloaf/container/dfi.cpp @@ -0,0 +1,11 @@ +#include "dfi.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* DFIFile::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new DFIIStream(containerIstream); +} diff --git a/lib/meatloaf/container/dfi.h b/lib/meatloaf/container/dfi.h new file mode 100644 index 0000000..bd6321d --- /dev/null +++ b/lib/meatloaf/container/dfi.h @@ -0,0 +1,204 @@ +// .DFI - DreamLoad File Archive +// +// https://www.lemon64.com/forum/viewtopic.php?t=37415#458552 +// + +// +// The image starts with track 1. The maximum possible tracknumber is $ff. Every +// track has a fixed size of 256 sectors or 64kBytes. The sectors start at $00 +// and the last sector in a track is $ff. This means the offset for a block at +// track t and sector s is: +// +// offset = (t-1)*$10000 + s*$100 +// +// Here's an example dfi header for a v1.0 dfi image: +// +// 000000 00 44 52 45 41 4d 4c 4f 41 44 20 46 49 4c 45 20 |.DREAMLOAD FILE | +// 000010 41 52 43 48 49 56 45 00 00 00 01 00 02 00 00 00 |ARCHIVE.........| +// 000020 01 01 01 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| +// 000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +// 000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +// 000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +// 000060 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 000070 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 000080 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 000090 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000a0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000b0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000c0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000d0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000e0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// 0000f0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | +// +// The header's size is 256 bytes, that's exactly one sector. The header is +// always the first sector in the image (track 1, sector 0). +// +// $00 - $17 : magic (0x00, dreamload file archive, 0x00) +// $18 - $1b : version (bit 0-15: minor, 16-31: major. $00010000 is v1.0) +// $1c - $1f : tracks in this image (a track has always 256 sectors) +// $20 - $21 : root dir track and sektor +// $22 - $23 : bam track and sector +// $24 - $5f : reserved +// $60 - $ff : comment or notes +// +// The BAM's size is always 8192 bytes. It is one continuous block which begins +// at the offset specified in the header (see bytes $22 and $23). 'continuous' +// means there is no link information in the first 2 bytes of a sector. All 8192 +// bytes are BAM information. +// +// The bits in the BAM are in ascending order. This means byte 0, bit #0 is the +// bit for track 1, sector 0: +// +// offset bit track sector +// 0 0 1 0 +// 0 1 1 1 +// ... +// 1 0 1 8 +// ... +// +// A directory consists of a header and the directory sectors. It's very similar +// to the 1541, but the DFI directory header has less info than the 1541 +// counterpart. +// +// Please do not assume that the directory sectors follow the directory header +// immediately. All sectors for a directory are allocated like sectors for a +// normal file and can be scattered over the whole image if neccessary. +// +// A directory header: +// $00 - $01 : first directory block, track and sector +// $02 : format typ 'M' +// $03 - $8f : reserved, should be 0 +// $90 - $9f : directory name +// $a0 - $a1 : shift spaces ($a0) +// $a2 - $a6 : directory id +// $a7 - $aa : shift spaces ($a0) +// $ab - $ac : root directory track and sector +// $ad - $ae : parent directory track and sector +// $af - $ff : reserved, should be 0 +// +// The image format was designed with subdirectories in mind, but I did not find +// the time yet to implement this in the tools. The idea is that a directory +// entry with type $86 points to the directory header of the subdir (the $86 +// comes from the CMD HD). Every directory, even the root dir, has the link to +// the root directory header at offset $ab-$ac. All non-root directories have a +// link to the parent directory at offset $ad-$ae. The root directory has 0/0 +// there. +// +// For some example sources please download the patched cbmconvert +// sources "cbmconvert-2.1.2_dfi.tar.gz" from the retrohackers forum: +// http://retrohackers.org/forum/viewtopic.php?p=434#434 +// + +// https://cbm8bit.com/8bit/commodore/server/Unrenamed%20Achives/browse/c64/dfi + + +#ifndef MEATLOAF_MEDIA_DFI +#define MEATLOAF_MEDIA_DFI + +#include "meat_io.h" +#include "disk/d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class DFIIStream : public D64IStream { + // override everything that requires overriding here + +public: + DFIIStream(std::shared_ptr is) : D64IStream(is) + { + // DFI Partition Info + std::vector b = { + { + 1, // track + 0, // sector + 0x00, // offset + 1, // start_track + 40, // end_track + 4 // byte_count + } + }; + + Partition p = { + 1, // track + 0, // sector + 0x90, // header_offset + 1, // directory_track + 4, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 255 }; + + // // The header's size is 256 bytes, that's exactly one sector. The header is + // // always the first sector in the image (track 1, sector 0). + // // + // // $00 - $17 : magic (0x00, dreamload file archive, 0x00) + // // $18 - $1b : version (bit 0-15: minor, 16-31: major. $00010000 is v1.0) + // // $1c - $1f : tracks in this image (a track has always 256 sectors) + // // $20 - $21 : root dir track and sektor + // // $22 - $23 : bam track and sector + // // $24 - $5f : reserved + // // $60 - $ff : comment or notes + // this.seek(0x20); + seek ( 0x20 ); + // this.partitions[0].track = this.read(); + partitions[0].header_track = read(); + // this.partitions[0].sector = this.read(); + partitions[0].header_sector = read(); + // this.partitions[0].block_allocation_map[0].track = this.read(); + partitions[0].block_allocation_map[0].track = read(); + // this.partitions[0].block_allocation_map[0].sector = this.read(); + partitions[0].block_allocation_map[0].sector = read(); + + // this.partitions[0].directory_track = this.partitions[0].track; + partitions[0].directory_track = partitions[0].header_track; + // this.partitions[0].directory_sector = this.partitions[0].sector + 1; + partitions[0].directory_sector = partitions[0].header_sector; + }; + + +protected: + +private: + friend class DFIFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class DFIFile: public D64File { +public: + DFIFile(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class DFIFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new DFIFile(path); + } + + bool handles(std::string fileName) override { + return byExtension(".dfi", fileName); + } + + DFIFileSystem(): MFileSystem("dfi") {}; +}; + + +#endif /* MEATLOAF_MEDIA_DFI */ diff --git a/lib/meatloaf/device/flash.cpp b/lib/meatloaf/device/flash.cpp new file mode 100644 index 0000000..659d395 --- /dev/null +++ b/lib/meatloaf/device/flash.cpp @@ -0,0 +1,505 @@ +#include "flash.h" + +#include +#include +#include +#include + +#include "../../../include/debug.h" +#include "peoples_url_parser.h" +#include "string_utils.h" + +/******************************************************** + * MFileSystem implementations + ********************************************************/ + +bool FlashFileSystem::handles(std::string path) +{ + return true; // fallback fs, so it must be last on FS list +} + +MFile* FlashFileSystem::getFile(std::string path) +{ + //Debug_printv("path[%s]", path.c_str()); + return new FlashFile(path); +} + + +/******************************************************** + * MFile implementations + ********************************************************/ + +bool FlashFile::pathValid(std::string path) +{ + auto apath = std::string(basepath + path).c_str(); + while (*apath) { + const char *slash = strchr(apath, '/'); + if (!slash) { + if (strlen(apath) >= FILENAME_MAX) { + // Terminal filename is too long + return false; + } + break; + } + if ((slash - apath) >= FILENAME_MAX) { + // This subdir name too long + return false; + } + apath = slash + 1; + } + + return true; +} + +bool FlashFile::isDirectory() +{ + if(path=="/" || path=="") + return true; + + struct stat info; + stat( std::string(basepath + path).c_str(), &info); + return S_ISDIR(info.st_mode); +} + + +MStream* FlashFile::getSourceStream(std::ios_base::openmode mode) +{ + std::string full_path = basepath + path; + MStream* istream = new FlashIStream(full_path, mode); + //Debug_printv("FlashFile::getSourceStream() 3, not null=%d", istream != nullptr); + istream->open(); + //Debug_printv("FlashFile::getSourceStream() 4"); + return istream; +} + +MStream* FlashFile::getDecodedStream(std::shared_ptr is) { + return is.get(); // we don't have to process this stream in any way, just return the original stream +} + +time_t FlashFile::getLastWrite() +{ + struct stat info; + stat( std::string(basepath + path).c_str(), &info); + + time_t ftime = info.st_mtime; // Time of last modification + return ftime; +} + +time_t FlashFile::getCreationTime() +{ + struct stat info; + stat( std::string(basepath + path).c_str(), &info); + + time_t ftime = info.st_ctime; // Time of last status change + return ftime; +} + +bool FlashFile::mkDir() +{ + if (m_isNull) { + return false; + } + int rc = mkdir(std::string(basepath + path).c_str(), ALLPERMS); + return (rc==0); +} + +bool FlashFile::exists() +{ + if (m_isNull) { + return false; + } + if (path=="/" || path=="") { + return true; + } + + //Debug_printv( "basepath[%s] path[%s]", basepath.c_str(), path.c_str() ); + + struct stat st; + int i = stat(std::string(basepath + path).c_str(), &st); + + return (i == 0); +} + +uint32_t FlashFile::size() { + if (m_isNull || path=="/" || path=="") + return 0; + else if(isDirectory()) { + return 0; + } + else { + struct stat info; + stat( std::string(basepath + path).c_str(), &info); + // Debug_printv( "size[%d]", info.st_size ); + return info.st_size; + } +} + +bool FlashFile::remove() { + // musi obslugiwac usuwanie plikow i katalogow! + if(path.empty()) + return false; + + int rc = ::remove( std::string(basepath + path).c_str() ); + if (rc != 0) { + Debug_printv("remove: rc=%d path=`%s`\r\n", rc, path.c_str()); + return false; + } + + return true; +} + + +bool FlashFile::rename(std::string pathTo) { + if(pathTo.empty()) + return false; + + int rc = ::rename( std::string(basepath + path).c_str(), std::string(basepath + pathTo).c_str() ); + if (rc != 0) { + return false; + } + return true; +} + + +void FlashFile::openDir(std::string path) +{ + if (!isDirectory()) { + dirOpened = false; + return; + } + + // Debug_printv("path[%s]", apath.c_str()); + if(path.empty()) { + dir = opendir( "/" ); + } + else { + dir = opendir( path.c_str() ); + } + + dirOpened = true; + if ( dir == NULL ) { + dirOpened = false; + } + // else { + // // Skip the . and .. entries + // struct dirent* dirent = NULL; + // dirent = readdir( dir ); + // dirent = readdir( dir ); + // } +} + + +void FlashFile::closeDir() +{ + if(dirOpened) { + closedir( dir ); + dirOpened = false; + } +} + + +bool FlashFile::rewindDirectory() +{ + _valid = false; + rewinddir( dir ); + + // // Skip the . and .. entries + // struct dirent* dirent = NULL; + // dirent = readdir( dir ); + // dirent = readdir( dir ); + + return (dir != NULL) ? true: false; +} + + +MFile* FlashFile::getNextFileInDir() +{ + // Debug_printv("base[%s] path[%s]", basepath.c_str(), path.c_str()); + if(!dirOpened) + openDir(std::string(basepath + path).c_str()); + + if(dir == nullptr) + return nullptr; + + // Debug_printv("before readdir(), dir not null:%d", dir != nullptr); + struct dirent* dirent = NULL; + do + { + dirent = readdir( dir ); + } while ( dirent != NULL && mstr::startsWith(dirent->d_name, ".") ); // Skip hidden files + + if ( dirent != NULL ) + { + //Debug_printv("path[%s] name[%s]", this->path.c_str(), dirent->d_name); + std::string entry_name = this->path + ((this->path == "/") ? "" : "/") + std::string(dirent->d_name); + return new FlashFile( entry_name ); + } + else + { + closeDir(); + return nullptr; + } +} + + +bool FlashFile::seekEntry( std::string filename ) +{ + std::string apath = (basepath + pathToFile()).c_str(); + if (apath.empty()) { + apath = "/"; + } + + Debug_printv( "path[%s] filename[%s] size[%d]", apath.c_str(), filename.c_str(), filename.size()); + + DIR* d = opendir( apath.c_str() ); + if(d == nullptr) + return false; + + // Read Directory Entries + if ( filename.size() > 0 ) + { + struct dirent* dirent = NULL; + bool found = false; + bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); + while ( (dirent = readdir( d )) != NULL ) + { + std::string entryFilename = dirent->d_name; + + Debug_printv("path[%s] filename[%s] entry.filename[%.16s]", apath.c_str(), filename.c_str(), entryFilename.c_str()); + + // Read Entry From Stream + if ( dirent->d_type != DT_DIR ) // Only want to match files not directories + { + // Read Entry From Stream + if (filename == "*") // Match first entry + { + filename = entryFilename; + found = true; + } + else if ( filename == entryFilename ) // Match exact + { + found = true; + } + else if ( wildcard ) + { + if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match + { + // Set filename to this filename + Debug_printv( "Found! file[%s] -> entry[%s]", filename.c_str(), entryFilename.c_str() ); + resetURL(apath + "/" + entryFilename); + found = true; + } + } + + if ( found ) + { + _exists = true; + closedir( d ); + return true; + } + } + } + + Debug_printv( "Not Found! file[%s]", filename.c_str() ); + } + + closedir( d ); + return false; +} + + + +/******************************************************** + * MStream implementations + ********************************************************/ +uint32_t FlashIStream::write(const uint8_t *buf, uint32_t size) { + if (!isOpen() || !buf) { + return 0; + } + + Debug_printv("in byteWrite '%c', handle->file_h is null=[%d]\r\n", buf[0], handle->file_h == nullptr); + + // buffer, element size, count, handle + int result = fwrite((void*) buf, 1, size, handle->file_h ); + + Debug_printv("after write rc=%d\r\n", result); + return result; +}; + + +/******************************************************** + * MIStreams implementations + ********************************************************/ + + +bool FlashIStream::open() { + if(isOpen()) + return true; + + //Debug_printv("IStream: trying to open flash fs, calling isOpen"); + + //Debug_printv("IStream: wasn't open, calling obtain"); + if(mode == std::ios_base::in) + handle->obtain(localPath, "r"); + else if(mode == std::ios_base::out) { + Debug_printv("FlashIStream: ok, we are in write mode!"); + handle->obtain(localPath, "w"); + } + else if(mode == std::ios_base::app) + handle->obtain(localPath, "a"); + else if(mode == (std::ios_base::in | std::ios_base::out)) + handle->obtain(localPath, "r+"); + else if(mode == (std::ios_base::in | std::ios_base::app)) + handle->obtain(localPath, "a+"); + else if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc)) + handle->obtain(localPath, "w+"); + else if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app)) + handle->obtain(localPath, "a+"); + + // The below code will definitely destroy whatever open above does, because it will move the file pointer + // so I just wrapped it to be called only for in + if(isOpen() && mode == std::ios_base::in) { + //Debug_printv("IStream: past obtain"); + // Set file size + fseek(handle->file_h, 0, SEEK_END); + //Debug_printv("IStream: past fseek 1"); + _size = ftell(handle->file_h); + _position = 0; + //Debug_printv("IStream: past ftell"); + fseek(handle->file_h, 0, SEEK_SET); + //Debug_printv("IStream: past fseek 2"); + return true; + } + return false; +}; + +void FlashIStream::close() { + if(isOpen()) handle->dispose(); +}; + +uint32_t FlashIStream::read(uint8_t* buf, uint32_t size) { + if (!isOpen() || !buf) { + Debug_printv("Not open"); + return 0; + } + + uint32_t bytesRead = 0; + if ( size > available() ) + size = available(); + + if ( size > 0 ) + { + bytesRead = fread((void*) buf, 1, size, handle->file_h ); + // Debug_printv("bytesRead[%d]", bytesRead); + // auto hex = mstr::toHex(buf, bytesRead); + // Debug_printv("[%s]", hex.c_str()); + _position += bytesRead; + } + + return bytesRead; +}; + + +// uint32_t FlashIStream::size() { +// return _size; +// }; + +// uint32_t FlashIStream::available() { +// if(!isOpen()) return 0; +// return _size - position(); +// }; + + +// uint32_t FlashIStream::position() { +// if(!isOpen()) return 0; +// return ftell(handle->file_h); +// }; + +// size_t FlashIStream::error() { +// return 0; +// }; + +bool FlashIStream::seek(uint32_t pos) { + // Debug_printv("pos[%d]", pos); + if (!isOpen()) { + Debug_printv("Not open"); + return false; + } + return ( fseek( handle->file_h, pos, SEEK_SET ) ) ? false : true; +}; + +bool FlashIStream::seek(uint32_t pos, int mode) { + // Debug_printv("pos[%d] mode[%d]", pos, mode); + if (!isOpen()) { + Debug_printv("Not open"); + return false; + } + return ( fseek( handle->file_h, pos, mode ) ) ? false : true; +} + +bool FlashIStream::isOpen() { + // Debug_printv("Inside isOpen, handle notnull:%d", handle != nullptr); + auto temp = handle != nullptr && handle->file_h != nullptr; + // Debug_printv("returning"); + return temp; +} + +/******************************************************** + * FlashHandle implementations + ********************************************************/ + + +FlashHandle::~FlashHandle() { + dispose(); +} + +void FlashHandle::dispose() { + //Debug_printv("file_h[%d]", file_h); + if (file_h != nullptr) { + + fclose( file_h ); + file_h = nullptr; + // rc = -255; + } +} + +void FlashHandle::obtain(std::string m_path, std::string mode) { + + //Serial.printf("*** Atempting opening flash handle'%s'\r\n", m_path.c_str()); + + if ((mode[0] == 'w') && strchr(m_path.c_str(), '/')) { + // For file creation, silently make subdirs as needed. If any fail, + // it will be caught by the real file open later on + + char *pathStr = new char[m_path.length()]; + strncpy(pathStr, m_path.data(), m_path.length()); + + if (pathStr) { + // Make dirs up to the final fnamepart + char *ptr = strchr(pathStr, '/'); + while (ptr) { + *ptr = 0; + mkdir(pathStr, ALLPERMS); + *ptr = '/'; + ptr = strchr(ptr+1, '/'); + } + } + delete[] pathStr; + } + + //Debug_printv("m_path[%s] mode[%s]", m_path.c_str(), mode.c_str()); + file_h = fopen( m_path.c_str(), mode.c_str()); + // rc = 1; + + //Serial.printf("FSTEST: lfs_file_open file rc:%d\r\n",rc); + +// if (rc == LFS_ERR_ISDIR) { +// // To support the SD.openNextFile, a null FD indicates to the FlashFSFile this is just +// // a directory whose name we are carrying around but which cannot be read or written +// } else if (rc == 0) { +// // lfs_file_sync(&FlashFileSystem::lfsStruct, &file_h); +// } else { +// Debug_printv("FlashFile::open: unknown return code rc=%d path=`%s`\r\n", +// rc, m_path.c_str()); +// } +} diff --git a/lib/meatloaf/device/flash.h b/lib/meatloaf/device/flash.h new file mode 100644 index 0000000..ec5a19a --- /dev/null +++ b/lib/meatloaf/device/flash.h @@ -0,0 +1,167 @@ + +#ifndef MEATLOAF_DEVICE_FLASH +#define MEATLOAF_DEVICE_FLASH + +#include "meat_io.h" + +#include "make_unique.h" + +#include +#include + +#include "../../include/debug.h" + +/******************************************************** + * MFileSystem + ********************************************************/ + +class FlashFileSystem: public MFileSystem +{ + bool handles(std::string path); + +public: + FlashFileSystem() : MFileSystem("FlashFS") {}; + MFile* getFile(std::string path) override; + +}; + + + +/******************************************************** + * MFile + ********************************************************/ + +class FlashFile: public MFile +{ +friend class FlashIStream; + +public: + std::string basepath = ""; + + FlashFile(std::string path): MFile(path) { + // parseUrl( path ); + + // Find full filename for wildcard + if (mstr::contains(name, "?") || mstr::contains(name, "*")) + seekEntry( name ); + + if (!pathValid(path.c_str())) + m_isNull = true; + else + m_isNull = false; + + //Debug_printv("basepath[%s] path[%s] valid[%d]", basepath.c_str(), this->path.c_str(), m_isNull); + }; + ~FlashFile() { + //Serial.printf("*** Destroying flashfile %s\r\n", url.c_str()); + closeDir(); + } + + //MFile* cd(std::string newDir); + bool isDirectory() override; + MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED stream + MStream* getDecodedStream(std::shared_ptr src); + + bool rewindDirectory() override; + MFile* getNextFileInDir() override; + bool mkDir() override; + bool exists() override; + bool remove() override; + bool rename(std::string dest); + + time_t getLastWrite() override; + time_t getCreationTime() override; + uint32_t size() override; + + bool seekEntry( std::string filename ); + +protected: + DIR* dir; + bool dirOpened = false; + +private: + virtual void openDir(std::string path); + virtual void closeDir(); + + bool _valid; + std::string _pattern; + + bool pathValid(std::string path); +}; + + +/******************************************************** + * FlashHandle + ********************************************************/ + +class FlashHandle { +public: + //int rc; + FILE* file_h = nullptr; + + FlashHandle() + { + //Debug_printv("*** Creating flash handle"); + memset(&file_h, 0, sizeof(file_h)); + }; + ~FlashHandle(); + void obtain(std::string localPath, std::string mode); + void dispose(); + +private: + int flags = 0; +}; + + +/******************************************************** + * MStream I + ********************************************************/ + +class FlashIStream: public MStream { +public: + FlashIStream(std::string& path, std::ios_base::openmode m) { + localPath = path; + mode = m; + handle = std::make_unique(); + //url = path; + } + ~FlashIStream() override { + close(); + } + + // MStream methods + bool isBrowsable() override { return false; }; + bool isRandomAccess() override { return true; }; + + // MStream methods + // uint32_t available() override; + // uint32_t size() override; + // uint32_t position() override; + // size_t error() override; + + virtual bool seek(uint32_t pos) override; + virtual bool seek(uint32_t pos, int mode) override; + + void close() override; + bool open() override; + + // MStream methods + //uint8_t read() override; + uint32_t read(uint8_t* buf, uint32_t size) override; + uint32_t write(const uint8_t *buf, uint32_t size) override; + + virtual bool seekPath(std::string path) override { + Debug_printv( "path[%s]", path.c_str() ); + return false; + } + + bool isOpen(); + +protected: + std::string localPath; + + std::unique_ptr handle; +}; + + +#endif // MEATLOAF_DEVICE_FLASH diff --git a/lib/meatloaf/device/psram.cpp b/lib/meatloaf/device/psram.cpp new file mode 100644 index 0000000..fb00eb8 --- /dev/null +++ b/lib/meatloaf/device/psram.cpp @@ -0,0 +1,90 @@ +// /* Himem API example + +// This example code is in the Public Domain (or CC0 licensed, at your option.) + +// Unless required by applicable law or agreed to in writing, this +// software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// */ + +// #include "psram.h" + +// #include +// #include +// #include +// #include +// #include "freertos/FreeRTOS.h" +// #include "freertos/task.h" +// #include "freertos/queue.h" +// #include "esp_system.h" +// #include "nvs_flash.h" +// #include "esp_heap_caps.h" +// #include "sdkconfig.h" +// #include "esp32/himem.h" + + +// //Fill memory with pseudo-random data generated from the given seed. +// //Fills the memory in 32-bit words for speed. +// void HighMemory::fill_mem_seed(int seed, void *mem, int len) +// { +// uint32_t *p = (uint32_t *)mem; +// unsigned int rseed = seed ^ 0xa5a5a5a5; +// for (int i = 0; i < len / 4; i++) { +// *p++ = rand_r(&rseed); +// } +// } + +// //Check the memory filled by fill_mem_seed. Returns true if the data matches the data +// //that fill_mem_seed wrote (when given the same seed). +// //Returns true if there's a match, false when the region differs from what should be there. +// bool HighMemory::check_mem_seed(int seed, void *mem, int len, int phys_addr) +// { +// uint32_t *p = (uint32_t *)mem; +// unsigned int rseed = seed ^ 0xa5a5a5a5; +// for (int i = 0; i < len / 4; i++) { +// uint32_t ex = rand_r(&rseed); +// if (ex != *p) { +// printf("check_mem_seed: %x has 0x%08"PRIx32" expected 0x%08"PRIx32"\n", phys_addr+((char*)p-(char*)mem), *p, ex); +// return false; +// } +// p++; +// } +// return true; +// } + +// //Allocate a himem region, fill it with data, check it and release it. +// bool HighMemory::test_region(int check_size, int seed) +// { +// esp_himem_handle_t mh; //Handle for the address space we're using +// esp_himem_rangehandle_t rh; //Handle for the actual RAM. +// bool ret = true; + +// //Allocate the memory we're going to check. +// ESP_ERROR_CHECK(esp_himem_alloc(check_size, &mh)); +// //Allocate a block of address range +// ESP_ERROR_CHECK(esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &rh)); +// for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) { +// uint32_t *ptr = NULL; +// //Map in block, write pseudo-random data, unmap block. +// ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr)); +// fill_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ); // +// ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ)); +// } +// vTaskDelay(5); //give the OS some time to do things so the task watchdog doesn't bark +// for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) { +// uint32_t *ptr; +// //Map in block, check against earlier written pseudo-random data, unmap block. +// ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr)); +// if (!check_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ, i)) { +// printf("Error in block %d\n", i / ESP_HIMEM_BLKSZ); +// ret = false; +// } +// ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ)); +// if (!ret) break; //don't check rest of blocks if error occurred +// } +// //Okay, all done! +// ESP_ERROR_CHECK(esp_himem_free(mh)); +// ESP_ERROR_CHECK(esp_himem_free_map_range(rh)); +// return ret; +// } + diff --git a/lib/meatloaf/device/psram.h b/lib/meatloaf/device/psram.h new file mode 100644 index 0000000..3fee9db --- /dev/null +++ b/lib/meatloaf/device/psram.h @@ -0,0 +1,202 @@ +// /* Himem API example + +// This example code is in the Public Domain (or CC0 licensed, at your option.) + +// Unless required by applicable law or agreed to in writing, this +// software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// */ + +// #ifndef MEATLOAF_HIMEMFS_H +// #define MEATLOAF_HIMEMFS_H + +// #include +// #include +// #include +// #include +// #include "freertos/FreeRTOS.h" +// #include "freertos/task.h" +// #include "freertos/queue.h" +// #include "esp_system.h" +// #include "nvs_flash.h" +// #include "esp_heap_caps.h" +// #include "sdkconfig.h" +// #include "esp32/himem.h" +// #include "dirent.h" +// #include "FS.h" + +// #include +// #include +// #include +// #include + +// #include "meat_io.h" +// #include "meat_buffer.h" + +// class HighMemory +// { +// private: +// esp_himem_handle_t mh; // Handle for the address space we're using +// esp_himem_rangehandle_t rh; // Handle for the actual RAM. +// uint32_t *mem_ptr; // Memory pointer + +// protected: + +// public: +// //Fill memory with pseudo-random data generated from the given seed. +// //Fills the memory in 32-bit words for speed. +// static void fill_mem_seed(int seed, void *mem, int len); + +// //Check the memory filled by fill_mem_seed. Returns true if the data matches the data +// //that fill_mem_seed wrote (when given the same seed). +// //Returns true if there's a match, false when the region differs from what should be there. +// static bool check_mem_seed(int seed, void *mem, int len, int phys_addr); + +// //Allocate a himem region, fill it with data, check it and release it. +// static bool test_region(int check_size, int seed); + +// size_t size() { +// return esp_himem_get_phys_size(); +// } + +// size_t free() { +// return esp_himem_get_free_size(); +// } + +// size_t blocks() { +// return ESP_HIMEM_BLKSZ; +// } + +// size_t block_size() { +// return ESP_HIMEM_BLKSZ; +// } + +// bool open(int block) { +// esp_err_t r = esp_himem_map(mh, rh, block, 0, ESP_HIMEM_BLKSZ, 0, (void**)&mem_ptr); +// return r; +// } + +// std::string read(); +// std::string write(); + +// bool seek(); +// }; + +// // void app_main(void) +// // { +// // HighMemoryFileSystem hm; + +// // size_t memcnt = hm.size(); +// // size_t memfree = hm.free(); + +// // printf("Himem has %dKiB of memory, %dKiB of which is free. Testing the free memory...\n", (int)memcnt/1024, (int)memfree/1024); +// // assert(test_region(memfree, 0xaaaa)); +// // printf("Done!\n"); +// // } + + + +// /******************************************************** +// * MFileSystem +// ********************************************************/ + +// class HighMemoryFileSystem: public MFileSystem +// { +// bool handles(std::string path); + +// public: +// HighMemoryFileSystem() : MFileSystem("HighMemoryFS") {}; +// MFile* getFile(std::string path) override; + +// }; + + + +// /******************************************************** +// * MFile +// ********************************************************/ + +// class HighMemoryFile: public MFile +// { +// friend class FlashIStream; + +// public: +// std::string basepath = ""; + +// HighMemoryFile(std::string path): MFile(path) { +// // parseUrl( path ); + +// // Find full filename for wildcard +// if (mstr::contains(name, "?") || mstr::contains(name, "*")) +// seekEntry( name ); + +// if (!pathValid(path.c_str())) +// m_isNull = true; +// else +// m_isNull = false; + +// //Debug_printv("basepath[%s] path[%s] valid[%d]", basepath.c_str(), this->path.c_str(), m_isNull); +// }; +// ~HighMemoryFile() { +// //Serial.printf("*** Destroying flashfile %s\r\n", url.c_str()); +// closeDir(); +// } + +// //MFile* cd(std::string newDir); +// bool isDirectory() override; +// MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED stream +// MStream* getDecodedStream(std::shared_ptr src); + +// bool rewindDirectory() override; +// MFile* getNextFileInDir() override; +// bool mkDir() override; +// bool exists() override; +// bool remove() override; +// bool rename(std::string dest); + +// time_t getLastWrite() override; +// time_t getCreationTime() override; +// uint32_t size() override; + +// bool seekEntry( std::string filename ); + +// protected: +// DIR* dir; +// bool dirOpened = false; + +// private: +// virtual void openDir(std::string path); +// virtual void closeDir(); + +// bool _valid; +// std::string _pattern; + +// bool pathValid(std::string path); +// }; + + +// /******************************************************** +// * HighMemoryHandle +// ********************************************************/ + +// class HighMemoryHandle { +// public: +// //int rc; +// FILE* file_h = nullptr; + +// HighMemoryHandle() +// { +// //Debug_printv("*** Creating flash handle"); +// memset(&file_h, 0, sizeof(file_h)); +// }; +// ~HighMemoryHandle(); +// void obtain(std::string localPath, std::string mode); +// void dispose(); + +// private: +// int flags = 0; +// }; + + + +// #endif // MEATLOAF_HIMEM_FS \ No newline at end of file diff --git a/lib/meatloaf/device/sd.h b/lib/meatloaf/device/sd.h new file mode 100755 index 0000000..6b4fb87 --- /dev/null +++ b/lib/meatloaf/device/sd.h @@ -0,0 +1,49 @@ +// SD:// - Secure Digital Card File System +// https://en.wikipedia.org/wiki/SD_card +// https://github.com/arduino-libraries/SD +// +#ifndef TEST_NATIVE +#ifndef MEATLOAF_DEVICE_SD +#define MEATLOAF_DEVICE_SD + +#include "meat_io.h" + +#include "flash.h" +#include "fnFsSD.h" + +#include "peoples_url_parser.h" + +#include +#include + +#define _filesystem fnSDFAT + + +/******************************************************** + * MFileSystem + ********************************************************/ + +class SDFileSystem: public MFileSystem +{ +private: + MFile* getFile(std::string path) override { + PeoplesUrlParser *url = PeoplesUrlParser::parseURL( path ); + + std::string basepath = fnSDFAT.basepath(); + basepath += std::string("/"); + //Debug_printv("basepath[%s] url.path[%s]", basepath.c_str(), url.path.c_str()); + + return new FlashFile( url->path ); + } + + bool handles(std::string name) { + std::string pattern = "sd:"; + return mstr::equals(name, pattern, false); + } +public: + SDFileSystem(): MFileSystem("sd") {}; +}; + + +#endif // MEATLOAF_DEVICE_SD +#endif // TEST_NATIVE \ No newline at end of file diff --git a/lib/meatloaf/disk/d40.h b/lib/meatloaf/disk/d40.h new file mode 100644 index 0000000..3fe9293 --- /dev/null +++ b/lib/meatloaf/disk/d40.h @@ -0,0 +1,4 @@ +// .D40 - 2040, 3040 disk image format +// +// http://www.baltissen.org/newhtm/diskimag.htm +// diff --git a/lib/meatloaf/disk/d64.cpp b/lib/meatloaf/disk/d64.cpp new file mode 100644 index 0000000..aa5d9b4 --- /dev/null +++ b/lib/meatloaf/disk/d64.cpp @@ -0,0 +1,468 @@ + +#include "d64.h" + +#include "endianness.h" + +// D64 Utility Functions + +bool D64IStream::seekBlock( uint64_t index, uint8_t offset ) +{ + uint16_t sectorOffset = 0; + uint8_t track = 0; + + // Debug_printv("track[%d] sector[%d] offset[%d]", track, sector, offset); + + // Determine actual track & sector from index + do + { + track++; + uint8_t count = sectorsPerTrack[speedZone(track)]; + if ( sectorOffset + count < index ) + sectorOffset += count; + else + break; + + //Debug_printv("track[%d] speedZone[%d] secotorsPerTrack[%d] sectorOffset[%d]", track, speedZone(track), count, sectorOffset); + } while ( true ); + uint8_t sector = index - sectorOffset; + + this->block = index; + this->track = track; + this->sector = sector; + + //Debug_printv("track[%d] sector[%d] speedZone[%d] sectorOffset[%d]", track, sector, speedZone(track), sectorOffset); + + return containerStream->seek( (index * block_size) + offset ); +} + +bool D64IStream::seekSector( uint8_t track, uint8_t sector, uint8_t offset ) +{ + uint16_t sectorOffset = 0; + + //Debug_printv("track[%d] sector[%d] offset[%d]", track, sector, offset); + + // Is this a valid track? + uint8_t c = partitions[partition].block_allocation_map.size() - 1; + uint8_t start_track = partitions[partition].block_allocation_map[0].start_track; + uint8_t end_track = partitions[partition].block_allocation_map[c].end_track; + if ( track < start_track || track > end_track ) + { + Debug_printv("track[%d] start_track[%d] end_track[%d]", track, start_track, end_track); + return false; + } + + // Is this a valid sector? + c = sectorsPerTrack[speedZone(track)]; + if ( sector > c ) + { + Debug_printv("sector[%d] track[%d] sectorsPerTrack[%d]", sector, track, c); + return false; + } + + track--; + for (uint8_t index = 0; index < track; ++index) + { + sectorOffset += sectorsPerTrack[speedZone(index + 1)]; + // Debug_printv("track[%d] speedZone[%d] secotorsPerTrack[%d] sectorOffset[%d]", (index + 1), speedZone(index), sectorsPerTrack[speedZone(index)], sectorOffset); + } + track++; + sectorOffset += sector; + + this->block = sectorOffset; + this->track = track; + this->sector = sector; + + //Debug_printv("track[%d] sector[%d] speedZone[%d] sectorOffset[%d]", track, sector, speedZone(track), sectorOffset); + + return containerStream->seek( (sectorOffset * block_size) + offset ); +} + +bool D64IStream::seekSector( std::vector trackSectorOffset ) +{ + return seekSector(trackSectorOffset[0], trackSectorOffset[1], trackSectorOffset[2]); +} + + +std::string D64IStream::readBlock(uint8_t track, uint8_t sector) +{ + return ""; +} + +bool D64IStream::writeBlock(uint8_t track, uint8_t sector, std::string data) +{ + return true; +} + +bool D64IStream::allocateBlock( uint8_t track, uint8_t sector) +{ + return true; +} + +bool D64IStream::deallocateBlock( uint8_t track, uint8_t sector) +{ + return true; +} + +bool D64IStream::seekEntry( std::string filename ) +{ + uint32_t index = 1; + mstr::replaceAll(filename, "\\", "/"); + bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); + + // Read Directory Entries + if ( filename.size() ) + { + while ( seekEntry( index ) ) + { + std::string entryFilename = entry.filename; + mstr::rtrimA0(entryFilename); + entryFilename = mstr::toUTF8(entryFilename); + + Debug_printv("index[%d] track[%d] sector[%d] filename[%s] entry.filename[%.16s]", index, track, sector, filename.c_str(), entryFilename.c_str()); + + //Debug_printv("filename[%s] entry[%s]", filename.c_str(), entryFilename.c_str()); + + // Read Entry From Stream + if (entry.file_type & 0b00000111 && filename == "*") // Match first PRG + { + filename = entryFilename; + return true; + } + else if ( filename == entryFilename ) // Match exact + { + return true; + } + else if ( wildcard ) + { + if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match + { + // Move stream pointer to start track/sector + return true; + } + } + + index++; + } + + Debug_printv("File not found!"); + } + + entry.next_track = 0; + entry.next_sector = 0; + entry.blocks = 0; + entry.filename[0] = '\0'; + + return false; +} + +bool D64IStream::seekEntry( uint16_t index ) +{ + // Calculate Sector offset & Entry offset + // 8 Entries Per Sector, 32 bytes Per Entry + index--; + uint16_t sectorOffset = index / 8; + uint16_t entryOffset = (index % 8) * 32; + + // Debug_printv("----------"); + // Debug_printv("index[%d] sectorOffset[%d] entryOffset[%d] entry_index[%d]", index, sectorOffset, entryOffset, entry_index); + + + if (index == 0 || index != entry_index) + { + // Start at first sector of directory + next_track = 0; + if ( !seekSector( + partitions[partition].directory_track, + partitions[partition].directory_sector, + partitions[partition].directory_offset + ) + ) return false; + + // Find sector with requested entry + do + { + if ( next_track ) + { + Debug_printv("next_track[%d] next_sector[%d]", entry.next_track, entry.next_sector); + if ( !seekSector( entry.next_track, entry.next_sector ) ) + return false; + } + + containerStream->read((uint8_t *)&entry, sizeof(entry)); + next_track = entry.next_track; + next_sector = entry.next_sector; + + Debug_printv("sectorOffset[%d] -> track[%d] sector[%d]", sectorOffset, track, sector); + } while ( sectorOffset-- > 0 ); + if ( !seekSector( track, sector, entryOffset ) ) + return false; + } + else + { + if ( entryOffset == 0 ) + { + if ( next_track == 0 ) + return false; + + //Debug_printv("Follow link track[%d] sector[%d] entryOffset[%d]", next_track, next_sector, entryOffset); + if ( !seekSector( next_track, next_sector, entryOffset ) ) + return false; + } + } + + containerStream->read((uint8_t *)&entry, sizeof(entry)); + + // If we are at the first entry in the sector then get next_track/next_sector + if ( entryOffset == 0 ) + { + next_track = entry.next_track; + next_sector = entry.next_sector; + } + + // Debug_printv("r[%d] file_type[%02X] file_name[%.16s]", r, entry.file_type, entry.filename); + + //if ( next_track == 0 && next_sector == 0xFF ) + entry_index = index + 1; + + return true; +} + + +uint16_t D64IStream::blocksFree() +{ + uint16_t free_count = 0; + + for(uint8_t x = 0; x < partitions[partition].block_allocation_map.size(); x++) + { + uint8_t bam[partitions[partition].block_allocation_map[x].byte_count]; + //Debug_printv("start_track[%d] end_track[%d]", block_allocation_map[x].start_track, block_allocation_map[x].end_track); + + if ( !seekSector( + partitions[partition].block_allocation_map[x].track, + partitions[partition].block_allocation_map[x].sector, + partitions[partition].block_allocation_map[x].offset + ) + ) return 0; + + for(uint8_t i = partitions[partition].block_allocation_map[x].start_track; i <= partitions[partition].block_allocation_map[x].end_track; i++) + { + containerStream->read((uint8_t *)&bam, sizeof(bam)); + if ( sizeof(bam) > 3 ) + { + if ( i != partitions[partition].directory_track ) + { + //Debug_printv("x[%d] track[%d] count[%d] size[%d]", x, i, bam[0], sizeof(bam)); + free_count += bam[0]; + } + } + else + { + // D71 tracks 36 - 70 you have to count the 1 bits (0 is allocated) + uint8_t bit_count = 0; + bit_count += std::bitset<8>(bam[0]).count(); + bit_count += std::bitset<8>(bam[1]).count(); + bit_count += std::bitset<8>(bam[2]).count(); + + //Debug_printv("x[%d] track[%d] count[%d] size[%d] bam0[%d] bam1[%d] bam2[%d] (counting 1 bits)", x, i, bit_count, sizeof(bam), bam[0], bam[1], bam[2]); + free_count += bit_count; + } + } + } + + return free_count; +} + +uint16_t D64IStream::readFile(uint8_t* buf, uint16_t size) { + + if ( sector_offset % block_size == 0 ) + { + // We are at the beginning of the block + // Read track/sector link + containerStream->read((uint8_t *)&next_track, 1); + containerStream->read((uint8_t *)&next_sector, 1); + sector_offset += 2; + //Debug_printv("next_track[%d] next_sector[%d] sector_offset[%d]", next_track, next_sector, sector_offset); + } + + uint16_t bytesRead = 0; + if ( size > available() ) + size = available(); + + if ( size > 0 ) + { + bytesRead += containerStream->read(buf, size); + sector_offset += bytesRead; + + if ( sector_offset % block_size == 0 ) + { + // We are at the end of the block + // Follow track/sector link to move to next block + if ( !seekSector( next_track, next_sector ) ) + { + return 0; + } + //Debug_printv("track[%d] sector[%d] sector_offset[%d]", track, sector, sector_offset); + } + } + + // if ( !bytesRead ) + // { + // sector_offset = 0; + // } + + return bytesRead; +} + + + +bool D64IStream::seekPath(std::string path) { + // Implement this to skip a queue of file streams to start of file by name + // this will cause the next read to return bytes of 'path' + seekCalled = true; + + next_track = 0; + next_sector = 0; + sector_offset = 0; + + entry_index = 0; + + // call image method to obtain file bytes here, return true on success: + // return D64Image.seekFile(containerIStream, path); + if ( mstr::endsWith(path,"#") ) // Direct Access Mode + { + Debug_printv("Direct Access Mode track[1] sector[0] path[%s]", path.c_str()); + seekCalled = false; + return seekSector(1, 0); + } + else if ( seekEntry(path) ) + { + //auto entry = containerImage->entry; + auto type = decodeType(entry.file_type).c_str(); + Debug_printv("filename[%.16s] type[%s] start_track[%d] start_sector[%d]", entry.filename, type, entry.start_track, entry.start_sector); + + // Calculate file size + uint8_t t = entry.start_track; + uint8_t s = entry.start_sector; + _size = seekFileSize( t, s ); + + // Set position to beginning of file + bool r = seekSector( t, s ); + + Debug_printv("File Size: blocks[%d] size[%d] available[%d] r[%d]", entry.blocks, _size, available(), r); + + return r; + } + else + { + Debug_printv( "Not found! [%s]", path.c_str()); + } + + return false; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D64File::getDecodedStream(std::shared_ptr containerIstream) { + // Debug_printv("[%s]", url.c_str()); + + return new D64IStream(containerIstream); +} + +bool D64File::isDirectory() { + //Debug_printv("pathInStream[%s]", pathInStream.c_str()); + if ( pathInStream == "" ) + return true; + else + return false; +}; + +bool D64File::rewindDirectory() { + dirIsOpen = true; + //Debug_printv("streamFile->url[%s]", streamFile->url.c_str()); + auto image = ImageBroker::obtain(streamFile->url); + if ( image == nullptr ) + Debug_printv("image pointer is null"); + + image->resetEntryCounter(); + + // Read Header + image->seekHeader(); + + // Set Media Info Fields + media_header = mstr::format("%.16s", image->header.disk_name); + mstr::A02Space(media_header); + media_id = mstr::format("%.5s", image->header.id_dos); + mstr::A02Space(media_id); + media_blocks_free = image->blocksFree(); + media_block_size = image->block_size; + media_image = name; + //mstr::toUTF8(media_image); + + return true; +} + +MFile* D64File::getNextFileInDir() { + + if(!dirIsOpen) + rewindDirectory(); + + // Get entry pointed to by containerStream + auto image = ImageBroker::obtain(streamFile->url); + + bool r = false; + do + { + r = image->seekNextImageEntry(); + } while ( r && image->entry.file_type == 0x00 ); // Skip hidden files + + if ( r ) + { + std::string fileName = image->entry.filename; + //mstr::rtrimA0(fileName); + mstr::replaceAll(fileName, "/", "\\"); + //Debug_printv( "entry[%s]", (streamFile->url + "/" + fileName).c_str() ); + auto file = MFSOwner::File(streamFile->url + "/" + fileName); + file->extension = image->decodeType(image->entry.file_type); + return file; + } + else + { + //Debug_printv( "END OF DIRECTORY"); + dirIsOpen = false; + return nullptr; + } +} + +time_t D64File::getLastWrite() { + return getCreationTime(); +} + +time_t D64File::getCreationTime() { + tm *entry_time = 0; + auto entry = ImageBroker::obtain(streamFile->url)->entry; + entry_time->tm_year = entry.year + 1900; + entry_time->tm_mon = entry.month; + entry_time->tm_mday = entry.day; + entry_time->tm_hour = entry.hour; + entry_time->tm_min = entry.minute; + + return mktime(entry_time); +} + +bool D64File::exists() { + // here I'd rather use D64 logic to see if such file name exists in the image! + //Debug_printv("here"); + return true; +} + +uint32_t D64File::size() { + //Debug_printv("[%s]", streamFile->url.c_str()); + // use D64 to get size of the file in image + auto entry = ImageBroker::obtain(streamFile->url)->entry; + uint32_t bytes = UINT16_FROM_LE_UINT16(entry.blocks); + + return bytes; +} diff --git a/lib/meatloaf/disk/d64.h b/lib/meatloaf/disk/d64.h new file mode 100644 index 0000000..c55daae --- /dev/null +++ b/lib/meatloaf/disk/d64.h @@ -0,0 +1,341 @@ +// .D64, .D41 - 1541 disk image format +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC345 +// https://ist.uwaterloo.ca/~schepers/formats/D64.TXT +// https://ist.uwaterloo.ca/~schepers/formats/GEOS.TXT +// https://www.lemon64.com/forum/viewtopic.php?t=70024&start=0 (File formats = Why is D64 not called D40/D41) +// - disucssion of disk id in sector missing from d64 file format is interesting +// https://www.c64-wiki.com/wiki/Disk_Image +// http://unusedino.de/ec64/technical3.html +// http://www.baltissen.org/newhtm/diskimag.htm +// + +#ifndef MEATLOAF_MEDIA_D64 +#define MEATLOAF_MEDIA_D64 + +#include "meat_io.h" + +#include +#include + +#include "string_utils.h" +#include "cbm_media.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D64IStream : public CBMImageStream { + +protected: + + struct BlockAllocationMap { + uint8_t track; + uint8_t sector; + uint8_t offset; + uint8_t start_track; + uint8_t end_track; + uint8_t byte_count; + }; + + struct Partition { + uint8_t header_track; + uint8_t header_sector; + uint8_t header_offset; + uint8_t directory_track; + uint8_t directory_sector; + uint8_t directory_offset; + std::vector block_allocation_map; + }; + + struct Header { + char disk_name[16]; + char unused[2]; + char id_dos[5]; + }; + + struct Entry { + uint8_t next_track; + uint8_t next_sector; + uint8_t file_type; + uint8_t start_track; + uint8_t start_sector; + char filename[16]; + uint8_t rel_start_track; // Or GOES info block start track + uint8_t rel_start_sector; // Or GEOS info block start sector + uint8_t rel_record_length; // Or GEOS file structure (Sequential / VLIR file) + uint8_t geos_type; // $00 - Non-GEOS (normal C64 file) + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint16_t blocks; + }; + +public: + std::vector partitions; + std::vector sectorsPerTrack = { 17, 18, 19, 21 }; + std::vector interleave = { 3, 10 }; // Directory, File + + uint8_t dos_version = 0x41; + std::string dos_rom = "dos1541"; + std::string dos_name = ""; + + bool error_info = false; + std::string bam_message = ""; + + D64IStream(std::shared_ptr is) : CBMImageStream(is) + { + // D64 Partition Info + std::vector b = { + { + 18, // track + 0, // sector + 0x04, // offset + 1, // start_track + 35, // end_track + 4 // byte_count + } + }; + + Partition p = { + 18, // track + 0, // sector + 0x90, // header_offset + 18, // directory_track + 1, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 17, 18, 19, 21 }; + + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + case 174848: // 35 tracks no errors + break; + + case 175531: // 35 w/ errors + error_info = true; + break; + + case 196608: // 40 tracks no errors + partitions[partition].block_allocation_map[0].end_track = 40; + break; + + case 197376: // 40 w/ errors + partitions[partition].block_allocation_map[0].end_track = 40; + error_info = true; + break; + + case 205312: // 42 tracks no errors + partitions[partition].block_allocation_map[0].end_track = 42; + break; + + case 206114: // 42 w/ errors + partitions[partition].block_allocation_map[0].end_track = 42; + error_info = true; + break; + } + + // Get DOS Version + + // Extend BAM Info for DOLPHIN, SPEED, and ProLogic DOS + // The location of the extra BAM information in sector 18/0, for 40 track images, + // will be different depending on what standard the disks have been formatted with. + // SPEED DOS stores them from $C0 to $D3, DOLPHIN DOS stores them from $AC to $BF + // and PrologicDOS stored them right after the existing BAM entries from $90-A3. + // PrologicDOS also moves the disk label and ID forward from the standard location + // of $90 to $A4. 64COPY and Star Commander let you select from several different + // types of extended disk formats you want to create/work with. + + // // DOLPHIN DOS + // partitions[0].block_allocation_map.push_back( + // { + // 18, // track + // 0, // sector + // 0xAC, // offset + // 36, // start_track + // 40, // end_track + // 4 // byte_count + // } + // ); + + // // SPEED DOS + // partitions[0].block_allocation_map.push_back( + // { + // 18, // track + // 0, // sector + // 0xC0, // offset + // 36, // start_track + // 40, // end_track + // 4 // byte_count + // } + // ); + + // // PrologicDOS + // partitions[0].block_allocation_map.push_back( + // { + // 18, // track + // 0, // sector + // 0x90, // offset + // 36, // start_track + // 40, // end_track + // 4 // byte_count + // } + // ); + // partitions[0].header_offset = 0xA4; + + //getBAMMessage(); + + }; + + // virtual std::unordered_map info() override { + // return { + // {"System", "Commodore"}, + // {"Format", "D64"}, + // {"Media Type", "DISK"}, + // {"Tracks", getTrackCount()}, + // {"Sectors / Blocks", this.getSectorCount()}, + // {"Sector / Block Size", std::string(block_size)}, + // {"Error Info", (this.error_info) ? "Available" : "Not Available"}, + // {"Write Protected", ""}, + // {"DOS Format", this.getDiskFormat()} + // }; + // }; + + uint16_t blocksFree() override; + + uint8_t speedZone( uint8_t track) override + { + return (track < 18) + (track < 25) + (track < 31); + }; + + bool seekBlock( uint64_t index, uint8_t offset = 0 ) override; + bool seekSector( uint8_t track, uint8_t sector, uint8_t offset = 0 ) override; + bool seekSector( std::vector trackSectorOffset ) override; + + void seekHeader() override { + seekSector( + partitions[partition].header_track, + partitions[partition].header_sector, + partitions[partition].header_offset + ); + containerStream->read((uint8_t*)&header, sizeof(header)); + } + + bool seekNextImageEntry() override { + return seekEntry( entry_index + 1 ); + } + + virtual bool seekPath(std::string path) override; + uint16_t readFile(uint8_t* buf, uint16_t size) override; + + Header header; // Directory header data + Entry entry; // Directory entry data + + uint8_t partition = 0; + uint64_t block = 0; + uint8_t track = 0; + uint8_t sector = 0; + uint16_t offset = 0; + uint64_t blocks_free = 0; + + uint8_t next_track = 0; + uint8_t next_sector = 0; + uint8_t sector_offset = 0; + +private: + void sendListing(); + + bool seekEntry( std::string filename ) override; + bool seekEntry( uint16_t index = 0 ) override; + + + std::string readBlock( uint8_t track, uint8_t sector ); + bool writeBlock( uint8_t track, uint8_t sector, std::string data ); + bool allocateBlock( uint8_t track, uint8_t sector ); + bool deallocateBlock( uint8_t track, uint8_t sector ); + + // Disk + friend class D64File; + friend class D71File; + friend class D80File; + friend class D81File; + friend class D82File; + friend class D8BFile; + friend class DNPFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D64File: public MFile { +public: + + D64File(std::string path, bool is_dir = true): MFile(path) + { + isDir = is_dir; + + media_image = name; + isPETSCII = true; + }; + + ~D64File() { + // don't close the stream here! It will be used by shared ptr D64Util to keep reading image params + } + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; + + bool isDirectory() override; + bool rewindDirectory() override; + MFile* getNextFileInDir() override; + bool mkDir() override { return false; }; + + bool exists() override; + bool remove() override { return false; }; + bool rename(std::string dest) override { return false; }; + time_t getLastWrite() override; + time_t getCreationTime() override; + uint32_t size() override; + + bool isDir = true; + bool dirIsOpen = false; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D64FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + //Debug_printv("path[%s]", path.c_str()); + return new D64File(path); + } + + bool handles(std::string fileName) override { + //Serial.printf("handles w dnp %s %d\r\n", fileName.rfind(".dnp"), fileName.length()-4); + return byExtension( + { + ".d64", + ".d41" + }, + fileName + ); + } + + D64FileSystem(): MFileSystem("d64") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D64 */ diff --git a/lib/meatloaf/disk/d71.cpp b/lib/meatloaf/disk/d71.cpp new file mode 100644 index 0000000..784cfe6 --- /dev/null +++ b/lib/meatloaf/disk/d71.cpp @@ -0,0 +1,11 @@ +#include "d71.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D71File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new D71IStream(containerIstream); +} diff --git a/lib/meatloaf/disk/d71.h b/lib/meatloaf/disk/d71.h new file mode 100644 index 0000000..c9a5480 --- /dev/null +++ b/lib/meatloaf/disk/d71.h @@ -0,0 +1,118 @@ +// .D71 - 1571 disk image format +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC373 +// https://ist.uwaterloo.ca/~schepers/formats/D71.TXT +// + + +#ifndef MEATLOAF_MEDIA_D71 +#define MEATLOAF_MEDIA_D71 + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D71IStream : public D64IStream { + // override everything that requires overriding here + +public: + D71IStream(std::shared_ptr is) : D64IStream(is) + { + // D71 Partition Info + std::vector b = { + { + 18, // track + 0, // sector + 0x04, // offset + 1, // start_track + 35, // end_track + 4 // byte_count + }, + { + 53, // track + 0, // sector + 0x00, // offset + 36, // start_track + 70, // end_track + 3 // byte_count + } + }; + + Partition p = { + 1, // track + 0, // sector + 0x04, // header_offset + 1, // directory_track + 4, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 17, 18, 19, 21 }; + dos_rom = "dos1571"; + + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + case 349696: // 70 tracks no errors + break; + + case 351062: // 70 w/ errors + error_info = true; + break; + } + }; + + virtual uint8_t speedZone( uint8_t track) override + { + if ( track < 35 ) + return (track < 18) + (track < 25) + (track < 31); + else + return (track < 53) + (track < 60) + (track < 66); + }; + +protected: + +private: + friend class D71File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D71File: public D64File { +public: + D71File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D71FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D71File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".d71", fileName); + } + + D71FileSystem(): MFileSystem("d71") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D71 */ diff --git a/lib/meatloaf/disk/d80.cpp b/lib/meatloaf/disk/d80.cpp new file mode 100644 index 0000000..3d3a2bc --- /dev/null +++ b/lib/meatloaf/disk/d80.cpp @@ -0,0 +1,11 @@ +#include "d80.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D80File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new D80IStream(containerIstream); +} \ No newline at end of file diff --git a/lib/meatloaf/disk/d80.h b/lib/meatloaf/disk/d80.h new file mode 100644 index 0000000..cd3a8c8 --- /dev/null +++ b/lib/meatloaf/disk/d80.h @@ -0,0 +1,103 @@ +// .D80 - This is a sector-for-sector copy of an 8050 floppy disk +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC360 +// https://ist.uwaterloo.ca/~schepers/formats/D80-D82.TXT +// + + +#ifndef MEATLOAF_MEDIA_D80 +#define MEATLOAF_MEDIA_D80 + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D80IStream : public D64IStream { + // override everything that requires overriding here + +public: + D80IStream(std::shared_ptr is) : D64IStream(is) + { + // D80 Partition Info + std::vector b = { + { + 38, // track + 0, // sector + 0x06, // offset + 1, // start_track + 50, // end_track + 5 // byte_count + }, + { + 38, // track + 3, // sector + 0x06, // offset + 51, // start_track + 77, // end_track + 5 // byte_count + } + }; + + Partition p = { + 39, // track + 0, // sector + 0x06, // header_offset + 39, // directory_track + 1, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 23, 25, 27, 29 }; + }; + + virtual uint8_t speedZone( uint8_t track) override + { + return (track < 40) + (track < 54) + (track < 65); + }; + +protected: + +private: + friend class D80File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D80File: public D64File { +public: + D80File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D80FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D80File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".d80", fileName); + } + + D80FileSystem(): MFileSystem("d80") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D80 */ diff --git a/lib/meatloaf/disk/d81.cpp b/lib/meatloaf/disk/d81.cpp new file mode 100644 index 0000000..5ff957f --- /dev/null +++ b/lib/meatloaf/disk/d81.cpp @@ -0,0 +1,11 @@ +#include "d81.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D81File::getDecodedStream(std::shared_ptr containerIstream) { + //Debug_printv("[%s]", url.c_str()); + + return new D81IStream(containerIstream); +} diff --git a/lib/meatloaf/disk/d81.h b/lib/meatloaf/disk/d81.h new file mode 100644 index 0000000..79a6119 --- /dev/null +++ b/lib/meatloaf/disk/d81.h @@ -0,0 +1,113 @@ +// .D81 - This is a byte for byte copy of a physical 1581 disk +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC354 +// https://ist.uwaterloo.ca/~schepers/formats/D81.TXT +// + + +#ifndef MEATLOAF_MEDIA_D81 +#define MEATLOAF_MEDIA_D81 + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D81IStream : public D64IStream { + // override everything that requires overriding here + +public: + D81IStream(std::shared_ptr is) : D64IStream(is) + { + // D81 Partition Info + std::vector b = { + { + 40, // track + 1, // sector + 0x10, // offset + 1, // start_track + 40, // end_track + 6 // byte_count + }, + { + 40, // track + 0, // sector + 0x10, // offset + 41, // start_track + 80, // end_track + 6 // byte_count + } + }; + + Partition p = { + 40, // track + 0, // sector + 0x04, // header_offset + 40, // directory_track + 3, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 40 }; + dos_rom = "dos1581"; + has_subdirs = true; + + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + case 819200: // 80 tracks no errors + break; + + case 822400: // 80 w/ errors + error_info = true; + break; + } + }; + + virtual uint8_t speedZone( uint8_t track) override { return 0; }; + +protected: + +private: + friend class D81File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D81File: public D64File { +public: + D81File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D81FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D81File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".d81", fileName); + } + + D81FileSystem(): MFileSystem("d81") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D81 */ diff --git a/lib/meatloaf/disk/d82.cpp b/lib/meatloaf/disk/d82.cpp new file mode 100644 index 0000000..670746b --- /dev/null +++ b/lib/meatloaf/disk/d82.cpp @@ -0,0 +1,11 @@ +#include "d82.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D82File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new D82IStream(containerIstream); +} \ No newline at end of file diff --git a/lib/meatloaf/disk/d82.h b/lib/meatloaf/disk/d82.h new file mode 100644 index 0000000..5a7a0a5 --- /dev/null +++ b/lib/meatloaf/disk/d82.h @@ -0,0 +1,122 @@ +// .D82 - This is a sector-for-sector copy of an 8250 floppy disk +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC363 +// https://ist.uwaterloo.ca/~schepers/formats/D80-D82.TXT +// + + +#ifndef MEATLOAF_MEDIA_D82 +#define MEATLOAF_MEDIA_D82 + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D82IStream : public D64IStream { + // override everything that requires overriding here + +public: + D82IStream(std::shared_ptr is) : D64IStream(is) + { + // D82 Partition Info + std::vector b = { + { + 38, // track + 0, // sector + 0x06, // offset + 1, // start_track + 50, // end_track + 5 // byte_count + }, + { + 38, // track + 3, // sector + 0x06, // offset + 51, // start_track + 100, // end_track + 5 // byte_count + }, + { + 38, // track + 6, // sector + 0x06, // offset + 101, // start_track + 150, // end_track + 5 // byte_count + }, + { + 38, // track + 9, // sector + 0x06, // offset + 151, // start_track + 154, // end_track + 5 // byte_count + } + }; + + Partition p = { + 39, // track + 0, // sector + 0x06, // header_offset + 39, // directory_track + 1, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 23, 25, 27, 29 }; + }; + + virtual uint8_t speedZone( uint8_t track) override + { + if (track < 78) + return (track < 40) + (track < 54) + (track < 65); + else + return (track < 117) + (track < 131) + (track < 142); + }; + +protected: + +private: + friend class D82File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D82File: public D64File { +public: + D82File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D82FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D82File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".d82", fileName); + } + + D82FileSystem(): MFileSystem("d82") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D82 */ diff --git a/lib/meatloaf/disk/d90.cpp b/lib/meatloaf/disk/d90.cpp new file mode 100644 index 0000000..4d4de81 --- /dev/null +++ b/lib/meatloaf/disk/d90.cpp @@ -0,0 +1,11 @@ +#include "d90.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* D90File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new D90IStream(containerIstream); +} \ No newline at end of file diff --git a/lib/meatloaf/disk/d90.h b/lib/meatloaf/disk/d90.h new file mode 100644 index 0000000..816ce31 --- /dev/null +++ b/lib/meatloaf/disk/d90.h @@ -0,0 +1,157 @@ +// .D90 - The D90 image is bit-for-bit copy of the hard drives in the D9090 and D9060 +// +// https://vice-emu.sourceforge.io/vice_16.html#SEC429 +// http://www.baltissen.org/newhtm/diskimag.htm +// + + +#ifndef MEATLOAF_MEDIA_D90 +#define MEATLOAF_MEDIA_D90 + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class D90IStream : public D64IStream { + // override everything that requires overriding here + +public: + D90IStream(std::shared_ptr is) : D64IStream(is) + { + // D90 Partition Info + std::vector b = { + { + 38, // track + 0, // sector + 0x06, // offset + 1, // start_track + 50, // end_track + 5 // byte_count + }, + { + 38, // track + 3, // sector + 0x06, // offset + 51, // start_track + 100, // end_track + 5 // byte_count + }, + { + 38, // track + 6, // sector + 0x06, // offset + 101, // start_track + 150, // end_track + 5 // byte_count + }, + { + 38, // track + 9, // sector + 0x06, // offset + 151, // start_track + 154, // end_track + 5 // byte_count + } + }; + + Partition p = { + 39, // track + 0, // sector + 0x06, // header_offset + 39, // directory_track + 1, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 17, 18, 19, 21 }; + + // this.size = data.media_data.length; + // switch (this.size + this.media_header_size) { + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + case 5013504: // D9060 + sectorsPerTrack = { (4 * 32) }; // Heads * Sectors + break; + + case 7520256: // D9090 + sectorsPerTrack = { (6 * 32) }; // Heads * Sectors + break; + } + + // this.seek(0x04); + seek( 0x04 ); + // this.partitions[0].directory_track = this.read(); + partitions[0].directory_track = read(); + // this.partitions[0].directory_sector = this.read(); + partitions[0].directory_sector = read(); + // this.partitions[0].track = this.read(); + partitions[0].header_track = read(); + // this.partitions[0].sector = this.read(); + partitions[0].header_sector = read(); + // this.partitions[0].block_allocation_map[0].track = this.read(); + partitions[0].block_allocation_map[0].track = read(); + // this.partitions[0].block_allocation_map[0].sector = this.read(); + partitions[0].block_allocation_map[0].sector = read(); + }; + + virtual uint8_t speedZone( uint8_t track) override + { + if ( track < 78 ) + return (track < 39) + (track < 53) + (track < 64); + else + return (track < 116) + (track < 130) + (track < 141); + }; + +protected: + +private: + friend class D90File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class D90File: public D64File { +public: + D90File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class D90FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new D90File(path); + } + + bool handles(std::string fileName) override { + return byExtension( + { + ".d90", + ".d60" + }, + fileName + ); + } + + D90FileSystem(): MFileSystem("d90") {}; +}; + + +#endif /* MEATLOAF_MEDIA_D90 */ diff --git a/lib/meatloaf/disk/dhd.h b/lib/meatloaf/disk/dhd.h new file mode 100644 index 0000000..6833f15 --- /dev/null +++ b/lib/meatloaf/disk/dhd.h @@ -0,0 +1,5 @@ +// .DHD - CMD Hard drive image format +// +// https://sourceforge.net/p/vice-emu/patches/253/ +// https://www.pipesup.ca/cmdhd-in-vice/ +// diff --git a/lib/meatloaf/disk/dmk.h b/lib/meatloaf/disk/dmk.h new file mode 100644 index 0000000..38d96e5 --- /dev/null +++ b/lib/meatloaf/disk/dmk.h @@ -0,0 +1,9 @@ +// .DMK - David M Keil’s disk format. A low-level disk image file format used by TRS-80 Model I & Model III, +// Tandy Color Computer, Dragon 32 & 64, and MSX emulators +// +// https://electrickery.hosting.philpem.me.uk/comp/trs80-4p/dmkeilImages/trstech.htm +// https://retrocomputing.stackexchange.com/questions/15282/understanding-the-dmk-disk-image-file-format-used-by-trs-80-emulators +// http://cpmarchives.classiccmp.org//trs80/mirrors/www.discover-net.net/~dmkeil/coco/cocotech.htm +// https://archive.worldofdragon.org/index.php?title=Tape%5CDisk_Preservation#DMK_File_Format +// https://github.com/lutris/openmsx/blob/master/doc/DMK-Format-Details.txt +// \ No newline at end of file diff --git a/lib/meatloaf/disk/dnp.cpp b/lib/meatloaf/disk/dnp.cpp new file mode 100644 index 0000000..cf9657b --- /dev/null +++ b/lib/meatloaf/disk/dnp.cpp @@ -0,0 +1,12 @@ + +#include "dnp.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* DNPFile::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new DNPIStream(containerIstream); +} diff --git a/lib/meatloaf/disk/dnp.h b/lib/meatloaf/disk/dnp.h new file mode 100644 index 0000000..5453ffa --- /dev/null +++ b/lib/meatloaf/disk/dnp.h @@ -0,0 +1,91 @@ +// .DNP - CMD hard Disk Native Partition +// +// https://ist.uwaterloo.ca/~schepers/formats/D2M-DNP.TXT +// + +#ifndef MEATLOAF_MEDIA_DNP +#define MEATLOAF_MEDIA_DNP + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class DNPIStream : public D64IStream { + // override everything that requires overriding here + +public: + DNPIStream(std::shared_ptr is) : D64IStream(is) + { + // DNP Partition Info + std::vector b = { + { + 1, // track + 2, // sector + 0x10, // offset + 1, // start_track + 255, // end_track + 8 // byte_count + } + }; + + Partition p = { + 1, // track + 0, // sector + 0x04, // header_offset + 1, // directory_track + 0, // directory_sector + 0x20, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 255 }; + has_subdirs = true; + }; + + virtual uint8_t speedZone( uint8_t track) override { return 0; }; + +protected: + +private: + friend class DNPFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class DNPFile: public D64File { +public: + DNPFile(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class DNPFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new DNPFile(path); + } + + bool handles(std::string fileName) override { + return byExtension(".dnp", fileName); + } + + DNPFileSystem(): MFileSystem("dnp") {}; +}; + + +#endif /* MEATLOAF_MEDIA_DNP */ diff --git a/lib/meatloaf/disk/dsk.cpp b/lib/meatloaf/disk/dsk.cpp new file mode 100644 index 0000000..22d07f0 --- /dev/null +++ b/lib/meatloaf/disk/dsk.cpp @@ -0,0 +1,11 @@ +#include "dsk.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* DSKFile::getDecodedStream(std::shared_ptr containerIstream) { + //Debug_printv("[%s]", url.c_str()); + + return new DSKIStream(containerIstream); +} diff --git a/lib/meatloaf/disk/dsk.h b/lib/meatloaf/disk/dsk.h new file mode 100644 index 0000000..b4fb87a --- /dev/null +++ b/lib/meatloaf/disk/dsk.h @@ -0,0 +1,125 @@ +// .DSK - This is a byte for byte copy of a physical disk +// +// https://github.com/simonowen/samdisk/tree/main +// Apple - https://gswv.apple2.org.za/a2zine/faqs/Csa2FLUTILS.html#006 +// Apple - http://fileformats.archiveteam.org/wiki/DSK_(Apple_II) +// Coleco Adam - https://retrocomputing.stackexchange.com/questions/15833/what-floppy-disk-format-and-layout-and-what-disk-image-format-are-used-for-the-c + +#ifndef MEATLOAF_MEDIA_DSK +#define MEATLOAF_MEDIA_DSK + +#include "meat_io.h" +#include "d64.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class DSKIStream : public D64IStream { + // override everything that requires overriding here + +public: + DSKIStream(std::shared_ptr is) : D64IStream(is) + { + // DSK Partition Info + std::vector b = { + { + 40, // track + 1, // sector + 0x10, // offset + 1, // start_track + 40, // end_track + 6 // byte_count + } + }; + + Partition p = { + 35, // track + 0, // sector + 0x04, // header_offset + 40, // directory_track + 3, // directory_sector + 0x00, // directory_offset + b // block_allocation_map + }; + partitions.clear(); + partitions.push_back(p); + sectorsPerTrack = { 16 }; + dos_rom = ""; + dos_name = ""; + has_subdirs = false; + error_info = true; + + uint32_t size = containerStream->size(); + switch (size + media_header_size) + { + // Apple // 35 Tracks, 16 sector/track, 256 bytes/sector + case 146660: + break; + + // Coco // 35 Tracks, 16 sector/track, 256 bytes/sector + case 161280: + break; + + // Apple // 40 Tracks, 16 sector/track, 256 bytes/sector + // Coleco Adam // 40 Tracks, 8 sectors per track, 512 bytes per sectors + case 163840: + break; + + // Coco OS9 + case 184320: + break; + } + }; + + virtual uint8_t speedZone( uint8_t track) override { return 0; }; + +protected: + +private: + friend class DSKFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class DSKFile: public D64File { +public: + DSKFile(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class DSKFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new DSKFile(path); + } + + bool handles(std::string fileName) override { + return byExtension( + { + ".dsk", + ".do", + ".po", + ".hdv" + }, + fileName + ); + } + + DSKFileSystem(): MFileSystem("dsk") {}; +}; + + +#endif /* MEATLOAF_MEDIA_DSK */ diff --git a/lib/meatloaf/disk/dxm.h b/lib/meatloaf/disk/dxm.h new file mode 100644 index 0000000..636730e --- /dev/null +++ b/lib/meatloaf/disk/dxm.h @@ -0,0 +1,6 @@ +// .D1M/D2M/D4M - CMD FD2000/FD4000 Disk Image Format +// +// https://cbm8bit.com/8bit/commodore/server/Unrenamed%20Achives/browse/c64/d2m +// https://web.archive.org/web/20180925144409/https://cbm8bit.com/articles/user-contributions/howto_d1m_d2m_d4m +// https://ist.uwaterloo.ca/~schepers/formats/D2M-DNP.TXT +// \ No newline at end of file diff --git a/lib/meatloaf/disk/f64.h b/lib/meatloaf/disk/f64.h new file mode 100644 index 0000000..baf63ad --- /dev/null +++ b/lib/meatloaf/disk/f64.h @@ -0,0 +1,2 @@ + +// https://cbm8bit.com/8bit/commodore/server/Unrenamed%20Achives/browse/c64/f64 \ No newline at end of file diff --git a/lib/meatloaf/disk/fdi.h b/lib/meatloaf/disk/fdi.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/disk/g64.cpp b/lib/meatloaf/disk/g64.cpp new file mode 100644 index 0000000..870bdd8 --- /dev/null +++ b/lib/meatloaf/disk/g64.cpp @@ -0,0 +1,11 @@ +#include "g64.h" + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* G64File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new G64IStream(containerIstream); +} diff --git a/lib/meatloaf/disk/g64.h b/lib/meatloaf/disk/g64.h new file mode 100644 index 0000000..cc0e76b --- /dev/null +++ b/lib/meatloaf/disk/g64.h @@ -0,0 +1,104 @@ +// .G64 - The G64 GCR-encoded disk image format +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC335 +// https://ist.uwaterloo.ca/~schepers/formats/G64.TXT +// http://www.linusakesson.net/programming/gcr-decoding/index.php +// + + +#ifndef MEATLOAF_MEDIA_G64 +#define MEATLOAF_MEDIA_G64 + +#include "meat_io.h" +#include "d64.h" + +// Format codes: +// ID Description +// 0 Unknown format +// 1 GCR Data +// 2 CBM DOS +// 3 CBM DOS Extended +// 4 MicroProse +// 5 RapidLok +// 6 Datasoft +// 7 Vorpal +// 8 V-MAX! +// 9 Teque +// 10 TDP +// 11 Big Five +// 12 OziSoft + +// Format Extensions: +// ID Description +// 0 Unknown protection +// 1 Datasoft with Weak bits +// 2 CBM DOS with Cyan loader, Weak bits +// 3 CBM DOS with Datasoft, Weak bits +// 4 RapidLok Key +// 5 Data Duplication +// 6 Melbourne House +// 7 Melbourne House, Weak bits +// 8 PirateBusters v1.0 +// 9 PirateBusters v2.0, Track A +// 10 PirateBusters v2.0, Track B +// 11 PirateSlayer +// 12 CBM DOS, XEMAG + +/******************************************************** + * Streams + ********************************************************/ + +class G64IStream : public D64IStream { + // override everything that requires overriding here + +public: + G64IStream(std::shared_ptr is) : D64IStream(is) + { + // G64 Offsets + //directory_header_offset = {18, 0, 0x90}; + //directory_list_offset = {18, 1, 0x00}; + //block_allocation_map = { {18, 0, 0x04, 1, 35, 4}, {53, 0, 0x00, 36, 70, 3} }; + //sectorsPerTrack = { 17, 18, 19, 21 }; + }; + + +protected: + +private: + friend class G64File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class G64File: public D64File { +public: + G64File(std::string path, bool is_dir = true) : D64File(path, is_dir) {}; + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class G64FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new G64File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".g64", fileName); + } + + G64FileSystem(): MFileSystem("g64") {}; +}; + + +#endif /* MEATLOAF_MEDIA_G64 */ diff --git a/lib/meatloaf/disk/hdd.h b/lib/meatloaf/disk/hdd.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/disk/jvc.h b/lib/meatloaf/disk/jvc.h new file mode 100644 index 0000000..d902fce --- /dev/null +++ b/lib/meatloaf/disk/jvc.h @@ -0,0 +1,6 @@ +// .JVC .JV1 .JV3 (.DSK) - The Jeff Vavasour Color Computer Disk Image format +// +// https://sites.google.com/site/dabarnstudio/coco-utilities/jvc-disk-format +// https://tlindner.macmess.org/?page_id=86 +// https://archive.worldofdragon.org/index.php?title=Tape%5CDisk_Preservation#JVC.2FDSK_File_Format +// \ No newline at end of file diff --git a/lib/meatloaf/disk/m2i.h b/lib/meatloaf/disk/m2i.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/disk/nib.h b/lib/meatloaf/disk/nib.h new file mode 100644 index 0000000..fbe077b --- /dev/null +++ b/lib/meatloaf/disk/nib.h @@ -0,0 +1,4 @@ +// .NIB - Commodore 1541/1571 nibbler disk image +// +// https://github.com/markusC64/nibtools +// diff --git a/lib/meatloaf/disk/p64.h b/lib/meatloaf/disk/p64.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/disk/scp.h b/lib/meatloaf/disk/scp.h new file mode 100644 index 0000000..ef9e0c6 --- /dev/null +++ b/lib/meatloaf/disk/scp.h @@ -0,0 +1,5 @@ +// .SCP - SuperCard Pro Flux Image Format +// +// https://www.cbmstuff.com/forum/showthread.php?tid=16 +// https://www.cbmstuff.com/downloads/scp/scp_image_specs.txt +// diff --git a/lib/meatloaf/disk/vdk.h b/lib/meatloaf/disk/vdk.h new file mode 100644 index 0000000..61fca3a --- /dev/null +++ b/lib/meatloaf/disk/vdk.h @@ -0,0 +1,4 @@ +// .VDK - Dragon 32 and 64 disk image file format +// +// https://archive.worldofdragon.org/index.php?title=Tape%5CDisk_Preservation#VDK_File_Format +// diff --git a/lib/meatloaf/disk/woz.h b/lib/meatloaf/disk/woz.h new file mode 100644 index 0000000..d7526dc --- /dev/null +++ b/lib/meatloaf/disk/woz.h @@ -0,0 +1,6 @@ +// .WOZ - This is a byte for byte copy of a physical disk +// +// https://applesaucefdc.com/woz/reference1/ +// https://applesaucefdc.com/woz/reference2/ +// https://github.com/leesaudan2/woz2dsk +// \ No newline at end of file diff --git a/lib/meatloaf/disk/x64.h b/lib/meatloaf/disk/x64.h new file mode 100644 index 0000000..920264d --- /dev/null +++ b/lib/meatloaf/disk/x64.h @@ -0,0 +1,4 @@ +// .X64 - Disk Image Format +// +// https://vice-emu.sourceforge.io/vice_17.html#SEC350 +// diff --git a/lib/meatloaf/disk/z64.h b/lib/meatloaf/disk/z64.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/file/p00.cpp b/lib/meatloaf/file/p00.cpp new file mode 100644 index 0000000..a5c878a --- /dev/null +++ b/lib/meatloaf/file/p00.cpp @@ -0,0 +1,36 @@ +#include "p00.h" + +/******************************************************** + * Streams + ********************************************************/ + +uint16_t P00IStream::readFile(uint8_t* buf, uint16_t size) { + uint16_t bytesRead = 0; + + bytesRead += containerStream->read(buf, size); + _position += bytesRead; + + return bytesRead; +} + + + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* P00File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new P00IStream(containerIstream); +} + + +uint32_t P00File::size() { + // Debug_printv("[%s]", streamFile->url.c_str()); + // use P00 to get size of the file in image + auto image = ImageBroker::obtain(streamFile->url); + + return image->size(); +} + diff --git a/lib/meatloaf/file/p00.h b/lib/meatloaf/file/p00.h new file mode 100644 index 0000000..11baf7b --- /dev/null +++ b/lib/meatloaf/file/p00.h @@ -0,0 +1,119 @@ +// .P00/P** - P00/S00/U00/R00 (Container files for the PC64 emulator) +// https://ist.uwaterloo.ca/~schepers/formats/PC64.TXT +// + +#ifndef MEATLOAF_MEDIA_P00 +#define MEATLOAF_MEDIA_P00 + +#include "meat_io.h" +#include "cbm_media.h" + +/******************************************************** + * Streams + ********************************************************/ + +class P00IStream : public CBMImageStream { + // override everything that requires overriding here + +public: + P00IStream(std::shared_ptr is) : CBMImageStream(is) { + entry_count = 1; + seekNextEntry(); + }; + +protected: + struct Header { + char signature[7]; + uint8_t pad1; + char filename[16]; + uint8_t pad2; + uint8_t rel_flag; + }; + + void seekHeader() override { + containerStream->seek(0x00); + containerStream->read((uint8_t*)&header, sizeof(header)); + } + bool seekNextImageEntry() override { + if ( entry_index == 0 ) { + entry_index = 1; + seekHeader(); + + _size = ( containerStream->size() - sizeof(header) ); + + return true; + } + return false; + } + + // For files with no directory structure + // tap, crt, tar + std::string seekNextEntry() override { + seekCalled = true; + if ( seekNextImageEntry() ) { + return header.filename; + } + return ""; + }; + + uint16_t readFile(uint8_t* buf, uint16_t size) override; + + Header header; + +private: + friend class P00File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class P00File: public MFile { +public: + + P00File(std::string path, bool is_dir = false): MFile(path) {}; + + ~P00File() { + // don't close the stream here! It will be used by shared ptr D64Util to keep reading image params + } + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; + + bool isDirectory() override { return false; };; + bool rewindDirectory() override { return false; };; + MFile* getNextFileInDir() override { return nullptr; };; + bool mkDir() override { return false; }; + + bool exists() override { return true; }; + bool remove() override { return false; }; + bool rename(std::string dest) override { return false; }; + time_t getLastWrite() override { return 0; }; + time_t getCreationTime() override { return 0; }; + uint32_t size() override; + + bool isDir = false; + bool dirIsOpen = false; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class P00FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new P00File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".p00", fileName); + } + + P00FileSystem(): MFileSystem("p00") {}; +}; + +#endif // MEATLOAF_MEDIA_P00 \ No newline at end of file diff --git a/lib/meatloaf/file/prg.h b/lib/meatloaf/file/prg.h new file mode 100644 index 0000000..d00f1c3 --- /dev/null +++ b/lib/meatloaf/file/prg.h @@ -0,0 +1,2 @@ +// .PRG - RAW CBM PRG file +// \ No newline at end of file diff --git a/lib/meatloaf/file/vsf.h b/lib/meatloaf/file/vsf.h new file mode 100644 index 0000000..3991db5 --- /dev/null +++ b/lib/meatloaf/file/vsf.h @@ -0,0 +1,5 @@ +// Vice Snapshot File - .VSF, .SNAP +// +// https://vice-emu.sourceforge.io/vice_9.html +// https://www.lemon64.com/forum/viewtopic.php?t=72969&sid=832434bce01d546e4a1ac185aa4ff459 +// \ No newline at end of file diff --git a/lib/meatloaf/iec_pipe.h b/lib/meatloaf/iec_pipe.h new file mode 100644 index 0000000..61fafab --- /dev/null +++ b/lib/meatloaf/iec_pipe.h @@ -0,0 +1,152 @@ +#ifndef MEATLIB_FILESYSTEM_IEC_PIPE +#define MEATLIB_FILESYSTEM_IEC_PIPE + +#include "meat_buffer.h" +#include "wrappers/iec_buffer.h" + +/* +This is the main IEC named channel abstraction. Meatloaf keeps an array/map of these as long as they're alive +and removes them when channel is closed + +- each named channel is connected to one file + +- it allows pusing data to/from remote file + +- when ATN is received the transfer stops and will be seamlesly resumed on next call to send/receive + +- resuming transfer is possible as long as this channel is open + +Example usage: + +// c64 requests a remote file "http://address/file.prg" +auto newPipe = new iecPipe(); +newPipe->establish("http://address/file.prg", std::ios_base::openmode::_S_in, iec); + +// store the pipe in your table of channels +..... + +// start transfer +newPipe->readFile(); +// this method returns only when either happened: +// 1) the file was read wholly so you have to call newPipe->finish() and remove it from your table of channles +// 2) ATN was pulled, so if reading from this channel is later requested again, you fetch it from +// your table of channels and call: +// storedPipe->readFile() +// you can readFile() as many times as needed, and it will do what you expect (resume from where it was interrupted) +// as long as it stays open and not EOF. + + +_S_out == C64 -> IEC -> file +_S_in == file -> IEC -> C64 + +*/ +class iecPipe { + Meat::iostream* fileStream = nullptr; + oiecstream iecStream; + std::ios_base::openmode mode; + +public: + + // you call this once, when C64 requested to open the stream + bool establish(std::string filename, std::ios_base::openmode m, systemBus* i) { + if(fileStream != nullptr) + return false; + + mode = m; + iecStream.open(i); + + fileStream = new Meat::iostream(filename.c_str(), mode); + } + + // this stream is disposed of, nothing more will happen with it! + void finish() { + if(fileStream != nullptr) + delete fileStream; + + fileStream = nullptr; + iecStream.close(); // will push last byte and send EOI (if hasn't been closed yet) + } + + bool seek(std::streampos p) { + if(mode == std::ios_base::openmode::_S_in) { + // we are LOADing, so we need to seek within the source file input stream (get buffer): + fileStream->seekg(p); + // our iec stream put buffer can potentially contain more bytes ready for read, but since we are + // seeking, we need to flush them, so they don't get sent to C64 as if they were read from file + iecStream.flushpbuff(); + } + else if(mode == std::ios_base::openmode::_S_out) { + // we are SAVE-ing, so, we need to seek within the destination file output stream (put buffer): + fileStream->seekp(p); + //iecStream.flushgbuff() - not sure if it will be required + } + + return false; + } + + // LOAD - push file bytes (fileStream.get) to C64 (iecStream.write) + void readFile() { + // this pipe wasn't itialized or wasn't meant for reading + if(fileStream == nullptr || mode != std::ios_base::openmode::_S_in) + return; + + // push bytes from file to C64 as long as they're available (eying ATN) + while(!fileStream->eof() && !fileStream->bad() && !(IEC.flags bitand ATN_PULLED)) + { + // If this is a socket stream, it might send NDA ("I have no data for you, but keep asking"), so lte's check it! + (*fileStream).checkNda(); + if((*fileStream).nda()) { + // JAIME: So you told me there's a way to signal "no data available on IEC", here's the place + // to send it: + + // TODO + } + else { + // let's try to get and send next byte to IEC + char nextChar; + //(*fileStream).get(nextChar); + fileStream->get(nextChar); + // EOF could be set after this get, so + if(!fileStream->eof()) { + iecStream.write(&nextChar, 1); + Debug_printf("%.2X ", nextChar); + } + } + } + + // the loop exited, it might be because: + // - ATN was pulled? + if(IEC.flags bitand ATN_PULLED) { + // JAIME: anything needs to happen here? + } + // - read whole? Send EOI! + else if(fileStream->eof()) + iecStream.close(); + // - error occured? + else if(fileStream->bad()) { + // JAIME: can we somehow signal an error here? + } + } + + // SAVE - pull C64 bytes (iecStream.get) to remote file (fileStream.put) + void writeFile() { + // this pipe wasn't itialized or wasn't meant for writing + if(fileStream == nullptr || mode != std::ios_base::openmode::_S_out) + return; + + // push bytes from C64 to file as long as they're available (eying ATN) + while(!fileStream->bad() && !(IEC.flags bitand ATN_PULLED)) + { + char nextChar; + //iecStream.get(nextChar); TODO + if(!iecStream.eof()) { + //(*fileStream).put('a'); + fileStream->put(nextChar); + } + } + // so here either the C64 stopped sending bytes to write or the outpt stream died + // nothing to see here, move along! + } +}; + +#endif /* MEATLIB_FILESYSTEM_IEC_PIPE */ diff --git a/lib/meatloaf/link/url.h b/lib/meatloaf/link/url.h new file mode 100644 index 0000000..3af2c95 --- /dev/null +++ b/lib/meatloaf/link/url.h @@ -0,0 +1,47 @@ +// .URL - Meatloaf URL Links +// + +#ifndef MEATLOAF_LINK_URL +#define MEATLOAF_LINK_URL + +#include "network/http.h" + +#include "peoples_url_parser.h" + + +/******************************************************** + * FS + ********************************************************/ + +class MLFileSystem: public MFileSystem +{ + MFile* getFile(std::string path) override { + if ( path.size() == 0 ) + return nullptr; + + Debug_printv("path[%s]", path.c_str()); + + // Read URL file + auto reader = Meat::New(path); + auto istream = reader->getSourceStream(); + + uint8_t url[istream->size()]; + istream->read(url, istream->size()); + + Debug_printv("url[%s]", url); + std::string ml_url((char *)url); + + return new HttpFile(ml_url); + } + + bool handles(std::string fileName) override { + //Serial.printf("handles w dnp %s %d\r\n", fileName.rfind(".dnp"), fileName.length()-4); + return byExtension( ".url", fileName ); + } + +public: + MLFileSystem(): MFileSystem("url") {}; +}; + + +#endif // MEATLOAF_LINK_URL \ No newline at end of file diff --git a/lib/meatloaf/link/webloc.h b/lib/meatloaf/link/webloc.h new file mode 100644 index 0000000..871355f --- /dev/null +++ b/lib/meatloaf/link/webloc.h @@ -0,0 +1,47 @@ +// .URL - Meatloaf URL Links +// + +#ifndef MEATLOAF_LINK_URL +#define MEATLOAF_LINK_URL + +#include "network/http.h" + +#include "peoples_url_parser.h" + + +/******************************************************** + * FS + ********************************************************/ + +class MLFileSystem: public MFileSystem +{ + MFile* getFile(std::string path) override { + if ( path.size() == 0 ) + return nullptr; + + // Read URL file + + //Debug_printv("MLFileSystem::getFile(%s)", path.c_str()); + PeoplesUrlParser *urlParser = PeoplesUrlParser::parseURL( path ); + std::string code = mstr::toUTF8(urlParser->name); + + //Debug_printv("url[%s]", urlParser.name.c_str()); + std::string ml_url = "https://api.meatloaf.cc/?" + code; + //Debug_printv("ml_url[%s]", ml_url.c_str()); + + //Debug_printv("url[%s]", ml_url.c_str()); + + return new HttpFile(ml_url); + } + + bool handles(std::string fileName) override { + //Serial.printf("handles w dnp %s %d\r\n", fileName.rfind(".dnp"), fileName.length()-4); + return byExtension( ".webloc", fileName ); + } + +public: + MLFileSystem(): MFileSystem("webloc") {}; +}; + + +#endif // MEATLOAF_LINK_URL \ No newline at end of file diff --git a/lib/meatloaf/loaders/koa.h b/lib/meatloaf/loaders/koa.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/loaders/psid.h b/lib/meatloaf/loaders/psid.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/loaders/sid.h b/lib/meatloaf/loaders/sid.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/meatloaf/make_unique.h b/lib/meatloaf/make_unique.h new file mode 100644 index 0000000..4a6e3cb --- /dev/null +++ b/lib/meatloaf/make_unique.h @@ -0,0 +1,42 @@ +#ifndef MEATLOAF_DEFINES_MKUNIQUE_H +#define MEATLOAF_DEFINES_MKUNIQUE_H +#if __cplusplus < 201703L + +#include +#include +#include +#include + +namespace std { + template struct _Unique_if { + typedef unique_ptr _Single_object; + }; + + template struct _Unique_if { + typedef unique_ptr _Unknown_bound; + }; + + template struct _Unique_if { + typedef void _Known_bound; + }; + + template + typename _Unique_if::_Single_object + make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } + + template + typename _Unique_if::_Unknown_bound + make_unique(size_t n) { + typedef typename remove_extent::type U; + return unique_ptr(new U[n]()); + } + + template + typename _Unique_if::_Known_bound + make_unique(Args&&...) = delete; +} + +#endif +#endif \ No newline at end of file diff --git a/lib/meatloaf/meat_buffer.h b/lib/meatloaf/meat_buffer.h new file mode 100644 index 0000000..016967b --- /dev/null +++ b/lib/meatloaf/meat_buffer.h @@ -0,0 +1,689 @@ +#ifndef MEATLOAF_BUFFER +#define MEATLOAF_BUFFER + +#include +#include + +#include "meat_io.h" + +#include "../../include/debug.h" + +namespace Meat +{ + /******************************************************** + * C++ Input MFile buffer + ********************************************************/ + + struct my_char_traits : public std::char_traits + { + typedef std::char_traits::int_type int_type; + static constexpr int_type nda() noexcept + { + return static_cast(_MEAT_NO_DATA_AVAIL); + } + }; + + template + class mfilebuf : public std::basic_filebuf + { + std::unique_ptr mstream; + std::unique_ptr mfile; + + static const size_t gbuffer_size = 2048; + static const size_t pbuffer_size = 512; + char *gbuffer; + char *pbuffer; + + std::streampos currBuffStart = 0; + std::streampos currBuffEnd; + + public: + typedef charT char_type; // 1 + typedef traits traits_type; + typedef typename traits_type::int_type int_type; + typedef typename traits_type::off_type off_type; + typedef typename traits_type::pos_type pos_type; + + mfilebuf() + { + gbuffer = new char[gbuffer_size+1]; + pbuffer = new char[pbuffer_size+1]; + }; + + ~mfilebuf() + { + if (pbuffer != nullptr) + delete[] pbuffer; + + if (gbuffer != nullptr) + delete[] gbuffer; + + close(); + } + + std::filebuf *doOpen(std::ios_base::openmode mode) + { + // Debug_println("In filebuf open pre reset mistream"); + mstream.reset(mfile->getSourceStream(mode)); + + if (mstream->isOpen()) + { + // Debug_println("In filebuf open success!"); + if (mode == std::ios_base::in) { + // initialize get buffer using gbuffer_size + this->setg(gbuffer, gbuffer, gbuffer); + } + else if (mode == std::ios_base::out) { + this->setp(pbuffer, pbuffer + pbuffer_size); + } + return this; + } + else + return nullptr; + } + + std::filebuf *open(const char *filename, std::ios_base::openmode mode) + { + // Debug_println("In filebuf open"); + mfile.reset(MFSOwner::File(filename)); + return doOpen(mode); + }; + + std::filebuf *open(const std::string &filename, std::ios_base::openmode mode) + { + // Debug_println("In filebuf open"); + mfile.reset(MFSOwner::File(filename)); + return doOpen(mode); + }; + + virtual bool close() + { + if (is_open()) + { + // Debug_printv("closing in filebuf\n"); + sync(); + mstream->close(); + return true; + } + return false; + } + + bool is_open() const + { + Debug_printv("is_open"); + + if (mstream == nullptr) + return false; + else + return mstream->isOpen(); + } + + /** + * @brief Fetches more data from the controlled sequence. + * @return The first character from the pending sequence. + * + * Informally, this function is called when the input buffer is + * exhausted (or does not exist, as buffering need not actually be + * done). If a buffer exists, it is @a refilled. In either case, the + * next available character is returned, or @c traits::eof() to + * indicate a null pending sequence. + * + * For a formal definition of the pending sequence, see a good text + * such as Langer & Kreft, or [27.5.2.4.3]/7-14. + * + * A functioning input streambuf can be created by overriding only + * this function (no buffer area will be used). For an example, see + * https://gcc.gnu.org/onlinedocs/libstdc++/manual/streambufs.html + * + * @note Base class version does nothing, returns eof(). + */ + + // https://newbedev.com/how-to-write-custom-input-stream-in-c + + // let's get a byte relative to current egptr + int operator[](int index) + { + // 1. let's check if our index is within current buffer POINTERS + // gptr = current character (get pointer) + // egptr = one past end of get area + if (this->gptr() == this->egptr()) + underflow(); + + if (this->gptr() < this->egptr()) + { + Debug_printf("%d char is within gptr-egptr range", index); // or - send the char across IEC to our C64 + return std::char_traits::to_int_type(this->gptr()[index]); + } + else + { + Debug_printf("Index out of current buffer %d", this->gptr() - this->egptr()); + + return my_char_traits::nda(); + } + } + + int underflow() override + { + + if (!is_open()) + { + return std::char_traits::eof(); + } + else if (this->gptr() == this->egptr()) + { + // the next statement assumes "size" characters were produced (if + // no more characters are available, size == 0. + // auto buffer = reader->read(); + + int readCount = mstream->read((uint8_t *)gbuffer, gbuffer_size); + + Debug_printv("meat buffer underflow, readCount=%d", readCount); + + // beg, curr, end <=> eback, gptr, egptr + if (readCount == _MEAT_NO_DATA_AVAIL) + { + // if gptr >= egptr - sgetc will call underflow again: + // gptr egptr + Debug_printv("meat underflow received _MEAT_NO_DATA_AVAIL!"); + //this->setg(gbuffer, gbuffer, gbuffer); + + return my_char_traits::nda(); + } + else if (readCount <= 0) + { + Debug_printv("--mfilebuf read error or EOF, RC=%d!", readCount); + //this->setg(gbuffer, gbuffer, gbuffer); + return std::char_traits::eof(); + } + else + { + currBuffEnd = mstream->position(); + currBuffStart = (uint32_t)currBuffEnd - readCount; // this is where our buffer data starts + + // Debug_printv("--mfilebuf underflow, read bytes=%d--", readCount); + // beg, curr, end <=> eback, gptr, egptr + this->setg(gbuffer, gbuffer, gbuffer + readCount); + } + } + // eback = beginning of get area + // gptr = current character (get pointer) + // egptr = one past end of get area + + return this->gptr() == this->egptr() + ? std::char_traits::eof() + : std::char_traits::to_int_type(*this->gptr()); + }; + + /** + * @brief Consumes data from the buffer; writes to the + * controlled sequence. + * @param __c An additional character to consume. + * @return eof() to indicate failure, something else (usually + * @a __c, or not_eof()) + * + * Informally, this function is called when the output buffer + * is full (or does not exist, as buffering need not actually + * be done). If a buffer exists, it is @a consumed, with + * some effect on the controlled sequence. + * (Typically, the buffer is written out to the sequence + * verbatim.) In either case, the character @a c is also + * written out, if @a __c is not @c eof(). + * + * For a formal definition of this function, see a good text + * such as Langer & Kreft, or [27.5.2.4.5]/3-7. + * + * A functioning output streambuf can be created by overriding only + * this function (no buffer area will be used). + * + * @note Base class version does nothing, returns eof(). + */ + + // // https://newbedev.com/how-to-write-custom-input-stream-in-c + + int overflow(int ch = traits_type::eof()) override + { + + Debug_printv("in overflow"); + // pptr = Returns the pointer to the current character (put pointer) in the put area. + // pbase = Returns the pointer to the beginning ("base") of the put area. + // epptr = Returns the pointer one past the end of the put area. + + if (!is_open()) + { + return EOF; + } + + char *end = this->pptr(); + + Debug_printv("before write call, ch=%d [%c], buffer contains:%d", ch, (char)ch, this->pptr() - this->pbase()); + + if (ch != EOF) + { + *end++ = ch; + this->pbump(1); + } + + Debug_printv("%d bytes in buffer will be written", end - this->pbase()); + + const uint8_t *pBase = (uint8_t *)this->pbase(); + + if (mstream->write(pBase, end - this->pbase()) == 0) + { + ch = EOF; + } + else if (ch == EOF) + { + ch = 0; + } + this->setp(pbuffer, pbuffer + pbuffer_size); + + return ch; + }; + + std::streampos seekposforce(std::streampos __pos, std::ios_base::openmode __mode = std::ios_base::in) + { + std::streampos __ret = std::streampos(off_type(-1)); + + if (mstream->seek(__pos)) + { + __ret = std::streampos(off_type(__pos)); + this->setg(gbuffer, gbuffer, gbuffer); + this->setp(pbuffer, pbuffer + pbuffer_size); + } + + return __ret; + } + + std::streampos seekpos(std::streampos __pos, std::ios_base::openmode __mode = std::ios_base::in) override + { + std::streampos __ret = std::streampos(off_type(-1)); + + // pbase - pbuffer start + // pptr - current pbuffer position + // epptr - pbuffer end + + // ebackk - gbuffer start + // gptr - current gbuffer position + // egptr - gbuffer end + + Debug_printv("meat buffer seekpos called, newPos=%d buffer=[%d,%d]", (size_t)__pos, (size_t)currBuffStart, (size_t)currBuffEnd); + + if (__pos >= currBuffStart && __pos < currBuffEnd) + { + Debug_printv("Seek withn chace, lucky!"); + + // we're seeing within existing buffer, so let's reuse + + // !!! + // NOTE - THIS PIECE OF CODE HAS TO BE THROUGHLY TESTED!!!! + // !!! + + std::streampos delta = __pos - currBuffStart; + // TODO - check if eback == gbuffer!!! + // TODO - check if pbase == pbuffer!!! + this->setg(this->eback(), gbuffer + delta, this->egptr()); + this->setp(this->pbase(), pbuffer + delta); + } + else if (mstream->seek(__pos)) + { + Debug_printv("Seek missed the chache, read required!"); + // the seek op isn't within existing buffer, so we need to actually + // call seek on stream and force underflow/overflow + + //__ret.state(_M_state_cur); + __ret = std::streampos(off_type(__pos)); + + // not sure if this is ok, but is supposed to cause underflow + // underflow will set it to: + // setg(gbuffer, gbuffer, gbuffer + readCount); + // begin next end + this->setg(gbuffer, gbuffer, gbuffer); + + // not sure if this is ok, but is supposed to cause overflow and prepare a clean buffer for writing + // that's how overflow does it after writing all: + // write(pbase, pptr-pbase) + // setp(pbuffer, pbuffer+pbuffer_size); + // begin end + this->setp(pbuffer, pbuffer + pbuffer_size); + } + + return __ret; + } + + /** + * @brief Synchronizes the buffer arrays with the controlled sequences. + * @return -1 on failure. + * + * Each derived class provides its own appropriate behavior, + * including the definition of @a failure. + * @note Base class version does nothing, returns zero. + * + * sync: Called on flush, should output any characters in the buffer to the sink. + * If you never call setp (so there's no buffer), you're always in sync, and this + * can be a no-op. overflow or uflow can call this one, or both can call some + * separate function. (About the only difference between sync and uflow is that + * uflow will only be called if there is a buffer, and it will never be called + * if the buffer is empty. sync will be called if the client code flushes the stream.) + */ + int sync() override + { + // Debug_printv("in wrapper sync"); + + if (this->pptr() == this->pbase()) + { + return 0; + } + else + { + const uint8_t *buffer = (uint8_t *)this->pbase(); + + // pptr = Returns the pointer to the current character (put pointer) in the put area. + // pbase = Returns the pointer to the beginning ("base") of the put area. + // epptr = Returns the pointer one past the end of the put area. + + // Debug_printv("before write call, mostream!=null[%d]", mostream!=nullptr); + auto result = mstream->write(buffer, this->pptr() - this->pbase()); + // Debug_printv("%d bytes left in buffer written to sink, rc=%d", pptr()-pbase(), result); + + this->setp(pbuffer, pbuffer + pbuffer_size); + + return (result != 0) ? 0 : -1; + } + }; + }; + + // [27.8.1.11] Template class basic_fstream + /** + * @brief Controlling input and output for files. + * @ingroup io + * + * @tparam _CharT Type of character stream. + * @tparam _Traits Traits for character type, defaults to + * char_traits<_CharT>. + * + * This class supports reading from and writing to named files, using + * the inherited functions from std::basic_iostream. To control the + * associated sequence, an instance of std::basic_filebuf is used, which + * this page refers to as @c sb. + */ + template > + class basic_fstream : public std::basic_iostream<_CharT, _Traits> + { + public: + // Types: + typedef _CharT char_type; + typedef _Traits traits_type; + typedef typename basic_fstream::traits_type::int_type int_type; + typedef typename basic_fstream::traits_type::pos_type pos_type; + typedef typename basic_fstream::traits_type::off_type off_type; + + // Non-standard types: + typedef mfilebuf __filebuf_type; + typedef std::basic_ios __ios_type; + typedef std::basic_iostream __iostream_type; + + private: + __filebuf_type _M_filebuf; + + public: + // Constructors/destructor: + /** + * @brief Default constructor. + * + * Initializes @c sb using its default constructor, and passes + * @c &sb to the base class initializer. Does not open any files + * (you haven't given it a filename to open). + */ + basic_fstream() : __iostream_type(), _M_filebuf() + { + this->init(&_M_filebuf); + } + + /** + * @brief Create an input/output file stream. + * @param __s Null terminated string specifying the filename. + * @param __mode Open file in specified mode (see std::ios_base). + */ + explicit basic_fstream(const char *__s, + std::ios_base::openmode __mode = std::ios_base::in) + : __iostream_type(0), _M_filebuf() + { + this->init(&_M_filebuf); + this->open(__s, __mode); + } + + explicit basic_fstream(MFile *fi, + std::ios_base::openmode __mode = std::ios_base::in) + : __iostream_type(0), _M_filebuf() + { + this->init(&_M_filebuf); + this->open(fi->url.c_str(), __mode); + } + +#if __cplusplus >= 201103L + /** + * @brief Create an input/output file stream. + * @param __s Null terminated string specifying the filename. + * @param __mode Open file in specified mode (see std::ios_base). + */ + explicit basic_fstream(const std::string &__s, std::ios_base::openmode __mode = std::ios_base::in) : __iostream_type(0), _M_filebuf() + { + this->init(&_M_filebuf); + this->open(__s, __mode); + } + +#if __cplusplus >= 201703L && __cplusplus < 202002L + /** + * @param Create an input/output file stream. + * @param __s filesystem::path specifying the filename. + * @param __mode Open file in specified mode (see std::ios_base). + */ + template > + basic_fstream(const _Path &__s, + ios_base::openmode __mode = ios_base::in | ios_base::out) + : basic_fstream(__s.c_str(), __mode) + { + } +#endif // C++17 + + basic_fstream(const basic_fstream &) = delete; + + // basic_fstream(basic_fstream &&__rhs) : __iostream_type(std::move(__rhs)), _M_filebuf(std::move(__rhs._M_filebuf)) + // { + // __iostream_type::set_rdbuf(&_M_filebuf); + // } +#endif + + /** + * @brief The destructor does nothing. + * + * The file is closed by the filebuf object, not the formatting + * stream. + */ + ~basic_fstream() + { + _M_filebuf.close(); + } + +#if __cplusplus >= 201103L + // 27.8.3.2 Assign and swap: + + basic_fstream &operator=(const basic_fstream &) = delete; + + basic_fstream &operator=(basic_fstream &&__rhs) + { + __iostream_type::operator=(std::move(__rhs)); + _M_filebuf = std::move(__rhs._M_filebuf); + return *this; + } + + void swap(basic_fstream &__rhs) + { + __iostream_type::swap(__rhs); + _M_filebuf.swap(__rhs._M_filebuf); + } +#endif + + // Members: + /** + * @brief Accessing the underlying buffer. + * @return The current basic_filebuf buffer. + * + * This hides both signatures of std::basic_ios::rdbuf(). + */ + __filebuf_type * + rdbuf() const + { + return const_cast<__filebuf_type *>(&_M_filebuf); + } + + /** + * @brief Wrapper to test for an open file. + * @return @c rdbuf()->is_open() + */ + bool is_open() + { + Debug_printv("is_open"); + return _M_filebuf.is_open(); + } + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 365. Lack of const-qualification in clause 27 + bool is_open() const + { + Debug_printv("is_open const"); + return _M_filebuf.is_open(); + } + + /** + * @brief Opens an external file. + * @param __s The name of the file. + * @param __mode The open mode flags. + * + * Calls @c std::basic_filebuf::open(__s,__mode). If that + * function fails, @c failbit is set in the stream's error state. + */ + void open(const char *__s, std::ios_base::openmode __mode = std::ios_base::in) + { + if (!_M_filebuf.open(__s, __mode)) + this->setstate(std::ios_base::failbit); + else + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 409. Closing an fstream should clear error state + this->clear(); + } + +#if __cplusplus >= 201103L + /** + * @brief Opens an external file. + * @param __s The name of the file. + * @param __mode The open mode flags. + * + * Calls @c std::basic_filebuf::open(__s,__mode). If that + * function fails, @c failbit is set in the stream's error state. + */ + void open(const std::string &__s, std::ios_base::openmode __mode = std::ios_base::in) + { + if (!_M_filebuf.open(__s, __mode)) + this->setstate(std::ios_base::failbit); + else + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 409. Closing an fstream should clear error state + this->clear(); + } + +#if __cplusplus >= 201703L && __cplusplus < 202002L + /** + * @brief Opens an external file. + * @param __s The name of the file, as a filesystem::path. + * @param __mode The open mode flags. + * + * Calls @c std::basic_filebuf::open(__s,__mode). If that + * function fails, @c failbit is set in the stream's error state. + */ + template + _If_fs_path<_Path, void> + open(const _Path &__s, + ios_base::openmode __mode = ios_base::in | ios_base::out) + { + open(__s.c_str(), __mode); + } +#endif // C++17 +#endif // C++11 + + /** + * @brief Close the file. + * + * Calls @c std::basic_filebuf::close(). If that function + * fails, @c failbit is set in the stream's error state. + */ + void close() + { + if (!_M_filebuf.close()) + this->setstate(std::ios_base::failbit); + } + + U8Char getUtf8() + { + U8Char codePoint(this); + return codePoint; + } + + char getPetscii() + { + return getUtf8().toPetscii(); + } + + void putPetsciiAsUtf8(char c) + { + U8Char wide = U8Char(c); + (*this) << wide.toUtf8(); + } + + bool nda() const { + return (this->rdstate() & ndabit) != 0; + } + + basic_fstream<_CharT, _Traits> &checkNda() + { + // first naive implementation... + + /* + istream->good(); // rdstate == 0 + istream->bad(); // rdstate() & badbit) != 0 + istream->eof(); // rdstate() & eofbit) != 0 + istream->fail(); // >rdstate() & (badbit | failbit)) != 0 + istream->rdstate(); + istream->setstate(); + _S_goodbit = 0, + _S_badbit = 1L << 0, + _S_eofbit = 1L << 1, + _S_failbit = 1L << 2, + _S_ios_iostate_end = 1L << 16, + _S_ios_iostate_max = __INT_MAX__, + _S_ios_iostate_min = ~__INT_MAX__ + */ + + const int_type __cb = this->rdbuf()->sgetc(); // check next char, don't advance + if(traits_type::eq_int_type(__cb, my_char_traits::nda())) { + // it's NDA - ok, so let's set a proper mark on this stream + this->rdbuf()->sbumpc(); // skip nda and... + this->setstate((std::ios_base::iostate)ndabit); // set state to nda + } + return *this; + } + }; + + typedef basic_fstream iostream; + + // iostream& operator>>(iostream& is, U8Char& c) { + // //U8Char codePoint(this); + // c = U8Char(&is); + // return is; + // } + + // https://stdcxx.apache.org/doc/stdlibug/39-3.html + // https://cplusplus.com/reference/istream/iostream/ +} + +#endif /* MEATLOAF_BUFFER */ diff --git a/lib/meatloaf/meat_io.cpp b/lib/meatloaf/meat_io.cpp new file mode 100644 index 0000000..e514cc8 --- /dev/null +++ b/lib/meatloaf/meat_io.cpp @@ -0,0 +1,601 @@ +#include "meat_io.h" + +#include +#include +#include +#include +#include + +#include "../../include/debug.h" + +#include "MIOException.h" + +#include "string_utils.h" +#include "peoples_url_parser.h" + +//#include "wrappers/directory_stream.h" + +// Archive + +// Cartridge + +// Container +#include "container/d8b.h" +#include "container/dfi.h" + +// Device +#include "device/flash.h" +#include "device/sd.h" + +// Disk +#include "disk/d64.h" +#include "disk/d71.h" +#include "disk/d80.h" +#include "disk/d81.h" +#include "disk/d82.h" +#include "disk/d90.h" +#include "disk/dnp.h" + +// File +#include "file/p00.h" +#include "archive/archive_ml.h" + +// Network +#include "network/http.h" +#include "network/tnfs.h" +// #include "network/ipfs.h" +// #include "network/tnfs.h" +// #include "network/smb.h" +// #include "network/ws.h" + +// Service +// #include "service/cs.h" +#include "service/ml.h" + + +// Tape +#include "tape/t64.h" +#include "tape/tcrt.h" + +#include "meat_buffer.h" + +/******************************************************** + * MFSOwner implementations + ********************************************************/ + +// initialize other filesystems here +FlashFileSystem defaultFS; +#ifdef SD_CARD +SDFileSystem sdFS; +#endif + +// Scheme +// HttpFileSystem httpFS; +// MLFileSystem mlFS; +// TNFSFileSystem tnfsFS; +// IPFSFileSystem ipfsFS; +// TNFSFileSystem tnfsFS; +// CServerFileSystem csFS; +// TcpFileSystem tcpFS; + +//WSFileSystem wsFS; + +// File +P00FileSystem p00FS; +//ArchiveContainerFileSystem archiveFS; + +// Disk +D64FileSystem d64FS; +D71FileSystem d71FS; +D80FileSystem d80FS; +D81FileSystem d81FS; +D82FileSystem d82FS; +D90FileSystem d90FS; +DNPFileSystem dnpFS; + +D8BFileSystem d8bFS; +DFIFileSystem dfiFS; + +// Tape +T64FileSystem t64FS; +TCRTFileSystem tcrtFS; + +// Cartridge + + + +// put all available filesystems in this array - first matching system gets the file! +// fist in list is default +std::vector MFSOwner::availableFS { + &defaultFS, +#ifdef SD_CARD + &sdFS, +#endif +// &archiveFS, // extension-based FS have to be on top to be picked first, otherwise the scheme will pick them! + &p00FS, + &d64FS, &d71FS, &d80FS, &d81FS, &d82FS, &d90FS, &dnpFS, + &d8bFS, &dfiFS, + &t64FS, &tcrtFS, +// &httpFS, &mlFS, &ipfsFS, +// &tnfsFS, &tcpFS, +// &tnfsFS +}; + +bool MFSOwner::mount(std::string name) { + Debug_print("MFSOwner::mount fs:"); + Debug_println(name.c_str()); + + for(auto i = availableFS.begin() + 1; i < availableFS.end() ; i ++) { + auto fs = (*i); + + if(fs->handles(name)) { + Debug_printv("MFSOwner found a proper fs"); + + bool ok = fs->mount(); + + if(ok) + Debug_print("Mounted fs: "); + else + Debug_print("Couldn't mount fs: "); + + Debug_println(name.c_str()); + + return ok; + } + } + return false; +} + +bool MFSOwner::umount(std::string name) { + for(auto i = availableFS.begin() + 1; i < availableFS.end() ; i ++) { + auto fs = (*i); + + if(fs->handles(name)) { + return fs->umount(); + } + } + return true; +} + +MFile* MFSOwner::File(MFile* file) { + return File(file->url); +} + +MFile* MFSOwner::File(std::shared_ptr file) { + return File(file->url); +} + + +MFile* MFSOwner::File(std::string path) { + //Debug_printv("in File\r\n"); + + // if(mstr::startsWith(path,"cs:", false)) { + // //Serial.printf("CServer path found!\r\n"); + // return csFS.getFile(path); + // } + + std::vector paths = mstr::split(path,'/'); + + Debug_printv("Trying to factory path [%s]", path.c_str()); + + auto pathIterator = paths.end(); + auto begin = paths.begin(); + auto end = paths.end(); + + auto foundFS = testScan(begin, end, pathIterator); + + if(foundFS != nullptr) { + Debug_printv("PATH: '%s' is in FS [%s]", path.c_str(), foundFS->symbol); + auto newFile = foundFS->getFile(path); + Debug_printv("newFile: '%s'", newFile->url.c_str()); + + pathIterator++; + newFile->pathInStream = mstr::joinToString(&pathIterator, &end, "/"); + Debug_printv("newFile->pathInStream: '%s'", newFile->pathInStream.c_str()); + + auto endHere = pathIterator; + pathIterator--; + + if(begin == pathIterator) + { + Debug_printv("** LOOK DOWN PATH NOT NEEDED path[%s]", path.c_str()); + newFile->streamFile = foundFS->getFile(mstr::joinToString(&begin, &pathIterator, "/")); + } + else + { + auto upperPath = mstr::joinToString(&begin, &pathIterator, "/"); + Debug_printv("** LOOK DOWN PATH: %s", upperPath.c_str()); + + auto upperFS = testScan(begin, end, pathIterator); + + if(upperFS != nullptr) { + auto wholePath = mstr::joinToString(&begin, &endHere, "/"); + + //auto cp = mstr::joinToString(&begin, &pathIterator, "/"); + Debug_printv("CONTAINER PATH WILL BE: '%s' ", wholePath.c_str()); + newFile->streamFile = upperFS->getFile(wholePath); // skończy się na d64 + Debug_printv("CONTAINER: '%s' is in FS [%s]", newFile->streamFile->url.c_str(), upperFS->symbol); + } + else { + Debug_printv("WARNING!!!! CONTAINER FAILED FOR: '%s'", upperPath.c_str()); + } + } + + return newFile; + } + + return nullptr; +} + +std::string MFSOwner::existsLocal( std::string path ) +{ + PeoplesUrlParser *url = PeoplesUrlParser::parseURL( path ); + + // Debug_printv( "path[%s] name[%s] size[%d]", path.c_str(), url.name.c_str(), url.name.size() ); + if ( url->name.size() == 16 ) + { + struct stat st; + int i = stat(std::string(path).c_str(), &st); + + // If not found try for a wildcard match + if ( i == -1 ) + { + DIR *dir; + struct dirent *ent; + + std::string p = url->pathToFile(); + std::string name = url->name; + // Debug_printv( "pathToFile[%s] basename[%s]", p.c_str(), name.c_str() ); + if ((dir = opendir ( p.c_str() )) != NULL) + { + /* print all the files and directories within directory */ + std::string e; + while ((ent = readdir (dir)) != NULL) { + // Debug_printv( "%s\r\n", ent->d_name ); + e = ent->d_name; + if ( mstr::compare( name, e ) ) + { + path = ( p + "/" + e ); + break; + } + } + closedir (dir); + } + } + } + + return path; +} + +MFileSystem* MFSOwner::testScan(std::vector::iterator &begin, std::vector::iterator &end, std::vector::iterator &pathIterator) { + while (pathIterator != begin) { + pathIterator--; + + auto part = *pathIterator; + mstr::toLower(part); + + //Debug_printv("index[%d] pathIterator[%s] size[%d]", pathIterator, pathIterator->c_str(), pathIterator->size()); + + auto foundIter=std::find_if(availableFS.begin() + 1, availableFS.end(), [&part](MFileSystem* fs){ + //Debug_printv("symbol[%s]", fs->symbol); + return fs->handles(part); + } ); + + if(foundIter != availableFS.end()) { + //Debug_printv("matched part '%s'\r\n", part.c_str()); + return (*foundIter); + } + }; + + auto fs = *availableFS.begin(); + //Debug_printv("return default file system [%s]", fs->symbol); + return fs; +} + +/******************************************************** + * MFileSystem implementations + ********************************************************/ + +MFileSystem::MFileSystem(const char* s) +{ + symbol = s; +} + +MFileSystem::~MFileSystem() {} + +/******************************************************** + * MFile implementations + ********************************************************/ + +MFile::MFile(std::string path) { + // Debug_printv("path[%s]", path.c_str()); + + // if ( mstr::contains(path, "$") ) + // { + // // Create directory stream here + // Debug_printv("Create directory stream here!"); + // path = ""; + // } + + resetURL(path); +} + +MFile::MFile(std::string path, std::string name) : MFile(path + "/" + name) { + if(mstr::startsWith(name, "xn--")) { + this->path = path + "/" + U8Char::fromPunycode(name); + } +} + +MFile::MFile(MFile* path, std::string name) : MFile(path->path + "/" + name) { + if(mstr::startsWith(name, "xn--")) { + this->path = path->path + "/" + U8Char::fromPunycode(name); + } +} + +bool MFile::operator!=(nullptr_t ptr) { + return m_isNull; +} + +MStream* MFile::getSourceStream(std::ios_base::openmode mode) { + // has to return OPENED stream + std::shared_ptr containerStream(streamFile->getSourceStream(mode)); // get its base stream, i.e. zip raw file contents + Debug_printv("containerStream isRandomAccess[%d] isBrowsable[%d]", containerStream->isRandomAccess(), containerStream->isBrowsable()); + + MStream* decodedStream(getDecodedStream(containerStream)); // wrap this stream into decoded stream, i.e. unpacked zip files + decodedStream->url = this->url; + Debug_printv("decodedStream isRandomAccess[%d] isBrowsable[%d]", decodedStream->isRandomAccess(), decodedStream->isBrowsable()); + + Debug_printv("pathInStream [%s]", pathInStream.c_str()); + + if(decodedStream->isRandomAccess() && pathInStream != "") { + bool foundIt = decodedStream->seekPath(this->pathInStream); + + if(!foundIt) + { + Debug_printv("path in stream not found"); + return nullptr; + } + } + else if(decodedStream->isBrowsable() && pathInStream != "") { + auto pointedFile = decodedStream->seekNextEntry(); + + while (!pointedFile.empty()) + { + if(mstr::compare(this->pathInStream, pointedFile)) + { + //Debug_printv("returning decodedStream"); + return decodedStream; + } + + pointedFile = decodedStream->seekNextEntry(); + } + Debug_printv("path in stream not found!"); + if(pointedFile.empty()) + return nullptr; + } + + //Debug_printv("returning decodedStream"); + return decodedStream; +}; + +MFile* MFile::cd(std::string newDir) +{ + Debug_printv("cd requested: [%s]", newDir.c_str()); + if ( streamFile != nullptr) + Debug_printv("streamFile[%s]", streamFile->url.c_str()); + + // OK to clarify - coming here there should be ONLY path or magicSymbol-path combo! + // NO "cd:xxxxx", no "/cd:xxxxx" ALLOWED here! ****************** + // + // if you want to support LOAD"CDxxxxxx" just parse/drop the CD BEFORE calling this function + // and call it ONLY with the path you want to change into! + + if(newDir.find(':') != std::string::npos) + { + // I can only guess we're CDing into another url scheme, this means we're changing whole path + return MFSOwner::File(newDir); + } + else if(newDir[0]=='_') // {CBM LEFT ARROW} + { + // user entered: CD:_ or CD_ + // means: go up one directory + + // user entered: CD:_DIR or CD_DIR + // means: go to a directory in the same directory as this one + return cdParent(mstr::drop(newDir,1)); + } + else if(newDir[0]=='.' && newDir[1]=='.') + { + if(newDir.size()==2) + { + // user entered: CD:.. or CD.. + // means: go up one directory + return cdParent(); + } + else + { + // user entered: CD:..DIR or CD..DIR + // meaning: Go back one directory + return cdLocalParent(mstr::drop(newDir,2)); + } + } + else if(newDir[0]=='/' && newDir[1]=='/') + { + // user entered: CD:// or CD// + // means: change to the root of stream + + // user entered: CD://DIR or CD//DIR + // means: change to a dir in root of stream + return cdLocalRoot(mstr::drop(newDir,2)); + } + else if(newDir[0]=='/') + { + // user entered: CD:/DIR or CD/DIR + // means: go to a directory in the same directory as this one + return cdParent(mstr::drop(newDir,1)); + } + else if(newDir[0]=='^') // {CBM UP ARROW} + { + // user entered: CD:^ or CD^ + // means: change to flash root + return MFSOwner::File("/"); + } + else + { + //newDir = mstr::toUTF8( newDir ); + + // Add new directory to path + if ( !mstr::endsWith(url, "/") && newDir.size() ) + url.push_back('/'); + + Debug_printv("> url[%s] newDir[%s]", url.c_str(), newDir.c_str()); + + // Add new directory to path + MFile* newPath = MFSOwner::File(url + newDir); + + if(mstr::endsWith(newDir, ".url", false)) { + // we need to get actual url + + //auto reader = Meat::New(newDir); + //auto istream = reader->getSourceStream(); + Meat::iostream reader(newPath); + + + //uint8_t url[istream->size()]; // NOPE, streams have no size! + //istream->read(url, istream->size()); + std::string url; + reader >> url; + + Debug_printv("url[%s]", url.c_str()); + //std::string ml_url((char *)url); + + delete newPath; + newPath = MFSOwner::File(url); + } + + return newPath; + } +}; + + +MFile* MFile::cdParent(std::string plus) +{ + Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str()); + + // drop last dir + // add plus + if(path.empty()) + { + // from here we can go only to flash root! + return MFSOwner::File("/"); + } + else + { + int lastSlash = url.find_last_of('/'); + if ( lastSlash == url.size() - 1 ) + { + lastSlash = url.find_last_of('/', url.size() - 2); + } + std::string newDir = mstr::dropLast(url, url.size() - lastSlash); + if(!plus.empty()) + newDir+= ("/" + plus); + + return MFSOwner::File(newDir); + } +}; + +MFile* MFile::cdLocalParent(std::string plus) +{ + Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str()); + // drop last dir + // check if it isn't shorter than streamFile + // add plus + int lastSlash = url.find_last_of('/'); + if ( lastSlash == url.size() - 1 ) { + lastSlash = url.find_last_of('/', url.size() - 2); + } + std::string parent = mstr::dropLast(url, url.size() - lastSlash); + if(parent.length()-streamFile->url.length()>1) + parent = streamFile->url; + return MFSOwner::File( parent + "/" + plus ); +}; + +MFile* MFile::cdRoot(std::string plus) +{ + Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str()); + return MFSOwner::File( "/" + plus ); +}; + +MFile* MFile::cdLocalRoot(std::string plus) +{ + Debug_printv("url[%s] path[%s] plus[%s]", url.c_str(), path.c_str(), plus.c_str()); + + if ( path.empty() || streamFile == nullptr ) { + // from here we can go only to flash root! + return MFSOwner::File( "/" + plus ); + } + return MFSOwner::File( streamFile->url + "/" + plus ); +}; + +// bool MFile::copyTo(MFile* dst) { +// Debug_printv("in copyTo\r\n"); +// Meat::iostream istream(this); +// Meat::iostream ostream(dst); + +// int rc; + +// Debug_printv("in copyTo, iopen=%d oopen=%d\r\n", istream.is_open(), ostream.is_open()); + +// if(!istream.is_open() || !ostream.is_open()) +// return false; + +// Debug_printv("commencing copy\r\n"); + +// while((rc = istream.get())!= EOF) { +// ostream.put(rc); +// if(ostream.bad() || istream.bad()) +// return false; +// } + +// Debug_printv("copying finished, rc=%d\r\n", rc); + +// return true; +// }; + +uint64_t MFile::getAvailableSpace() +{ + if ( mstr::startsWith(path, (char *)"/sd") ) + { +#ifdef SD_CARD + FATFS* fsinfo; + DWORD fre_clust; + + if (f_getfree("/", &fre_clust, &fsinfo) == 0) + { + uint64_t total = ((uint64_t)(fsinfo->csize)) * (fsinfo->n_fatent - 2) * (fsinfo->ssize); + uint64_t used = ((uint64_t)(fsinfo->csize)) * ((fsinfo->n_fatent - 2) - (fsinfo->free_clst)) * (fsinfo->ssize); + uint64_t free = total - used; + //Debug_printv("total[%llu] used[%llu free[%llu]", total, used, free); + return free; + } +#endif + } + else + { +#ifdef FLASH_SPIFFS + size_t total = 0, used = 0; + esp_spiffs_info("flash", &total, &used); + size_t free = total - used; + //Debug_printv("total[%d] used[%d] free[%d]", total, used, free); + return free; +#elif FLASH_LITTLEFS + // TODO: Implement for LITTLEFS + Debug_printv("LITTLEFS getAvailableSpace()"); +#endif + } + + return 65535; +} + + + diff --git a/lib/meatloaf/meat_io.h b/lib/meatloaf/meat_io.h new file mode 100644 index 0000000..f44dd01 --- /dev/null +++ b/lib/meatloaf/meat_io.h @@ -0,0 +1,223 @@ +#ifndef MEATLOAF_FILE +#define MEATLOAF_FILE + +#include +#include +#include +#include + +//#include "../../include/debug.h" + +//#include "wrappers/iec_buffer.h" + +#include "meat_stream.h" +#include "peoples_url_parser.h" +#include "string_utils.h" +#include "U8Char.h" + +#define _MEAT_NO_DATA_AVAIL (std::ios_base::eofbit) + +static const std::ios_base::iostate ndabit = _MEAT_NO_DATA_AVAIL; + + +/******************************************************** + * Universal file + ********************************************************/ + +class MFile : public PeoplesUrlParser { +public: + MFile() {}; // only for local FS!!! + MFile(nullptr_t null) : m_isNull(true) {}; + MFile(std::string path); + MFile(std::string path, std::string name); + MFile(MFile* path, std::string name); + + virtual ~MFile() { + if(streamFile != nullptr) { + // Debug_printv("WARNING: streamFile null in '%s' destructor. This MFile was obviously not initialized properly!", url.c_str()); + // } + // else { + //Debug_printv("Deleting: [%s]", this->url.c_str()); + delete streamFile; + } + }; + + bool isPETSCII = false; + std::string media_header; + std::string media_id; + std::string media_archive; + std::string media_image; + uint16_t media_blocks_free = 0; + uint16_t media_block_size = 256; + + bool operator!=(nullptr_t ptr); + + // bool copyTo(MFile* dst); + + // has to return OPENED stream + virtual MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in); + virtual MStream* getDecodedStream(std::shared_ptr src) = 0; + + MFile* cd(std::string newDir); + MFile* cdParent(std::string = ""); + MFile* cdLocalParent(std::string); + MFile* cdRoot(std::string); + MFile* cdLocalRoot(std::string); + virtual bool isDirectory() = 0; + virtual bool rewindDirectory() = 0 ; + virtual MFile* getNextFileInDir() = 0 ; + virtual bool mkDir() = 0 ; + + virtual bool exists() { return _exists; }; + virtual bool remove() = 0; + virtual bool rename(std::string dest) = 0; + virtual time_t getLastWrite() = 0 ; + virtual time_t getCreationTime() = 0 ; + virtual uint64_t getAvailableSpace(); + + virtual uint32_t size() { + return _size; + }; + virtual uint32_t blocks() { + auto s = size(); + if ( s > 0 && s < media_block_size ) + return 1; + else + return ( s / media_block_size ); + } + + virtual bool isText() { + return mstr::isText(extension); + } + + MFile* streamFile = nullptr; + std::string pathInStream; + + uint32_t _size = 0; + uint32_t _exists = true; + +protected: + bool m_isNull; + +friend class MFSOwner; +}; + +/******************************************************** + * Filesystem instance + * it knows how to create a MFile instance! + ********************************************************/ + +class MFileSystem { +public: + MFileSystem(const char* symbol); + virtual ~MFileSystem() = 0; + virtual bool mount() { return true; }; + virtual bool umount() { return true; }; + virtual bool handles(std::string path) = 0; + virtual MFile* getFile(std::string path) = 0; + bool isMounted() { + return _is_mounted; + } + + static bool byExtension(const char* ext, std::string fileName) { + return mstr::endsWith(fileName, ext, false); + } + + static bool byExtension(const std::vector &ext, std::string fileName) { + for ( const auto &e : ext ) + { + if ( mstr::endsWith(fileName, e.c_str(), false) ) + return true; + } + + return false; + } + +protected: + const char* symbol = nullptr; + bool _is_mounted = false; + + friend class MFSOwner; +}; + + +/******************************************************** + * MFile factory + ********************************************************/ + +class MFSOwner { +public: + static std::vector availableFS; + + static MFile* File(std::string name); + static MFile* File(std::shared_ptr file); + static MFile* File(MFile* file); + + static MFileSystem* scanPathLeft(std::vector paths, std::vector::iterator &pathIterator); + + static std::string existsLocal( std::string path ); + static MFileSystem* testScan(std::vector::iterator &begin, std::vector::iterator &end, std::vector::iterator &pathIterator); + + + static bool mount(std::string name); + static bool umount(std::string name); +}; + +/******************************************************** + * Meat namespace, standard C++ buffers and streams + ********************************************************/ + +namespace Meat { + struct _Unique_mf { + typedef std::unique_ptr _Single_file; + }; + + // Creates a unique_ptr for a given url + + /** + * @brief Creates a unique_ptr instance froma given url + * @param url The url to the file. + * @return @c unique_ptr + */ + template + typename _Unique_mf::_Single_file + New(std::string url) { + return std::unique_ptr(MFSOwner::File(url)); + } + + /** + * @brief Creates a unique_ptr instance froma given url + * @param url The url to the file. + * @return @c unique_ptr + */ + template + typename _Unique_mf::_Single_file + New(const char* url) { + return std::unique_ptr(MFSOwner::File(std::string(url))); + } + + /** + * @brief Creates a unique_ptr instance froma given MFile + * @param file The url to the file. + * @return @c unique_ptr + */ + template + typename _Unique_mf::_Single_file + New(MFile* mFile) { + return std::unique_ptr(MFSOwner::File(mFile->url)); + } + + /** + * @brief Wraps MFile* into unique_ptr so it closes itself as required + * @param file The url to the file. + * @return @c unique_ptr + */ + template + typename _Unique_mf::_Single_file + Wrap(MFile* file) { + return std::unique_ptr(file); + } + +} + +#endif // MEATLOAF_FILE diff --git a/lib/meatloaf/meat_stream.h b/lib/meatloaf/meat_stream.h new file mode 100644 index 0000000..3e6597a --- /dev/null +++ b/lib/meatloaf/meat_stream.h @@ -0,0 +1,138 @@ +#ifndef MEATLOAF_STREAM +#define MEATLOAF_STREAM + +#include + +/******************************************************** + * Universal streams + ********************************************************/ + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#define SA0 0b00001111 +#define SA1 0b00011111 +#define SA2 0b00101111 +#define SA3 0b00111111 +#define SA4 0b01001111 +#define SA5 0b01011111 +#define SA6 0b01101111 +#define SA7 0b01111111 +#define SA8 0b10001111 +#define SA9 0b10011111 +#define SA10 0b10101111 +#define SA11 0b10111111 +#define SA12 0b11001111 +#define SA13 0b11011111 +#define SA14 0b11101111 +#define SA15 0b11111111 +// SA for TCP: +// TCP_NON_BLOCKING = clear bit 4 +// TCP_BLOCKING = set bit 4 +// TCP_CLENT_SOCKET = clear bit 5 +// TCP_SERVER_SOCKET = set bit 5 + +class MStream +{ +protected: + uint32_t _size = 0; + uint32_t _position = 0; + uint8_t m_load_address[2] = {0, 0}; + uint8_t m_error = 0; + +public: + virtual ~MStream() {}; + + std::ios_base::openmode mode; + std::string url = ""; + + bool has_subdirs = true; + size_t block_size = 256; + + virtual std::unordered_map info() { + return {}; + } + + virtual uint32_t size() { + return _size; + }; + + virtual uint32_t available() { + return _size - _position; + }; + + virtual uint32_t position() { + return _position; + } + virtual void position( uint32_t p) { + _position = p; + } + + virtual size_t error() { + return m_error; + } + + virtual uint32_t blocks() { + if ( _size > 0 && _size < block_size ) + return 1; + else + return ( _size / block_size ); + } + + virtual bool eos() { +// Debug_printv("_size[%d] m_bytesAvailable[%d] _position[%d]", _size, available(), _position); + if ( available() == 0 ) + return true; + + return false; + } + virtual void reset() + { + _size = block_size; + _position = 0; + }; + + virtual bool isOpen() = 0; + virtual bool isBrowsable() { return false; }; + virtual bool isRandomAccess() { return false; }; + + virtual void close() = 0; + virtual bool open() = 0; + + virtual uint32_t write(const uint8_t *buf, uint32_t size) = 0; + virtual uint32_t read(uint8_t* buf, uint32_t size) = 0; + + virtual bool seek(uint32_t pos, int mode) { + if(mode == SEEK_SET) { + return seek(pos); + } + else if(mode == SEEK_CUR) { + if(pos == 0) return true; + return seek(position()+pos); + } + else { + return seek(size() - pos); + } + } + virtual bool seek(uint32_t pos) = 0; + + // For files with a browsable random access directory structure + // d64, d74, d81, dnp, etc. + virtual bool seekPath(std::string path) { + return false; + }; + + // For files with no directory structure + // tap, crt, tar + virtual std::string seekNextEntry() { + return ""; + }; + + virtual bool seekBlock( uint64_t index, uint8_t offset = 0 ) { return false; }; + virtual bool seekSector( uint8_t track, uint8_t sector, uint8_t offset = 0 ) { return false; }; + virtual bool seekSector( std::vector trackSectorOffset ) { return false; }; +}; + + +#endif // MEATLOAF_STREAM diff --git a/lib/meatloaf/network/afp.h b/lib/meatloaf/network/afp.h new file mode 100755 index 0000000..d33cde2 --- /dev/null +++ b/lib/meatloaf/network/afp.h @@ -0,0 +1 @@ +// AFP:// - Apple File Protocol diff --git a/lib/meatloaf/network/codenet.h b/lib/meatloaf/network/codenet.h new file mode 100644 index 0000000..2cc347f --- /dev/null +++ b/lib/meatloaf/network/codenet.h @@ -0,0 +1,6 @@ +// +// Codenet is a simple Network Protocol invented by John "Graham" Selck. +// It was quickly adopted by others and became the defacto standard for cross developing using the RR-Net +// +// http://wiki.icomp.de/wiki/Codenet +// diff --git a/lib/meatloaf/network/fsp.h b/lib/meatloaf/network/fsp.h new file mode 100755 index 0000000..a1ed4f0 --- /dev/null +++ b/lib/meatloaf/network/fsp.h @@ -0,0 +1 @@ +// FSP:// - File Service Protocol diff --git a/lib/meatloaf/network/ftp.h b/lib/meatloaf/network/ftp.h new file mode 100755 index 0000000..821580e --- /dev/null +++ b/lib/meatloaf/network/ftp.h @@ -0,0 +1 @@ +// FTP:// - File Transfer Protocol diff --git a/lib/meatloaf/network/http.cpp b/lib/meatloaf/network/http.cpp new file mode 100644 index 0000000..3c4dc67 --- /dev/null +++ b/lib/meatloaf/network/http.cpp @@ -0,0 +1,575 @@ +// #include "http.h" + +// #include + +// #include "../../../include/debug.h" +// #include "../../../include/global_defines.h" + +// /******************************************************** +// * File impls +// ********************************************************/ + +// MeatHttpClient* HttpFile::fromHeader() { +// if(client == nullptr) { +// //Debug_printv("Client was not present, creating"); +// client = new MeatHttpClient(); + +// // let's just get the headers so we have some info +// //Debug_printv("Client requesting head"); +// //Debug_printv("before head url[%s]", url.c_str()); +// client->HEAD(url); +// //Debug_printv("after head url[%s]", client->url.c_str()); +// resetURL(client->url); +// } +// return client; +// } + +// bool HttpFile::isDirectory() { +// if(fromHeader()->m_isDirectory) +// return true; + +// if(fromHeader()->m_isWebDAV) { +// // try webdav PROPFIND to get a listing +// return true; +// } +// else +// // otherwise return false +// return false; +// } + +// MStream* HttpFile::getSourceStream(std::ios_base::openmode mode) { +// // has to return OPENED stream +// //Debug_printv("Input stream requested: [%s]", url.c_str()); + +// // headers have to be supplied here, they might be set on this channel using +// // i.e. CBM DOS commands like +// // H+:Accept: */* +// // H+:Accept-Encoding: gzip, deflate +// // here you add them all to a map, like this: +// std::map headers; +// // headers["Accept"] = "*/*"; +// // headers["Accept-Encoding"] = "gzip, deflate"; +// // etc. +// MStream* istream = new HttpIStream(url, mode, headers); +// istream->open(); + +// return istream; +// } + +// MStream* HttpFile::getDecodedStream(std::shared_ptr is) { +// return is.get(); // DUMMY return value - we've overriden istreamfunction, so this one won't be used +// } + +// time_t HttpFile::getLastWrite() { +// if(fromHeader()->m_isWebDAV) { +// return 0; +// } +// else +// // take from webdav PROPFIND or fallback to Last-Modified +// return 0; +// } + +// time_t HttpFile::getCreationTime() { +// if(fromHeader()->m_isWebDAV) { +// return 0; +// } +// else +// // take from webdav PROPFIND or fallback to Last-Modified +// return 0; +// } + +// bool HttpFile::exists() { +// return fromHeader()->_exists; +// } + +// uint32_t HttpFile::size() { +// if(fromHeader()->m_isWebDAV) { +// // take from webdav PROPFIND +// return 0; +// } +// else +// // fallback to what we had from the header +// return fromHeader()->_size; +// } + +// bool HttpFile::remove() { +// if(fromHeader()->m_isWebDAV) { +// // PROPPATCH allows deletion +// return false; +// } +// return false; +// } + +// bool HttpFile::mkDir() { +// if(fromHeader()->m_isWebDAV) { +// // MKCOL creates dir +// return false; +// } +// return false; +// } + +// bool HttpFile::rewindDirectory() { +// if(fromHeader()->m_isWebDAV) { +// // we can try if this is webdav, then +// // PROPFIND allows listing dir +// return false; +// } +// return false; +// }; + +// MFile* HttpFile::getNextFileInDir() { +// Debug_printv(""); +// if(fromHeader()->m_isWebDAV) { +// // we can try if this is webdav, then +// // PROPFIND allows listing dir +// return nullptr; +// } +// return nullptr; +// }; + + +// bool HttpFile::isText() { +// return fromHeader()->isText; +// } + +// /******************************************************** +// * Istream impls +// ********************************************************/ +// bool HttpIStream::open() { +// bool r = false; +// m_http.setHeaders(headers); + +// if(mode == (std::ios_base::out | std::ios_base::app)) +// r = m_http.PUT(url); +// else if(mode == std::ios_base::out) +// r = m_http.POST(url); +// else +// r = m_http.GET(url); + +// if ( r ) { +// _size = m_http._size; +// } + +// return r; +// } + +// void HttpIStream::close() { +// //Debug_printv("CLOSE called explicitly on this HTTP stream!"); +// m_http.close(); +// } + +// bool HttpIStream::seek(uint32_t pos) { +// if ( !m_http._is_open ) +// { +// Debug_printv("error"); +// return false; +// } + +// return m_http.seek(pos); +// } + +// uint32_t HttpIStream::read(uint8_t* buf, uint32_t size) { +// uint32_t bytesRead = 0; +// if ( size > available() ) +// size = available(); + +// if ( size > 0 ) +// { +// bytesRead = m_http.read(buf, size); +// _position += bytesRead; +// } + +// return bytesRead; +// }; + +// uint32_t HttpIStream::write(const uint8_t *buf, uint32_t size) { +// uint32_t bytesWritten = m_http.write(buf, size); +// _position += bytesWritten; +// return bytesWritten; +// } + + + +// bool HttpIStream::isOpen() { +// return m_http._is_open; +// }; + +// // uint32_t HttpIStream::size() { +// // return m_http._size; +// // }; + +// // uint32_t HttpIStream::available() { +// // return m_http.m_bytesAvailable; +// // }; + +// // uint32_t HttpIStream::position() { +// // return m_http._position; +// // } + +// // size_t HttpIStream::error() { +// // return m_http.m_error; +// // } + +// /******************************************************** +// * Meat HTTP client impls +// ********************************************************/ +// bool MeatHttpClient::GET(std::string dstUrl) { +// Debug_printv("GET"); +// return open(dstUrl, HTTP_METHOD_GET); +// } + +// bool MeatHttpClient::POST(std::string dstUrl) { +// Debug_printv("POST"); +// return open(dstUrl, HTTP_METHOD_POST); +// } + +// bool MeatHttpClient::PUT(std::string dstUrl) { +// Debug_printv("PUT"); +// return open(dstUrl, HTTP_METHOD_PUT); +// } + +// bool MeatHttpClient::HEAD(std::string dstUrl) { +// Debug_printv("HEAD"); +// bool rc = open(dstUrl, HTTP_METHOD_HEAD); +// close(); +// return rc; +// } + +// bool MeatHttpClient::processRedirectsAndOpen(int range) { +// wasRedirected = false; +// _size = -1; + +// Debug_printv("reopening url[%s] from position:%d", url.c_str(), range); +// lastRC = openAndFetchHeaders(lastMethod, range); + +// while(lastRC == HttpStatus_MovedPermanently || lastRC == HttpStatus_Found || lastRC == 303) +// { +// Debug_printv("--- Page moved, doing redirect to [%s]", url.c_str()); +// close(); +// lastRC = openAndFetchHeaders(lastMethod, range); +// wasRedirected = true; +// } + +// if(lastRC != HttpStatus_Ok && lastRC != 301 && lastRC != 206) { +// Debug_printv("opening stream failed, httpCode=%d", lastRC); +// close(); +// return false; +// } + +// // TODO - set m_isWebDAV somehow +// _is_open = true; +// _exists = true; +// _position = 0; + +// Debug_printv("length[%d] avail[%d] isFriendlySkipper[%d] isText[%d] httpCode[%d]", _size, available(), isFriendlySkipper, isText, lastRC); + +// return true; +// } + +// bool MeatHttpClient::open(std::string dstUrl, esp_http_client_method_t meth) { +// url = dstUrl; +// lastMethod = meth; +// //m_error = 0; + +// return processRedirectsAndOpen(0); +// }; + +// void MeatHttpClient::close() { +// if(m_http != nullptr) { +// if ( _is_open ) { +// esp_http_client_close(m_http); +// } +// esp_http_client_cleanup(m_http); +// //Debug_printv("HTTP Close and Cleanup"); +// m_http = nullptr; +// } +// _is_open = false; +// } + +// void MeatHttpClient::setOnHeader(const std::function &lambda) { +// onHeader = lambda; +// } + +// bool MeatHttpClient::seek(uint32_t pos) { +// if(pos==_position) +// return true; + +// if(isFriendlySkipper) { +// esp_http_client_close(m_http); + +// bool op = processRedirectsAndOpen(pos); + +// Debug_printv("SEEK in HttpIStream %s: range request RC=%d", url.c_str(), lastRC); + +// if(!op) +// return false; + +// // 200 = range not supported! according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests +// if(lastRC == 206){ +// Debug_printv("Seek successful"); + +// _position = pos; +// return true; +// } +// } + +// if(lastMethod == HTTP_METHOD_GET) { +// Debug_printv("Server doesn't support resume, reading from start and discarding"); +// // server doesn't support resume, so... +// if(pos<_position || pos == 0) { +// // skipping backward let's simply reopen the stream... +// esp_http_client_close(m_http); +// bool op = open(url, lastMethod); +// if(!op) +// return false; + +// // and read pos bytes - requires some buffer +// for(int i = 0; i0) { +// _position+=bytesRead; +// } +// return bytesRead; +// } +// return 0; +// }; + +// uint32_t MeatHttpClient::write(const uint8_t* buf, uint32_t size) { +// if (_is_open) { +// auto bytesWritten= esp_http_client_write(m_http, (char *)buf, size ); +// _position+=bytesWritten; +// return bytesWritten; +// } +// return 0; +// }; + +// int MeatHttpClient::openAndFetchHeaders(esp_http_client_method_t meth, int resume) { + +// if ( url.size() < 5) +// return 0; + +// mstr::replaceAll(url, " ", "%20"); +// esp_http_client_config_t config = { +// .url = url.c_str(), +// .user_agent = USER_AGENT, +// .method = meth, +// .timeout_ms = 10000, +// .max_redirection_count = 10, +// .event_handler = _http_event_handler, +// .user_data = this, +// .keep_alive_enable = true, +// .keep_alive_idle = 10, +// .keep_alive_interval = 1 +// }; + +// //Debug_printv("HTTP Init url[%s]", url.c_str()); +// m_http = esp_http_client_init(&config); + +// for (const auto& pair : headers) { +// std::cout << pair.first << ": " << pair.second << std::endl; +// esp_http_client_set_header(m_http, pair.first.c_str(), pair.second.c_str()); +// } + +// if(resume > 0) { +// char str[40]; +// snprintf(str, sizeof str, "bytes=%lu-", (unsigned long)resume); +// esp_http_client_set_header(m_http, "range", str); +// } + +// //Debug_printv("--- PRE OPEN"); + +// esp_err_t initOk = esp_http_client_open(m_http, 0); // or open? It's not entirely clear... + +// if(initOk == ESP_FAIL) +// return 0; + +// //Debug_printv("--- PRE FETCH HEADERS"); + +// int lengthResp = esp_http_client_fetch_headers(m_http); +// if(_size == -1 && lengthResp > 0) { +// // only if we aren't chunked! +// _size = lengthResp; +// _position = 0; +// } + +// //Debug_printv("--- PRE GET STATUS CODE"); + +// return esp_http_client_get_status_code(m_http); +// } + +// esp_err_t MeatHttpClient::_http_event_handler(esp_http_client_event_t *evt) +// { +// MeatHttpClient* meatClient = (MeatHttpClient*)evt->user_data; + +// switch(evt->event_id) { +// case HTTP_EVENT_ERROR: // This event occurs when there are any errors during execution +// Debug_printv("HTTP_EVENT_ERROR"); +// //meatClient->m_error = 1; +// break; + +// case HTTP_EVENT_ON_CONNECTED: // Once the HTTP has been connected to the server, no data exchange has been performed +// // Debug_printv("HTTP_EVENT_ON_CONNECTED"); +// break; + +// case HTTP_EVENT_HEADER_SENT: // After sending all the headers to the server +// // Debug_printv("HTTP_EVENT_HEADER_SENT"); +// break; + +// case HTTP_EVENT_ON_HEADER: // Occurs when receiving each header sent from the server +// // Does this server support resume? +// // Accept-Ranges: bytes + +// if (mstr::equals("Accept-Ranges", evt->header_key, false)) +// { +// if(meatClient != nullptr) { +// meatClient->isFriendlySkipper = mstr::equals("bytes", evt->header_value,false); +// //Debug_printv("* Ranges info present '%s', comparison=%d!",evt->header_value, strcmp("bytes", evt->header_value)==0); +// } +// } +// // what can we do UTF8<->PETSCII on this stream? +// else if (mstr::equals("Content-Type", evt->header_key, false)) +// { +// std::string asString = evt->header_value; +// bool isText = mstr::isText(asString); + +// if(meatClient != nullptr) { +// meatClient->isText = isText; +// //Debug_printv("* Content info present '%s', isText=%d!, type=%s", evt->header_value, isText, asString.c_str()); +// } +// } +// else if(mstr::equals("Last-Modified", evt->header_key, false)) +// { +// // Last-Modified, value=Thu, 03 Dec 1992 08:37:20 - may be used to get file date +// } +// else if(mstr::equals("Content-Disposition", evt->header_key, false)) +// { +// // Content-Disposition, value=attachment; filename*=UTF-8''GeckOS-c64.d64 +// // we can set isText from real file extension, too! +// std::string value = evt->header_value; +// if ( mstr::contains( value, (char *)"index.prg" ) ) +// { +// Debug_printv("HTTP Directory Listing [%s]", meatClient->url.c_str()); +// meatClient->m_isDirectory = true; +// } +// } +// else if(mstr::equals("Content-Length", evt->header_key, false)) +// { +// //Debug_printv("* Content len present '%s'", evt->header_value); +// meatClient->_size = std::stoi(evt->header_value); +// } +// else if(mstr::equals("Location", evt->header_key, false)) +// { +// Debug_printv("* This page redirects from '%s' to '%s'", meatClient->url.c_str(), evt->header_value); +// if ( mstr::startsWith(evt->header_value, (char *)"http") ) +// { +// //Debug_printv("match"); +// meatClient->url = evt->header_value; +// } +// else +// { +// //Debug_printv("no match"); +// if ( mstr::startsWith(evt->header_value, (char *)"/") ) +// { +// // Absolute path redirect +// PeoplesUrlParser *u = PeoplesUrlParser::parseURL( meatClient->url ); +// meatClient->url = u->root() + evt->header_value; +// } +// else +// { +// // Relative path redirect +// meatClient->url += evt->header_value; +// } +// } + +// Debug_printv("new url '%s'", meatClient->url.c_str()); +// } + +// // Allow override in lambda +// meatClient->onHeader(evt->header_key, evt->header_value); + +// break; + +// #if __cplusplus > 201703L +// //#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 3, 0) +// case HTTP_EVENT_REDIRECT: + +// Debug_printv("* This page redirects from '%s' to '%s'", meatClient->url.c_str(), evt->header_value); +// if ( mstr::startsWith(evt->header_value, (char *)"http") ) +// { +// //Debug_printv("match"); +// meatClient->url = evt->header_value; +// } +// else +// { +// //Debug_printv("no match"); +// meatClient->url += evt->header_value; +// } +// break; +// #endif + +// case HTTP_EVENT_ON_DATA: // Occurs multiple times when receiving body data from the server. MAY BE SKIPPED IF BODY IS EMPTY! +// //Debug_printv("HTTP_EVENT_ON_DATA, len=%d", evt->data_len); +// { +// // int status = esp_http_client_get_status_code(meatClient->m_http); + +// // if ((status == HttpStatus_Found || status == HttpStatus_MovedPermanently || status == 303) /*&& client->_redirect_count < (client->_max_redirects - 1)*/) +// // { +// // //Debug_printv("HTTP_EVENT_ON_DATA: Redirect response body, ignoring"); +// // } +// // else { +// // //Debug_printv("HTTP_EVENT_ON_DATA: Got response body"); +// // } + + +// if (esp_http_client_is_chunked_response(evt->client)) { +// int len; +// esp_http_client_get_chunk_length(evt->client, &len); +// meatClient->_size = len; +// //Debug_printv("HTTP_EVENT_ON_DATA: Got chunked response, chunklen=%d, contentlen[%d]", len, meatClient->_size); +// } +// } +// break; + +// case HTTP_EVENT_ON_FINISH: +// // Occurs when finish a HTTP session +// // This may get called more than once if esp_http_client decides to retry in order to handle a redirect or auth response +// //Debug_printv("HTTP_EVENT_ON_FINISH %u\r\n", uxTaskGetStackHighWaterMark(nullptr)); +// // Keep track of how many times we "finish" reading a response from the server +// //Debug_printv("HTTP_EVENT_ON_FINISH"); +// break; +// case HTTP_EVENT_DISCONNECTED: // The connection has been disconnected +// //Debug_printv("HTTP_EVENT_DISCONNECTED"); +// //meatClient->m_bytesAvailable = 0; +// break; +// } +// return ESP_OK; +// } diff --git a/lib/meatloaf/network/http.h b/lib/meatloaf/network/http.h new file mode 100755 index 0000000..3365359 --- /dev/null +++ b/lib/meatloaf/network/http.h @@ -0,0 +1,194 @@ +// // HTTP:// - Hypertext Transfer Protocol +// // HTTPS:// - Hypertext Transfer Protocol Secure +// // https://buger.dread.cz/simple-esp8266-https-client-without-verification-of-certificate-fingerprint.html +// // https://forum.arduino.cc/t/esp8266-httpclient-library-for-https/495245 +// // + + +// #ifndef MEATLOAF_SCHEME_HTTP +// #define MEATLOAF_SCHEME_HTTP + +// #include "meat_io.h" + +// #include +// #include +// #include + +// #include "../../../include/debug.h" +// //#include "../../include/global_defines.h" +// //#include "../../include/version.h" + +// #define HTTP_BLOCK_SIZE 256 + +// //#define PRODUCT_ID "MEATLOAF CBM" +// //#define PLATFORM_DETAILS "C64; 6510; 2; NTSC; EN;" // Make configurable. This will help server side to select appropriate content. +// //#define USER_AGENT "MEATLOAF/" FN_VERSION_FULL " (" PLATFORM_DETAILS ")" + +// class MeatHttpClient { +// esp_http_client_handle_t m_http = nullptr; +// static esp_err_t _http_event_handler(esp_http_client_event_t *evt); +// int openAndFetchHeaders(esp_http_client_method_t meth, int resume = 0); +// esp_http_client_method_t lastMethod; +// std::function onHeader = [] (char* key, char* value){ +// //Debug_printv("HTTP_EVENT_ON_HEADER, key=%s, value=%s", key, value); +// return 0; +// }; + +// std::map headers; + +// public: + +// MeatHttpClient() { +// } + +// ~MeatHttpClient() { +// close(); +// } + +// void setHeaders(std::map &h) { +// headers = h; +// } + +// bool GET(std::string url); +// bool POST(std::string url); +// bool PUT(std::string url); +// bool HEAD(std::string url); + +// bool processRedirectsAndOpen(int range); +// bool open(std::string url, esp_http_client_method_t meth); +// void close(); +// void setOnHeader(const std::function &f); +// bool seek(uint32_t pos); +// uint32_t read(uint8_t* buf, uint32_t size); +// uint32_t write(const uint8_t* buf, uint32_t size); + +// bool _is_open = false; +// bool _exists = false; + +// uint32_t available() { +// return _size - _position; +// } + +// uint32_t _size = 0; +// // uint32_t m_bytesAvailable = 0; +// uint32_t _position = 0; +// // size_t m_error = 0; + +// bool m_isWebDAV = false; +// bool m_isDirectory = false; +// bool isText = false; +// bool isFriendlySkipper = false; +// bool wasRedirected = false; +// std::string url; + +// int lastRC = 0; +// }; + +// /******************************************************** +// * File implementations +// ********************************************************/ + + +// class HttpFile: public MFile { +// MeatHttpClient* fromHeader(); +// MeatHttpClient* client = nullptr; + +// public: +// HttpFile() { +// Debug_printv("C++, if you try to call this, be damned!"); +// }; +// HttpFile(std::string path): MFile(path) { +// // Debug_printv("constructing http file from url [%s]", url.c_str()); +// }; +// HttpFile(std::string path, std::string filename): MFile(path) {}; +// ~HttpFile() override { +// if(client != nullptr) +// delete client; +// } +// bool isDirectory() override; +// MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED streamm +// time_t getLastWrite() override ; +// time_t getCreationTime() override ; +// bool rewindDirectory() override ; +// MFile* getNextFileInDir() override ; +// bool mkDir() override ; +// bool exists() override ; +// uint32_t size() override ; +// bool remove() override ; +// bool isText() override ; +// bool rename(std::string dest) { return false; }; +// MStream* getDecodedStream(std::shared_ptr src); +// //void addHeader(const String& name, const String& value, bool first = false, bool replace = true); +// }; + + +// /******************************************************** +// * Streams +// ********************************************************/ + +// class HttpIStream: public MStream { +// std::map headers; + +// public: +// HttpIStream(std::string path) { +// url = path; +// }; +// HttpIStream(std::string path, std::ios_base::openmode m, std::map &h) { +// url = path; +// mode = m; +// headers = h; +// }; + +// ~HttpIStream() { +// close(); +// }; + +// // MStream methods +// // uint32_t size() override; +// // uint32_t available() override; +// // uint32_t position() override; +// // size_t error() override; + +// virtual bool seek(uint32_t pos); + +// void close() override; +// bool open() override; + +// // MStream methods +// uint32_t read(uint8_t* buf, uint32_t size) override; +// uint32_t write(const uint8_t *buf, uint32_t size) override; + +// bool isOpen() override; + +// protected: +// MeatHttpClient m_http; + +// }; + + + +// /******************************************************** +// * FS +// ********************************************************/ + +// class HttpFileSystem: public MFileSystem +// { +// MFile* getFile(std::string path) override { +// return new HttpFile(path); +// } + +// bool handles(std::string name) { +// if ( mstr::equals(name, (char *)"http:", false) ) +// return true; + +// if ( mstr::equals(name, (char *)"https:", false) ) +// return true; + +// return false; +// } +// public: +// HttpFileSystem(): MFileSystem("http") {}; +// }; + + +// #endif /* MEATLOAF_SCHEME_HTTP */ diff --git a/lib/meatloaf/network/ipfs.cpp b/lib/meatloaf/network/ipfs.cpp new file mode 100644 index 0000000..e4a22c8 --- /dev/null +++ b/lib/meatloaf/network/ipfs.cpp @@ -0,0 +1,22 @@ +// #include "ipfs.h" + +// #include "../../../include/global_defines.h" +// #include "../../../include/debug.h" + + +// MStream* IPFSFile::getSourceStream(std::ios_base::openmode mode) { +// // has to return OPENED stream +// //Debug_printv("[%s]", url.c_str()); +// MStream* istream = new IPFSIStream(url); +// istream->open(); +// return istream; +// }; + + +// bool IPFSIStream::open() { +// return m_http.GET(url); +// }; + +// bool IPFSIStream::seek(uint32_t pos) { +// return true; +// } \ No newline at end of file diff --git a/lib/meatloaf/network/ipfs.h b/lib/meatloaf/network/ipfs.h new file mode 100644 index 0000000..ae7e7cb --- /dev/null +++ b/lib/meatloaf/network/ipfs.h @@ -0,0 +1,122 @@ +// // IPFS - Interplanetary File System +// // https://ipfs.tech/ +// // https://github.com/exmgr/ipfs-client-esp32 +// // https://developers.cloudflare.com/web3/ +// // https://web3.storage/ +// // https://docs.ipfs.tech/reference/kubo/rpc/#getting-started +// // https://www.sciencedirect.com/science/article/pii/S1877042811023998 +// // + +// // https://ipfs.io or https://cloudflare-ipfs.com. +// // https://ipfs.github.io/public-gateway-checker/ + +// // +// // List Directory +// // https://ipfs.io/api/v0/ls?arg=QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi +// // https://dweb.link/api/v0/ls?arg=QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi +// // +// // List Directory from root CID +// // https://dweb.link/api/v0/ls?arg=QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9 +// // +// // Download File Directly +// // https://ipfs.io/ipfs/QmYuvTLSEmreSvz13zoFEofD7zWNoDBSbESkEZkdovEDTG +// // https://dweb.link/ipfs/QmYuvTLSEmreSvz13zoFEofD7zWNoDBSbESkEZkdovEDTG?filename=1000+miler.d64 +// // https://dweb.link/api/v0/get?arg=QmYuvTLSEmreSvz13zoFEofD7zWNoDBSbESkEZkdovEDTG +// // +// // Download File from root CID with path +// // https://dweb.link/ipfs/QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9/1000%20miler.d64 +// // +// // Download File with byte ranges +// // read first 20 bytes of a file +// // https://dweb.link/api/v0/cat?arg=QmTSGxVkFCshrgCsuZhrmk2ucuRXToEvsRXxM9vwvJWiMp&offset=0&length=20 +// // +// // File Stat +// // https://dweb.link/api/v0/object/stat?arg=QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9/1000%20miler.d64 +// // IPFS://localhost:8080/api/v0/object/stat?arg=QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9/1000%20miler.d64 +// // NumLinks = 0, it is a file +// // DataSize = {file_size} + 10 bytes +// // +// // https://github.com/ipfs/kubo/issues/8528 +// // +// // IPFS HEAD to determine DIR or FILE +// // content-type: text/html +// // etag: "DIRIndex-*" = Directory +// // https://ipfs.io/ipfs/QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9 +// // +// // content-type: application/octet-stream = File +// // content-length: >0 = File +// // https://ipfs.io/ipfs/QmbpUikCNvAbtUCgqvjMiKRFZbbNRAeg8V2KVyVqmtp7Fi/0-9/1000 miler.d64 +// // + +// // OTHER IMPLEMENTATIONS +// // https://dat-ecosystem.org/ +// // https://hypercore-protocol.org/ +// // +// // + + +// #ifndef MEATLOAF_SCHEME_IPFS +// #define MEATLOAF_SCHEME_IPFS + +// #include "network/http.h" + +// #include "../../../include/debug.h" + +// #include "peoples_url_parser.h" + + +// /******************************************************** +// * File +// ********************************************************/ + +// class IPFSFile: public HttpFile { + +// public: +// IPFSFile(std::string path): HttpFile(path) { +// //this->url = "https://dweb.link/ipfs/" + this->host + "/" + this->path; +// this->url = "https://ipfs.io/ipfs/" + this->host + "/" + this->path; +// resetURL(this->url); +// Debug_printv("url[%s]", this->url.c_str()); +// }; +// ~IPFSFile() {}; + +// MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override; // file on IPFS server = standard HTTP file available via GET +// }; + + +// /******************************************************** +// * Streams +// ********************************************************/ + +// class IPFSIStream: public HttpIStream { + +// public: +// IPFSIStream(std::string path) : HttpIStream(path) {}; +// ~IPFSIStream() {}; + +// bool open() override; +// bool seek(uint32_t pos) override; +// }; + + +// /******************************************************** +// * FS +// ********************************************************/ + +// class IPFSFileSystem: public MFileSystem +// { +// MFile* getFile(std::string path) override { +// // Debug_printv("IPFSFileSystem::getFile(%s)", path.c_str()); +// return new IPFSFile(path); +// } + +// bool handles(std::string name) { +// std::string pattern = "ipfs:"; +// return mstr::startsWith(name, pattern.c_str(), false); +// } + +// public: +// IPFSFileSystem(): MFileSystem("ipfs") {}; +// }; + +// #endif // MEATLOAF_SCHEME_IPFS \ No newline at end of file diff --git a/lib/meatloaf/network/nc.h b/lib/meatloaf/network/nc.h new file mode 100644 index 0000000..e307ff9 --- /dev/null +++ b/lib/meatloaf/network/nc.h @@ -0,0 +1,3 @@ +// NC:// - Netcat +// https://nc110.sourceforge.io/ +// diff --git a/lib/meatloaf/network/nfs.h b/lib/meatloaf/network/nfs.h new file mode 100755 index 0000000..0bf91f7 --- /dev/null +++ b/lib/meatloaf/network/nfs.h @@ -0,0 +1,5 @@ +// NFS - Network File System +// https://en.wikipedia.org/wiki/Network_File_System +// +// https://github.com/sahlberg/libnfs +// diff --git a/lib/meatloaf/network/sftp.h b/lib/meatloaf/network/sftp.h new file mode 100755 index 0000000..495d3b6 --- /dev/null +++ b/lib/meatloaf/network/sftp.h @@ -0,0 +1,3 @@ +// SFTP:// - SSH File Transfer Protocol +// https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol +// diff --git a/lib/meatloaf/network/smb.h b/lib/meatloaf/network/smb.h new file mode 100755 index 0000000..4e95426 --- /dev/null +++ b/lib/meatloaf/network/smb.h @@ -0,0 +1,3 @@ +// SMB:// - Server Messagee Block Protocol +// https://en.wikipedia.org/wiki/Server_Message_Block +// diff --git a/lib/meatloaf/network/tcp.h b/lib/meatloaf/network/tcp.h new file mode 100644 index 0000000..3fbeb8b --- /dev/null +++ b/lib/meatloaf/network/tcp.h @@ -0,0 +1,354 @@ +#ifndef MEATLOAF_SCHEME_TCP +#define MEATLOAF_SCHEME_TCP + +#include "lwip/sockets.h" +#include "lwip/netdb.h" +#include "../../include/debug.h" + +// +// This is a standard "reading socket" - i.e. if you connect to a remote server +// +class MeatSocket { + int sock = -1; + uint8_t iecPort = 0; + bool blocking = false; + +public: + MeatSocket() {}; + MeatSocket(int s, uint8_t iecp) : sock(s), iecPort(iecp) { + // for socket created by our server + } + + bool open(const char *address, u16_t port) { + struct sockaddr_in dest_addr; + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(port); + dest_addr.sin_addr.s_addr = inet_addr(address); + //Debug_printv("dest_addr.sin_addr.s_addr=%x", dest_addr.sin_addr.s_addr); + if (dest_addr.sin_addr.s_addr == 0xffffffff) { + struct hostent *hp; + hp = gethostbyname(address); + if (hp == NULL) { + Debug_printv("TCP Client Error: Connect to %s", address); + return false; + } + struct ip4_addr *ip4_addr; + ip4_addr = (struct ip4_addr *)hp->h_addr; + dest_addr.sin_addr.s_addr = ip4_addr->addr; + } + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); // SCOK_STREAM = TCP/IP SOCK_DGRAM = UDP + if (sock < 0) { + Debug_printv("Unable to create socket: errno %d", errno); + return false; + } + //Debug_printv("Socket created, connecting to %s:%d", address, port); + + int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6)); + + if (err != 0) { + Debug_printv("Socket unable to connect: errno %d", errno); + return false; + } + //Debug_printv("After connect for socet"); + + return true; + } + + void close() { + closesocket(sock); + shutdown(sock, 0); + + sock = -1; + } + + size_t write(const void* buffer, size_t bufsize) { + if(!isOpen()) + return -1; + return send(sock, buffer, bufsize, 0); + } + + int read(void* buffer, size_t bufsize) { + // might work in non-blocking mode. In this mode recv returns + // BSD_ERROR_WOULDBLOCK and then we can poll again, that's what we want + // TODO - check what's the value of BSD_ERROR_WOULDBLOCK and if recv returns + // error - mark this socket closed + if(!isOpen()) { + Debug_println("tcp read - NOT OPEN!\r\n"); + return -100; + } + //Debug_printv("tcp::read - calling recv, buff!=null:%d, buffsize=%d, blocking=%d", buffer!=nullptr, bufsize, blocking); + int byteCount = recv(sock, buffer, bufsize, (blocking) ? 0 : MSG_DONTWAIT); + //Debug_printv("tcp::read - post recv"); + if(!blocking && byteCount == -1) { + return _MEAT_NO_DATA_AVAIL; + } + + return byteCount; + } + + bool isOpen() { + return sock != -1; + } +}; + +// +// This is a local server socket +// It waits for a connection and then opens a new "reading socket" for exclusive communication with anyone that connects to ML +// +class MeatSocketServer { + bool isAlive = false; + int port = 0; + uint8_t iecPort = 0; + + void start(int p) { + port = p; + xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)this, 5, NULL); + } + + void shutdown() { + // openSockets.foreach { it.close() } + // openScokets.clear() + isAlive = false; + } + + static void tcp_server_task(void *param) + { + MeatSocketServer* meatServer = (MeatSocketServer*)param; + struct sockaddr_storage dest_addr; + int ip_protocol = 0; + int keepAlive = 1; + int keepIdle = 10; + int keepInterval = 5; + int keepCount = 5; + char addr_str[128]; + + int port = meatServer->port; + + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(port); + ip_protocol = IPPROTO_IP; + + int listen_sock = socket(AF_INET, SOCK_STREAM, ip_protocol); + if (listen_sock < 0) { + Debug_printv("Unable to create socket: errno %d", errno); + vTaskDelete(NULL); + return; + } + int opt = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + Debug_printv("Socket created"); + + int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err != 0) { + Debug_printv("Socket unable to bind: errno %d", errno); + Debug_printv("IPPROTO: %d", AF_INET); + goto CLEAN_UP; + } + Debug_printv("Socket bound, port %d", port); + + err = listen(listen_sock, 1); + if (err != 0) { + Debug_printv("Error occurred during listen: errno %d", errno); + goto CLEAN_UP; + } + + meatServer->isAlive = true; + + while (meatServer->isAlive) { + + Debug_printv("Socket listening"); + + struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6 + socklen_t addr_len = sizeof(source_addr); + int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); + if (sock < 0) { + Debug_printv("Unable to accept connection: errno %d", errno); + break; + } + + // Set tcp keepalive option + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int)); + setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int)); + setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int)); + setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int)); + // Convert ip address to string + if (source_addr.ss_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1); + } + #ifdef CONFIG_EXAMPLE_IPV6 + else if (source_addr.ss_family == PF_INET6) { + inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1); + } + #endif + Debug_printv("Socket accepted ip address: %s", addr_str); + + // do_retransmit(sock); + // + // we'll do this here instead: add a new socket that starts one IEC port above server or currently open ports + // if(openSocket.count < 9) { + // auto newSock = new MeatSocket(sock, iecPort + openSocket.count +1); + // openSockets.Add(newSock); + // } + // purgeClosedSockets(); + + + } + + CLEAN_UP: + close(listen_sock); + vTaskDelete(NULL); + } +}; + + + + +class TcpStream: public MStream { + +public: + TcpStream(std::string path) { + url = path; + }; + ~TcpStream() { + close(); + }; + + // MStream methods + uint32_t size() override { + return -1; + } + uint32_t available() override { + return 0; + } + uint32_t position() override { + return 0; + } + size_t error() override { + return 0; + } + + virtual bool seek(uint32_t pos) { + return false; + } + + void close() override { + socket.close(); + } + + bool open() override { + PeoplesUrlParser *p = PeoplesUrlParser::parseURL( url ); + return socket.open(p->host.c_str(), p->getPort()); + } + + // MStream methods + uint32_t read(uint8_t* buf, uint32_t size) override { + return socket.read(buf, size); + } + uint32_t write(const uint8_t *buf, uint32_t size) override { + return socket.write(buf, size); + } + + bool isOpen() { + return socket.isOpen(); + } + +protected: + MeatSocket socket; + std::string url; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + + +class TcpFile: public MFile { + +public: + TcpFile() { + Debug_printv("C++, if you try to call this, be damned!"); + }; + TcpFile(std::string path): MFile(path) { + Debug_printv("constructing tcp file from url [%s]", url.c_str()); + }; + TcpFile(std::string path, std::string filename): MFile(path) {}; + ~TcpFile() override { + } + bool isDirectory() override { + return false; + } + + // We are overriding getSourceStream, because obviously - TCP scheme won't be wrapped in anything + MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override { + // has to return OPENED streamm + MStream* istream = new TcpStream(url); + istream->open(); + return istream; + } + + // DUMMY return value - we've overriden getSourceStream, so this one won't be even called! + MStream* getDecodedStream(std::shared_ptr src) { + return nullptr; + } + + time_t getLastWrite() override { + return 0; + } + time_t getCreationTime() override { + return 0; + } + bool rewindDirectory() override { + return false; + } + MFile* getNextFileInDir() override { + return nullptr; + } + bool mkDir() override { + return false; + } + bool exists() override { + return true; + } + uint32_t size() override { + return -1; + } + bool remove() override { + return false; + } + bool isText() override { + return false; + } + bool rename(std::string dest) { return false; }; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class TcpFileSystem: public MFileSystem +{ + MFile* getFile(std::string path) override { + return new TcpFile(path); + } + + bool handles(std::string name) { + if ( mstr::equals(name, (char *)"tcp:", false) ) + return true; + + return false; + } +public: + TcpFileSystem(): MFileSystem("tcp") {}; +}; + + + +#endif /* MEATFILESYSTEM_SCHEME_TCP */ diff --git a/lib/meatloaf/network/telnet.h b/lib/meatloaf/network/telnet.h new file mode 100755 index 0000000..e395dd9 --- /dev/null +++ b/lib/meatloaf/network/telnet.h @@ -0,0 +1,3 @@ +// TELNET:// +// https://en.wikipedia.org/wiki/Telnet +// diff --git a/lib/meatloaf/network/tftp.h b/lib/meatloaf/network/tftp.h new file mode 100755 index 0000000..dfbcc29 --- /dev/null +++ b/lib/meatloaf/network/tftp.h @@ -0,0 +1,3 @@ +// TFTP:// - Trivial File Transfer Protocol +// https://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol +// diff --git a/lib/meatloaf/network/tnfs.cpp b/lib/meatloaf/network/tnfs.cpp new file mode 100644 index 0000000..151f3d2 --- /dev/null +++ b/lib/meatloaf/network/tnfs.cpp @@ -0,0 +1,428 @@ +// #include "tnfs.h" + +// #include "../../../include/debug.h" + +// #include +// #include + + + +// /******************************************************** +// * MFile implementations +// ********************************************************/ + +// bool TNFSFile::pathValid(std::string path) +// { +// auto apath = std::string(basepath + path).c_str(); +// while (*apath) { +// const char *slash = strchr(apath, '/'); +// if (!slash) { +// if (strlen(apath) >= FILENAME_MAX) { +// // Terminal filename is too long +// return false; +// } +// break; +// } +// if ((slash - apath) >= FILENAME_MAX) { +// // This subdir name too long +// return false; +// } +// apath = slash + 1; +// } + +// return true; +// } + +// bool TNFSFile::isDirectory() +// { +// if(path=="/" || path=="") +// return true; + +// struct stat info; +// stat( std::string(basepath + path).c_str(), &info); +// return S_ISDIR(info.st_mode); +// } + +// MStream* TNFSFile::getDecodedStream(std::shared_ptr is) { +// return is.get(); // we don't have to process this stream in any way, just return the original stream +// } + +// MStream* TNFSFile::getSourceStream(std::ios_base::openmode mode) +// { +// std::string full_path = basepath + path; +// MStream* istream = new TNFSIStream(full_path); +// //Debug_printv("TNFSFile::getSourceStream() 3, not null=%d", istream != nullptr); +// istream->open(); +// //Debug_printv("TNFSFile::getSourceStream() 4"); +// return istream; +// } + +// time_t TNFSFile::getLastWrite() +// { +// struct stat info; +// stat( std::string(basepath + path).c_str(), &info); + +// time_t ftime = info.st_mtime; // Time of last modification +// return ftime; +// } + +// time_t TNFSFile::getCreationTime() +// { +// struct stat info; +// stat( std::string(basepath + path).c_str(), &info); + +// time_t ftime = info.st_ctime; // Time of last status change +// return ftime; +// } + +// bool TNFSFile::mkDir() +// { +// if (m_isNull) { +// return false; +// } +// int rc = mkdir(std::string(basepath + path).c_str(), ALLPERMS); +// return (rc==0); +// } + +// bool TNFSFile::exists() +// { +// if (m_isNull) { +// return false; +// } +// if (path=="/" || path=="") { +// return true; +// } + +// //Debug_printv( "basepath[%s] path[%s]", basepath.c_str(), path.c_str() ); + +// struct stat st; +// int i = stat(std::string(basepath + path).c_str(), &st); + +// return (i == 0); +// } + +// uint32_t TNFSFile::size() { +// if (m_isNull || path=="/" || path=="") +// return 0; +// else if(isDirectory()) { +// return 0; +// } +// else { +// struct stat info; +// stat( std::string(basepath + path).c_str(), &info); +// // Debug_printv( "size[%d]", info.st_size ); +// return info.st_size; +// } +// } + +// bool TNFSFile::remove() { +// // musi obslugiwac usuwanie plikow i katalogow! +// if(path.empty()) +// return false; + +// int rc = ::remove( std::string(basepath + path).c_str() ); +// if (rc != 0) { +// Debug_printv("remove: rc=%d path=`%s`\r\n", rc, path); +// return false; +// } + +// return true; +// } + + +// bool TNFSFile::rename(std::string pathTo) { +// if(pathTo.empty()) +// return false; + +// int rc = ::rename( std::string(basepath + path).c_str(), std::string(basepath + pathTo).c_str() ); +// if (rc != 0) { +// return false; +// } +// return true; +// } + + +// void TNFSFile::openDir(std::string apath) +// { +// if (!isDirectory()) { +// dirOpened = false; +// return; +// } + +// // Debug_printv("path[%s]", apath.c_str()); +// if(apath.empty()) { +// dir = opendir( "/" ); +// } +// else { +// dir = opendir( apath.c_str() ); +// } + +// dirOpened = true; +// if ( dir == NULL ) { +// dirOpened = false; +// } +// // else { +// // // Skip the . and .. entries +// // struct dirent* dirent = NULL; +// // dirent = readdir( dir ); +// // dirent = readdir( dir ); +// // } +// } + + +// void TNFSFile::closeDir() +// { +// if(dirOpened) { +// closedir( dir ); +// dirOpened = false; +// } +// } + + +// bool TNFSFile::rewindDirectory() +// { +// _valid = false; +// rewinddir( dir ); + +// // // Skip the . and .. entries +// // struct dirent* dirent = NULL; +// // dirent = readdir( dir ); +// // dirent = readdir( dir ); + +// return (dir != NULL) ? true: false; +// } + + +// MFile* TNFSFile::getNextFileInDir() +// { +// // Debug_printv("base[%s] path[%s]", basepath.c_str(), path.c_str()); +// if(!dirOpened) +// openDir(std::string(basepath + path).c_str()); + +// if(dir == nullptr) +// return nullptr; + +// // Debug_printv("before readdir(), dir not null:%d", dir != nullptr); +// struct dirent* dirent = NULL; +// if((dirent = readdir( dir )) != NULL) +// { +// // Debug_printv("path[%s] name[%s]", this->path, dirent->d_name); +// return new TNFSFile(this->path + ((this->path == "/") ? "" : "/") + std::string(dirent->d_name)); +// } +// else +// { +// closeDir(); +// return nullptr; +// } +// } + + +// bool TNFSFile::seekEntry( std::string filename ) +// { +// DIR* d; +// std::string apath = (basepath + pathToFile()).c_str(); +// if (apath.empty()) { +// apath = "/"; +// } + +// Debug_printv( "path[%s] filename[%s] size[%d]", apath.c_str(), filename.c_str(), filename.size()); + +// d = opendir( apath.c_str() ); +// if(d == nullptr) +// return false; + +// // Read Directory Entries +// struct dirent* dirent = NULL; +// if ( filename.size() > 0 ) +// { +// while ( (dirent = readdir( d )) != NULL ) +// { +// std::string entryFilename = dirent->d_name; + +// Debug_printv("path[%s] filename[%s] entry.filename[%.16s]", apath.c_str(), filename.c_str(), entryFilename.c_str()); + +// // Read Entry From Stream +// if (filename == "*") +// { +// filename = entryFilename; +// closedir( d ); +// return true; +// } +// else if ( filename == entryFilename ) +// { +// closedir( d ); +// return true; +// } +// else if ( mstr::compare(filename, entryFilename) ) +// { +// // Set filename to this filename +// Debug_printv( "Found! file[%s] -> entry[%s]", filename.c_str(), entryFilename.c_str() ); +// resetURL(apath + "/" + std::string(dirent->d_name)); +// closedir( d ); +// return true; +// } +// } + +// Debug_printv( "Not Found! file[%s]", filename.c_str() ); +// } + +// closedir( d ); +// return false; +// } + + + +// /******************************************************** +// * MStream implementations +// ********************************************************/ +// uint32_t TNFSIStream::write(const uint8_t *buf, uint32_t size) { +// if (!isOpen() || !buf) { +// return 0; +// } + +// //Debug_printv("in byteWrite '%c', handle->file_h is null=[%d]\r\n", buf[0], handle->file_h == nullptr); + +// // buffer, element size, count, handle +// int result = fwrite((void*) buf, 1, size, handle->file_h ); + +// //Debug_printv("after lfs_file_write"); + +// if (result < 0) { +// Debug_printv("write rc=%d\r\n", result); +// } +// return result; +// }; + + +// /******************************************************** +// * MIStreams implementations +// ********************************************************/ + + +// bool TNFSIStream::open() { +// if(isOpen()) +// return true; + +// //Debug_printv("IStream: trying to open flash fs, calling isOpen"); + +// //Debug_printv("IStream: wasn't open, calling obtain"); +// handle->obtain(localPath, "r"); + +// if(isOpen()) { +// //Debug_printv("IStream: past obtain"); +// // Set file size +// fseek(handle->file_h, 0, SEEK_END); +// //Debug_printv("IStream: past fseek 1"); +// _size = ftell(handle->file_h); +// //Debug_printv("IStream: past ftell"); +// fseek(handle->file_h, 0, SEEK_SET); +// //Debug_printv("IStream: past fseek 2"); +// return true; +// } +// return false; +// }; + +// void TNFSIStream::close() { +// if(isOpen()) handle->dispose(); +// }; + +// uint32_t TNFSIStream::read(uint8_t* buf, uint32_t size) { +// if (!isOpen() || !buf) { +// Debug_printv("Not open"); +// return 0; +// } + +// int bytesRead = fread((void*) buf, 1, size, handle->file_h ); + +// if (bytesRead < 0) { +// Debug_printv("read rc=%d\r\n", bytesRead); +// return 0; +// } + +// return bytesRead; +// }; + +// bool TNFSIStream::seek(uint32_t pos) { +// // Debug_printv("pos[%d]", pos); +// if (!isOpen()) { +// Debug_printv("Not open"); +// return false; +// } +// return ( fseek( handle->file_h, pos, SEEK_SET ) ) ? true : false; +// }; + +// bool TNFSIStream::seek(uint32_t pos, int mode) { +// // Debug_printv("pos[%d] mode[%d]", pos, mode); +// if (!isOpen()) { +// Debug_printv("Not open"); +// return false; +// } +// return ( fseek( handle->file_h, pos, mode ) ) ? true: false; +// } + +// bool TNFSIStream::isOpen() { +// // Debug_printv("Inside isOpen, handle notnull:%d", handle != nullptr); +// auto temp = handle != nullptr && handle->file_h != nullptr; +// // Debug_printv("returning"); +// return temp; +// } + +// /******************************************************** +// * TNFSHandle implementations +// ********************************************************/ + + +// TNFSHandle::~TNFSHandle() { +// dispose(); +// } + +// void TNFSHandle::dispose() { +// //Debug_printv("file_h[%d]", file_h); +// if (file_h != nullptr) { + +// fclose( file_h ); +// file_h = nullptr; +// // rc = -255; +// } +// } + +// void TNFSHandle::obtain(std::string m_path, std::string mode) { + +// //Serial.printf("*** Atempting opening flash handle'%s'\r\n", m_path.c_str()); + +// if ((mode[0] == 'w') && strchr(m_path.c_str(), '/')) { +// // For file creation, silently make subdirs as needed. If any fail, +// // it will be caught by the real file open later on + +// char *pathStr = new char[m_path.length()]; +// strncpy(pathStr, m_path.data(), m_path.length()); + +// if (pathStr) { +// // Make dirs up to the final fnamepart +// char *ptr = strchr(pathStr, '/'); +// while (ptr) { +// *ptr = 0; +// mkdir(pathStr, ALLPERMS); +// *ptr = '/'; +// ptr = strchr(ptr+1, '/'); +// } +// } +// delete[] pathStr; +// } + +// //Debug_printv("m_path[%s] mode[%s]", m_path.c_str(), mode.c_str()); +// file_h = fopen( m_path.c_str(), mode.c_str()); +// // rc = 1; + +// //Serial.printf("FSTEST: lfs_file_open file rc:%d\r\n",rc); + +// // if (rc == LFS_ERR_ISDIR) { +// // // To support the SD.openNextFile, a null FD indicates to the FlashFSFile this is just +// // // a directory whose name we are carrying around but which cannot be read or written +// // } else if (rc == 0) { +// // // lfs_file_sync(&TNFSFileSystem::lfsStruct, &file_h); +// // } else { +// // Debug_printv("TNFSFile::open: unknown return code rc=%d path=`%s`\r\n", +// // rc, m_path.c_str()); +// // } +// } diff --git a/lib/meatloaf/network/tnfs.h b/lib/meatloaf/network/tnfs.h new file mode 100644 index 0000000..08f2469 --- /dev/null +++ b/lib/meatloaf/network/tnfs.h @@ -0,0 +1,167 @@ +// #ifndef MEATLOAF_DEVICE_TNFS +// #define MEATLOAF_DEVICE_TNFS + +// #include "meat_io.h" + +// #include "fnFS.h" + +// #include "../../../include/debug.h" + +// #include "make_unique.h" + +// #include +// #include + + +// /******************************************************** +// * MFile +// ********************************************************/ + +// class TNFSFile: public MFile +// { +// friend class TNFSIStream; + +// public: +// std::string basepath = ""; + +// TNFSFile(std::string path) { + +// // Find full filename for wildcard +// if (mstr::contains(name, "?") || mstr::contains(name, "*")) +// seekEntry( name ); + +// if (!pathValid(path.c_str())) +// m_isNull = true; +// else +// m_isNull = false; + +// //Debug_printv("basepath[%s] path[%s] valid[%d]", basepath.c_str(), this->path.c_str(), m_isNull); +// }; +// ~TNFSFile() { +// //Serial.printf("*** Destroying flashfile %s\r\n", url.c_str()); +// closeDir(); +// } + +// //MFile* cd(std::string newDir); +// bool isDirectory() override; +// MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED stream +// time_t getLastWrite() override ; +// time_t getCreationTime() override ; +// bool rewindDirectory() override ; +// MFile* getNextFileInDir() override ; +// bool mkDir() override ; +// bool exists() override ; +// uint32_t size() override ; +// bool remove() override ; +// bool rename(std::string dest); +// MStream* getDecodedStream(std::shared_ptr src); + +// bool seekEntry( std::string filename ); + +// protected: +// DIR* dir; +// bool dirOpened = false; + +// private: +// FileSystem *_fs = nullptr; + +// virtual void openDir(std::string path); +// virtual void closeDir(); + +// bool _valid; +// std::string _pattern; + +// bool pathValid(std::string path); + +// }; + + +// /******************************************************** +// * TNFSHandle +// ********************************************************/ + +// class TNFSHandle { +// public: +// //int rc; +// FILE* file_h = nullptr; + +// TNFSHandle() +// { +// //Debug_printv("*** Creating flash handle"); +// memset(&file_h, 0, sizeof(file_h)); +// }; +// ~TNFSHandle(); +// void obtain(std::string localPath, std::string mode); +// void dispose(); + +// private: +// int flags = 0; +// }; + + +// /******************************************************** +// * MStream I +// ********************************************************/ + +// class TNFSIStream: public MStream { +// public: +// TNFSIStream(std::string& path) { +// localPath = path; +// handle = std::make_unique(); +// url = path; +// } +// ~TNFSIStream() override { +// close(); +// } + +// bool isBrowsable() override { return false; }; +// bool isRandomAccess() override { return true; }; + +// virtual bool seek(uint32_t pos) override; +// virtual bool seek(uint32_t pos, int mode) override; + +// void close() override; +// bool open() override; + +// // MStream methods +// //uint8_t read() override; +// uint32_t read(uint8_t* buf, uint32_t size) override; +// uint32_t write(const uint8_t *buf, uint32_t size) override; + +// virtual bool seekPath(std::string path) override { +// Debug_printv( "path[%s]", path.c_str() ); +// return false; +// } + +// bool isOpen(); + +// protected: +// std::string localPath; + +// std::unique_ptr handle; +// }; + + +// /******************************************************** +// * MFileSystem +// ********************************************************/ + +// class TNFSFileSystem: public MFileSystem +// { +// MFile* getFile(std::string path) override { +// return new TNFSFile(path); +// } + +// bool handles(std::string name) { +// if ( mstr::equals(name, (char *)"tnfs:", false) ) +// return true; + +// return false; +// } +// public: +// TNFSFileSystem(): MFileSystem("tnfs") {}; + +// }; + + +// #endif // MEATLOAF_DEVICE_TNFS diff --git a/lib/meatloaf/network/webdav.cpp b/lib/meatloaf/network/webdav.cpp new file mode 100644 index 0000000..0582184 --- /dev/null +++ b/lib/meatloaf/network/webdav.cpp @@ -0,0 +1,228 @@ +// #include "webdav.h" + +// /******************************************************** +// * File impls +// ********************************************************/ + +// bool WebDAVFile::isDirectory() { +// // hey, why not? +// return false; +// } + +// MStream* WebDAVFile::getSourceStream() { +// // has to return OPENED stream +// Debug_printv("[%s]", url.c_str()); +// MStream* istream = new WebDAVIStream(url); +// istream->open(); +// return istream; +// } + +// MStream* WebDAVFile::getDecodedStream(std::shared_ptr is) { +// return is.get(); // we've overriden istreamfunction, so this one won't be used +// } + +// time_t WebDAVFile::getLastWrite() { +// return 0; // might be taken from Last-Modified, probably not worth it +// } + +// time_t WebDAVFile::getCreationTime() { +// return 0; // might be taken from Last-Modified, probably not worth it +// } + +// bool WebDAVFile::exists() { +// Debug_printv("[%s]", url.c_str()); +// // we may try open the stream to check if it exists +// std::unique_ptr test(getSourceStream()); +// // remember that MStream destuctor should close the stream! +// return test->isOpen(); +// } + +// uint32_t WebDAVFile::size() { +// // we may take content-lenght from header if exists +// std::unique_ptr test(getSourceStream()); + +// size_t size = 0; + +// if(test->isOpen()) +// size = test->available(); + +// test->close(); + +// return size; +// } + + + +// // void WebDAVFile::addHeader(const String& name, const String& value, bool first, bool replace) { +// // //m_http.addHeader +// // } + + +// /******************************************************** +// * Ostream impls +// ********************************************************/ + +// bool WebDAVOStream::seek(uint32_t pos) { +// if(pos==_position) +// return true; + +// if(isFriendlySkipper) { +// char str[40]; +// // Range: bytes=91536-(91536+255) +// snprintf(str, sizeof str, "bytes=%lu-%lu", (unsigned long)pos, ((unsigned long)pos + 255)); +// m_http.set_header("range",str); +// int httpCode = m_http.GET(); //Send the request +// Debug_printv("httpCode[%d] str[%s]", httpCode, str); +// if(httpCode != 200 || httpCode != 206) +// return false; + +// Debug_printv("stream opened[%s]", url.c_str()); +// //m_file = m_http.getStream(); //Get the response payload as Stream +// _position = pos; +// return true; + +// } else { +// if(pos<_position) { +// // skipping backward and range not supported, let's simply reopen the stream... +// m_http.close(); +// bool op = open(); +// if(!op) +// return false; +// } + +// _position = 0; +// // ... and then read until we reach pos +// // while(_position < pos) { +// // _position+=m_file.readBytes(buffer, size); <----------- trurn this on!!!! +// // } + +// return true; +// } +// } + + + +// void WebDAVOStream::close() { +// m_http.close(); +// } + +// bool WebDAVOStream::open() { +// // we'll ad a lambda that will allow adding headers +// // m_http.addHeader("Content-Type", "application/x-www-form-urlencoded"); +// mstr::replaceAll(url, "HTTP:", "http:"); +// // m_http.setReuse(true); +// bool initOk = m_http.begin( url ); +// Debug_printv("[%s] initOk[%d]", url.c_str(), initOk); +// if(!initOk) +// return false; + +// //int httpCode = m_http.PUT(); //Send the request +// //Serial.printf("URLSTR: httpCode=%d\r\n", httpCode); +// // if(httpCode != 200) +// // return false; + +// _is_open = true; +// //m_file = m_http.getStream(); //Get the response payload as Stream +// return true; +// } + +// //uint32_t WebDAVOStream::write(uint8_t) {}; +// uint32_t WebDAVOStream::write(const uint8_t *buf, uint32_t size) { +// return 0; // m_file.write(buf, size); +// } + +// bool WebDAVOStream::isOpen() { +// return _is_open; +// } + + +// /******************************************************** +// * Istream impls +// ********************************************************/ + +// bool WebDAVIStream::seek(uint32_t pos) { +// if(pos==_position) +// return true; + +// if(isFriendlySkipper) { +// char str[40]; +// // Range: bytes=91536-(91536+255) +// snprintf(str, sizeof str, "bytes=%lu-%lu", (unsigned long)pos, ((unsigned long)pos + 255)); +// m_http.set_header("range",str); +// int httpCode = m_http.GET(); //Send the request +// Debug_printv("httpCode[%d] str[%s]", httpCode, str); +// if(httpCode != 200 || httpCode != 206) +// return false; + +// Debug_printv("stream opened[%s]", url.c_str()); +// //m_file = m_http.getStream(); //Get the response payload as Stream +// _position = pos; +// return true; + +// } else { +// if(pos<_position) { +// // skipping backward and range not supported, let's simply reopen the stream... +// m_http.close(); +// bool op = open(); +// if(!op) +// return false; +// } + +// _position = 0; +// // ... and then read until we reach pos +// // while(_position < pos) { +// // _position+=m_file.readBytes(buffer, size); <----------- trurn this on!!!! +// // } + +// return true; +// } +// } + +// void WebDAVIStream::close() { +// m_http.close(); +// } + +// bool WebDAVIStream::open() { +// //mstr::replaceAll(url, "HTTP:", "http:"); +// bool initOk = m_http.begin( url ); +// Debug_printv("input %s: someRc=%d", url.c_str(), initOk); +// if(!initOk) +// return false; + +// // Setup response headers we want to collect +// const char * headerKeys[] = {"accept-ranges", "content-type", "content-length"}; +// const size_t numberOfHeaders = 2; +// m_http.collect_headers(headerKeys, numberOfHeaders); + +// //Send the request +// int httpCode = m_http.GET(); +// Debug_printv("httpCode=%d", httpCode); +// if(httpCode != 200) +// return false; + +// // Accept-Ranges: bytes - if we get such header from any request, good! +// isFriendlySkipper = m_http.get_header("accept-ranges") == "bytes"; +// Debug_printv("isFriendlySkipper[%d]", isFriendlySkipper); +// _is_open = true; +// Debug_printv("[%s]", url.c_str()); +// //m_file = m_http.getStream(); //Get the response payload as Stream +// _size = stoi(m_http.get_header("content-length")); +// Debug_printv("length=%d", _size); + +// // Is this text? +// std::string ct = m_http.get_header("content-type").c_str(); +// Debug_printv("content_type[%s]", ct.c_str()); +// isText = mstr::isText(ct); + +// return true; +// }; + +// uint32_t WebDAVIStream::read(uint8_t* buf, uint32_t size) { +// auto bytesRead= m_http.read( buf, size ); +// _position+=bytesRead; +// return bytesRead; +// }; + +// bool WebDAVIStream::isOpen() { +// return _is_open; +// }; diff --git a/lib/meatloaf/network/webdav.h b/lib/meatloaf/network/webdav.h new file mode 100755 index 0000000..060e454 --- /dev/null +++ b/lib/meatloaf/network/webdav.h @@ -0,0 +1,171 @@ +// WEBDAV:// - WebDAV + +#ifndef MEATLOAF_SCHEME_WEBDAV +#define MEATLOAF_SCHEME_WEBDAV + +#include "http.h" + +#include "meat_io.h" +#include "../../include/global_defines.h" + + +/******************************************************** + * File implementations + ********************************************************/ + + +class WebDAVFile: public MFile { + +public: + WebDAVFile(std::string path): MFile(path) {}; + + bool isDirectory() override; + MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED stream + time_t getLastWrite() override ; + time_t getCreationTime() override ; + bool rewindDirectory() override { return false; }; + MFile* getNextFileInDir() override { return nullptr; }; + bool mkDir() override { return false; }; + bool exists() override ; + uint32_t size() override ; + bool remove() override { return false; }; + bool rename(std::string dest) { return false; }; + MStream* getDecodedStream(std::shared_ptr src); + //void addHeader(const String& name, const String& value, bool first = false, bool replace = true); +}; + + +/******************************************************** + * Streams + ********************************************************/ + +class WebDAVIOStream: public MStream { +public: + WebDAVIOStream(std::string& path) { + url = path; + } + ~WebDAVIOStream() { + close(); + } + + void close() override; + bool open() override; + + // MStream methods + uint32_t position() override; + uint32_t available() override; + uint32_t read(uint8_t* buf, uint32_t size) override; + uint32_t write(const uint8_t *buf, uint32_t size) override; + bool isOpen(); + +protected: + std::string url; + bool _is_open; + +// WiFiClient m_file; + MeatHttpClient m_http; +}; + + +class WebDAVIStream: public MStream { + +public: + WebDAVIStream(std::string path) { + //m_http.set_header("user-agent", USER_AGENT); + //m_http.setUserAgent(USER_AGENT); + //m_http.setTimeout(10000); + //m_http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + //m_http.setRedirectLimit(10); + url = path; + } + ~WebDAVIStream() { + close(); + } + + // MStream methods + uint32_t size() override; + uint32_t available() override; + uint32_t position() override; + + virtual bool seek(uint32_t pos); + + void close() override; + bool open() override; + + // MStream methods + uint32_t read(uint8_t* buf, uint32_t size) override; + bool isOpen(); + +protected: + std::string url; + bool _is_open; + bool isFriendlySkipper = false; + +// WiFiClient m_file; + MeatHttpClient m_http; +}; + + +class WebDAVOStream: public MStream { + +public: + // MStream methods + WebDAVOStream(std::string path) { + //m_http.set_header("user-agent", USER_AGENT); + //m_http.setTimeout(10000); + //m_http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + //m_http.setRedirectLimit(10); + //m_http.setReuse(true); + + url = path; + } + ~WebDAVOStream() { + close(); + } + + // MStream methods + size_t size() override; + size_t available() override; + size_t position() override; + + virtual bool seek(size_t pos); + + void close() override; + bool open() override; + + + // MStream methods + size_t write(const uint8_t *buf, size_t size) override; + bool isOpen(); + +protected: + std::string url; + bool _is_open; + bool isFriendlySkipper = false; + +// WiFiClient m_file; + //WiFiClient m_client; + MeatHttpClient m_http; +}; + + +/******************************************************** + * FS + ********************************************************/ + +class WebDAVFileSystem: public MFileSystem +{ + MFile* getFile(std::string path) override { + return new WebDAVFile(path); + } + + bool handles(std::string name) { + std::string pattern = "webdav:"; + return mstr::equals(name, pattern, false); + } +public: + WebDAVFileSystem(): MFileSystem("webdav") {}; +}; + + +#endif // MEATLOAF_SCHEME_WEBDAV \ No newline at end of file diff --git a/lib/meatloaf/network/ws.h b/lib/meatloaf/network/ws.h new file mode 100644 index 0000000..7fcbc84 --- /dev/null +++ b/lib/meatloaf/network/ws.h @@ -0,0 +1,176 @@ +// WS:// - WebSockets +// https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API +// https://en.wikipedia.org/wiki/WebSocket +// + +#ifndef MEATLOAF_SCHEME_WS +#define MEATLOAF_SCHEME_WS + +#include "meat_io.h" +#include "../../include/global_defines.h" + +#include "string_utils.h" + +/******************************************************** + * Streams + ********************************************************/ + +class CSIOStream: public MStream { +public: + CSIOStream(MFile* file, bool isServer) : m_isServer(isServer) { + // drop ws:// from url and it's OK! + address = websockets::WSInterfaceString(mstr::drop(file->url, 5).c_str()); + if(!file->port.empty()) + port = std::stoi(file->port); + } + + ~CSIOStream() { + close(); + } + + void close() override { + client.close(); + } + + bool open() override { + if(m_isServer) { + server.listen(port); + client = server.accept(); + prepareClientCallbacks(); + _is_open = server.available(); + } + else { + prepareClientCallbacks(); + _is_open = client.connect(address); + // waring - client->poll() required to keep it working! + } + + return _is_open; + }; + + // MStream methods + uint32_t position() override { return 0; }; + uint32_t available() override { return INT_MAX; }; + bool isOpen() { return _is_open; }; + bool seek(uint32_t pos) { return false; }; + uint32_t size() { return INT_MAX; }; + + uint32_t read(uint8_t* buf, uint32_t size) override { + //auto msg = client.readBlocking(); // we don't want to block. We'll store a message from callback in msg + + if(!_is_open) + return 0; + + if(!lastMsg.isEmpty()) { + auto maxLen = (size < lastMsg.length()) ? size : lastMsg.length(); + strncpy((char *)buf, lastMsg.c_str(), maxLen); + Debug_printv("Sending last msg and polling!"); + lastMsg = ""; + client.poll(); + return maxLen; + } + else { + Debug_printv("Polling!"); + client.poll(); + return 0; + } + } + size_t write(const uint8_t *buf, size_t size) override { + if(!_is_open) + return 0; + + websockets::WSInterfaceString message((char *)buf); + client.send(message); + return message.length(); + } + +protected: + websockets::WSInterfaceString address; // format: www.myserver.com:8080 + uint16_t port = 80; + bool _is_open; + bool m_isServer; + websockets::WebsocketsClient client; + websockets::WebsocketsServer server; + websockets::WSInterfaceString lastMsg; + + void prepareClientCallbacks() { + client.onMessage([this](websockets::WebsocketsMessage msg){ + // + // I think this get called only if you do client.poll() + // + Debug_printv("Got message: %s", msg.data().c_str()); + + if(msg.isText()) { + //(*msgPtr) = (std::string)msg.data(); + this->lastMsg = msg.data(); + } + }); + + client.onEvent([this](websockets::WebsocketsEvent event, String data) { + if(event == websockets::WebsocketsEvent::ConnectionOpened) { + Debug_printv("Socket Connnection Opened"); + this->_is_open = true; + } else if(event == websockets::WebsocketsEvent::ConnectionClosed) { + Debug_printv("Socket Connnection Closed"); + this->_is_open = false; + } else if(event == websockets::WebsocketsEvent::GotPing) { + Debug_printv("Got a Ping!"); + } else if(event == websockets::WebsocketsEvent::GotPong) { + Debug_printv("Got a Pong!"); + } + }); + }; +}; + +/******************************************************** + * File implementations + ********************************************************/ + +class WSFile: public MFile { + +public: + WSFile(std::string path): MFile(path) {}; + + bool isDirectory() override { return false; } + MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override { + // input stream = SERVER socket + return new CSIOStream(this, true); + }; + time_t getLastWrite() override { return 0; }; + time_t getCreationTime() override { return 0; }; + bool rewindDirectory() override { return false; }; + MFile* getNextFileInDir() override { return nullptr; }; + bool mkDir() override { return false; }; + bool exists() override { return false; }; + size_t size() override { return 0; }; + bool remove() override { return false; }; + bool rename(std::string dest) { return false; }; + MStream* getDecodedStream(std::shared_ptr src) { + return nullptr; + }; + +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class WSFileSystem: public MFileSystem +{ + MFile* getFile(std::string path) override { + return new WSFile(path); + } + + bool handles(std::string name) { + std::string pattern = "ws:"; + return mstr::equals(name, pattern, false); + } +public: + WSFileSystem(): MFileSystem("ws") {}; +}; + + + +#endif /* MEATLOAF_SCHEME_WS */ diff --git a/lib/meatloaf/network/wss.h b/lib/meatloaf/network/wss.h new file mode 100644 index 0000000..57d30f6 --- /dev/null +++ b/lib/meatloaf/network/wss.h @@ -0,0 +1,4 @@ +// WSS:// - WebSockets Secure +// https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API +// https://en.wikipedia.org/wiki/WebSocket +// diff --git a/lib/meatloaf/scanners/virus.h b/lib/meatloaf/scanners/virus.h new file mode 100644 index 0000000..bf4b4ca --- /dev/null +++ b/lib/meatloaf/scanners/virus.h @@ -0,0 +1,5 @@ +// +// https://codebase64.org/doku.php?id=base:viruslist +// https://www.c64-wiki.com/wiki/BHP-Virus +// http://hitmen.c02.at/files/docs/c64/C64_Virus_List.txt +// \ No newline at end of file diff --git a/lib/meatloaf/service/cs.cpp b/lib/meatloaf/service/cs.cpp new file mode 100644 index 0000000..69070a8 --- /dev/null +++ b/lib/meatloaf/service/cs.cpp @@ -0,0 +1,496 @@ +// #include "cs.h" + +// #include "make_unique.h" + +// /******************************************************** +// * Client impls +// ********************************************************/ +// // fajna sciezka do sprawdzenia: +// // utilities/disk tools/cie.d64 + +// CServerSessionMgr CServerFileSystem::session; + +// bool CServerSessionMgr::establishSession() { +// if(!buf.is_open()) { +// currentDir = "cs:/"; +// buf.open(); +// } + +// return buf.is_open(); +// } + +// std::string CServerSessionMgr::readLn() { +// char buffer[80]; +// // telnet line ends with 10; +// getline(buffer, 80, 10); +// Debug_printv("Inside readln got: '%s'", buffer); +// return std::string((char *)buffer); +// } + +// bool CServerSessionMgr::sendCommand(std::string command) { +// std::string c = mstr::toPETSCII2(command); +// // 13 (CR) sends the command +// if(establishSession()) { +// Serial.printf("CServer: send command: %s\r\n", c.c_str()); +// (*this) << (c+'\r'); +// (*this).flush(); +// return true; +// } +// else +// return false; +// } + +// bool CServerSessionMgr::isOK() { +// // auto a = readLn(); + +// auto reply = readLn(); +// // for(int i = 0 ; ipath.c_str()); + +// if(buf.is_open()) { +// // if we are still connected we can smart change dir by just going up or down +// // but for time being, we stick to traversing from root +// if(!sendCommand("cf /")) +// return false; +// } +// else { +// // if we aren't, change dir to root (alos connects the session); +// if(!sendCommand("cf /")) +// return false; +// } + +// Debug_printv("here"); +// if(isOK()) { +// Debug_printv("path[%s]", path->path.c_str()); +// if(path->path.compare("/") == 0) { +// currentDir = path->url; +// return true; +// } + +// std::vector chopped = mstr::split(path->path, '/'); + +// //MFile::parsePath(&chopped, path->path); - nope this doessn't work and crases in the loop! + +// Debug_printv("Before loop"); +// //Debug_printv("Chopped size:%d\r\n", chopped.size()); +// fnSystem.delay(500); + +// for(size_t i = 1; i < chopped.size(); i++) { +// //Debug_printv("Before chopped deref"); + +// auto part = chopped[i]; + +// //Debug_printv("traverse path part: [%s]\r\n", part.c_str()); +// if(mstr::endsWith(part, ".d64", false)) +// { +// // THEN we have to mount the image INSERT image_name +// sendCommand("insert "+part); + +// // disk image is the end, so return +// if(isOK()) { +// currentDir = path->url; +// return true; +// } +// else { +// // or: ?500 - DISK NOT FOUND. +// return false; +// } +// } +// else +// { +// // CF xxx - to browse into subsequent dirs +// sendCommand("cf "+part); +// if(!isOK()) { +// // or: ?500 - CANNOT CHANGE TO dupa +// return false; +// } +// } +// } + +// currentDir = path->url; +// return true; +// } +// else +// return false; // shouldn't really happen, right? +// } + +// /******************************************************** +// * I Stream impls +// ********************************************************/ + + +// void CServerIStream::close() { +// _is_open = false; +// }; + +// bool CServerIStream::open() { +// auto file = std::make_unique(url); +// _is_open = false; + +// if(file->isDirectory()) +// return false; // or do we want to stream whole d64 image? :D + +// if(CServerFileSystem::session.traversePath(file.get())) { +// // should we allow loading of * in any directory? +// // then we can LOAD and get available count from first 2 bytes in (LH) endian +// // name here MUST BE UPPER CASE +// // trim spaces from right of name too +// mstr::rtrimA0(file->name); +// //mstr::toPETSCII2(file->name); +// CServerFileSystem::session.sendCommand("load "+file->name); +// // read first 2 bytes with size, low first, but may also reply with: ?500 - ERROR +// uint8_t buffer[2] = { 0, 0 }; +// read(buffer, 2); +// // hmmm... should we check if they're "?5" for error?! +// if(buffer[0]=='?' && buffer[1]=='5') { +// Debug_printv("CServer: open file failed"); +// CServerFileSystem::session.readLn(); +// _is_open = false; +// } +// else { +// _size = buffer[0] + buffer[1]*256; // put len here +// // if everything was ok +// Serial.printf("CServer: file open, size: %d\r\n", _size); +// _is_open = true; +// } +// } + +// return _is_open; +// }; + +// // MStream methods + +// uint32_t CServerIStream::write(const uint8_t *buf, uint32_t size) { +// return -1; +// } + +// uint32_t CServerIStream::read(uint8_t* buf, uint32_t size) { +// //Debug_printv("CServerIStream::read"); +// auto bytesRead = CServerFileSystem::session.receive(buf, size); +// _position+=bytesRead; +// //ledTogg(true); +// return bytesRead; +// }; + +// bool CServerIStream::isOpen() { +// return _is_open; +// } + + +// /******************************************************** +// * File impls +// ********************************************************/ + + +// // MFile* CServerFile::cd(std::string newDir) { +// // // maah - don't really know how to handle this! + +// // // Drop the : if it is included +// // if(newDir[0]==':') { +// // Debug_printv("[:]"); +// // newDir = mstr::drop(newDir,1); +// // } + +// // Debug_printv("cd in CServerFile! New dir [%s]\r\n", newDir.c_str()); +// // if(newDir[0]=='/' && newDir[1]=='/') { +// // if(newDir.size()==2) { +// // // user entered: CD:// or CD// +// // // means: change to the root of roots +// // return MFSOwner::File("/"); // chedked, works ad flash root! +// // } +// // else { +// // // user entered: CD://DIR or CD//DIR +// // // means: change to a dir in root of roots +// // Debug_printv("[//]"); +// // return root(mstr::drop(newDir,2)); +// // } +// // } +// // else if(newDir[0]=='/') { +// // if(newDir.size()==1) { +// // // user entered: CD:/ or CD/ +// // // means: change to container root +// // // *** might require a fix for flash fs! +// // return MFSOwner::File(streamFile->path); +// // } +// // else { +// // Debug_printv("[/]"); +// // // user entered: CD:/DIR or CD/DIR +// // // means: change to a dir in container root +// // return MFSOwner::File("cs:/"+mstr::drop(newDir,1)); +// // } +// // } +// // else if(newDir[0]=='_') { +// // if(newDir.size()==1) { +// // // user entered: CD:_ or CD_ +// // // means: go up one directory +// // return parent(); +// // } +// // else { +// // Debug_printv("[_]"); +// // // user entered: CD:_DIR or CD_DIR +// // // means: go to a directory in the same directory as this one +// // return parent(mstr::drop(newDir,1)); +// // } +// // } +// // if(newDir[0]=='.' && newDir[1]=='.') { +// // if(newDir.size()==2) { +// // // user entered: CD:.. or CD.. +// // // means: go up one directory +// // return parent(); +// // } +// // else { +// // Debug_printv("[..]"); +// // // user entered: CD:..DIR or CD..DIR +// // // meaning: Go back one directory +// // return localParent(mstr::drop(newDir,2)); +// // } +// // } + +// // // ain't that redundant? +// // // if(newDir[0]=='.' && newDir[1]=='/') { +// // // Debug_printv("[./]"); +// // // // Reference to current directory +// // // return localParent(mstr::drop(newDir,2)); +// // // } + +// // if(newDir[0]=='~' /*&& newDir[1]=='/' let's be consistent!*/) { +// // if(newDir.size() == 1) { +// // // user entered: CD:~ or CD~ +// // // meaning: go to the .sys folder +// // return MFSOwner::File("/.sys"); +// // } +// // else { +// // Debug_printv("[~]"); +// // // user entered: CD:~FOLDER or CD~FOLDER +// // // meaning: go to a folder in .sys folder +// // return MFSOwner::File("/.sys/" + mstr::drop(newDir,1)); +// // } +// // } +// // if(newDir.find(':') != std::string::npos) { +// // // I can only guess we're CDing into another url scheme, this means we're changing whole path +// // return MFSOwner::File(newDir); +// // } +// // else { +// // // Add new directory to path +// // if(mstr::endsWith(url,"/")) +// // return MFSOwner::File(url+newDir); +// // else +// // return MFSOwner::File(url+"/"+newDir); +// // } +// // }; + + +// bool CServerFile::isDirectory() { +// // if penultimate part is .d64 - it is a file +// // otherwise - false + +// //Debug_printv("trying to chop [%s]", path.c_str()); + +// auto chopped = mstr::split(path,'/'); + +// if(path.empty()) { +// // rood dir is a dir +// return true; +// } +// if(chopped.size() == 1) { +// // we might be in an image in the root +// return mstr::endsWith((chopped[0]), ".d64", false); +// } +// if(chopped.size()>1) { +// auto second = chopped.end()-2; + +// //auto x = (*second); +// // Debug_printv("isDirectory second from right: [%s]", (*second).c_str()); +// if ( mstr::endsWith((*second), ".d64", false)) +// return false; +// else +// return true; +// } +// return false; +// }; + +// MStream* CServerFile::getSourceStream(std::ios_base::openmode mode) { +// MStream* istream = new CServerIStream(url); +// istream->open(); +// return istream; +// }; + +// bool CServerFile::rewindDirectory() { +// dirIsOpen = false; + +// if(!isDirectory()) +// return false; + + +// Debug_printv("pre traverse path"); + +// if(!CServerFileSystem::session.traversePath(this)) return false; + +// Debug_printv("post traverse path"); + +// if(mstr::endsWith(path, ".d64", false)) +// { +// dirIsImage = true; +// // to list image contents we have to run +// Debug_printv("cserver: this is a d64 img, sending $ command!"); +// CServerFileSystem::session.sendCommand("$"); +// auto line = CServerFileSystem::session.readLn(); // mounted image name +// if(CServerFileSystem::session.is_open()) { +// dirIsOpen = true; +// media_image = line.substr(5); +// line = CServerFileSystem::session.readLn(); // dir header +// media_header = line.substr(2, line.find_last_of("\"")); +// media_id = line.substr(line.find_last_of("\"")+2); +// return true; +// } +// else +// return false; +// } +// else +// { +// dirIsImage = false; +// // to list directory contents we use +// //Debug_printv("cserver: this is a directory!"); +// CServerFileSystem::session.sendCommand("disks"); +// auto line = CServerFileSystem::session.readLn(); // dir header +// if(CServerFileSystem::session.is_open()) { +// media_header = line.substr(2, line.find_last_of("]")-1); +// media_id = "C=SVR"; +// dirIsOpen = true; + +// return true; +// } +// else +// return false; +// } +// }; + +// MFile* CServerFile::getNextFileInDir() { + +// Debug_printv("pre rewind"); + +// if(!dirIsOpen) +// rewindDirectory(); + +// Debug_printv("pre dir is open"); + +// if(!dirIsOpen) +// return nullptr; + +// std::string name; +// size_t size; +// std::string new_url = url; + +// if(url.size()>4) // If we are not at root then add additional "/" +// new_url += "/"; + +// Debug_printv("pre dir is image"); + +// if(dirIsImage) { +// auto line = CServerFileSystem::session.readLn(); +// Debug_printv("next file in dir got %s", line.c_str()); +// // 'ot line:'0 ␒"CIE�������������" 00�2A� +// // 'ot line:'2 "CIE+SERIAL " PRG 2049 +// // 'ot line:'1 "CIE-SYS31801 " PRG 2049 +// // 'ot line:'1 "CIE-SYS31801S " PRG 2049 +// // 'ot line:'1 "CIE-SYS52281 " PRG 2049 +// // 'ot line:'1 "CIE-SYS52281S " PRG 2049 +// // 'ot line:'658 BLOCKS FREE. + +// if(line.find('\x04')!=std::string::npos) { +// Debug_printv("No more!"); +// dirIsOpen = false; +// return nullptr; +// } +// if(line.find("BLOCKS FREE.")!=std::string::npos) { +// media_blocks_free = atoi(line.substr(0, line.find_first_of(" ")).c_str()); +// dirIsOpen = false; +// return nullptr; +// } +// else { +// name = line.substr(5,15); +// size = atoi(line.substr(0, line.find_first_of(" ")).c_str()); +// mstr::rtrim(name); +// Debug_printv("xx: %s -- %s %d", line.c_str(), name.c_str(), size); +// //return new CServerFile(path() +"/"+ name); +// new_url += name; +// return new CServerFile(new_url, size); +// } +// } else { +// auto line = CServerFileSystem::session.readLn(); +// // Got line:'' +// // Got line:'' +// // 'ot line:'FAST-TESTER DELUXE EXCESS.D64 +// // 'ot line:'EMPTY.D64 +// // 'ot line:'CMD UTILITIES D1.D64 +// // 'ot line:'CBMCMD22.D64 +// // 'ot line:'NAV96.D64 +// // 'ot line:'NAV92.D64 +// // 'ot line:'SINGLE DISKCOPY 64 (1983)(KEVIN PICKELL).D64 +// // 'ot line:'LYNX (19XX)(-).D64 +// // 'ot line:'GEOS DISK EDITOR (1990)(GREG BADROS).D64 +// // 'ot line:'FLOPPY REPAIR KIT (1984)(ORCHID SOFTWARE LABORATOR +// // 'ot line:'1541 DEMO DISK (19XX)(-).D64 + +// // 32 62 91 68 73 83 75 32 84 79 79 76 83 93 13 No more! = > [DISK TOOLS] + +// if(line.find('\x04')!=std::string::npos) { +// Debug_printv("No more!"); +// dirIsOpen = false; +// return nullptr; +// } +// else { + +// if((*line.begin())=='[') { +// name = line.substr(1,line.length()-3); +// size = 0; +// } +// else { +// name = line.substr(0, line.length()-1); +// size = 683; +// } + +// // Debug_printv("\nurl[%s] name[%s] size[%d]\r\n", url.c_str(), name.c_str(), size); +// if(name.size() > 0) +// { +// new_url += name; +// return new CServerFile(new_url, size); +// } +// else +// return nullptr; +// } +// } +// }; + +// bool CServerFile::exists() { +// return true; +// } ; + +// uint32_t CServerFile::size() { +// return m_size; +// }; + +// bool CServerFile::mkDir() { +// // but it does support creating dirs = MD FOLDER +// return false; +// }; + +// bool CServerFile::remove() { +// // but it does support remove = SCRATCH FILENAME +// return false; +// }; + + \ No newline at end of file diff --git a/lib/meatloaf/service/cs.h b/lib/meatloaf/service/cs.h new file mode 100644 index 0000000..4fd2431 --- /dev/null +++ b/lib/meatloaf/service/cs.h @@ -0,0 +1,309 @@ +// // CS:/ - a scheme for handling CommodoreServer Internet Protocol +// // see: https://www.commodoreserver.com/BlogEntryView.asp?EID=9D133160E7C344A398EC1F45AEF4BF32 +// // + +// #ifndef MEATLOAF_SCHEME_CS +// #define MEATLOAF_SCHEME_CS + +// #include "meat_io.h" +// #include "network/tcp.h" + +// #include "fnSystem.h" +// #include "fnTcpClient.h" + +// #include "utils.h" +// #include "string_utils.h" + +// #include +// #include + +// /******************************************************** +// * Telnet buffer +// ********************************************************/ + +// class csstreambuf : public std::streambuf { +// char* gbuf; +// char* pbuf; + +// protected: +// MeatSocket m_wifi; + +// public: +// csstreambuf() {} + +// ~csstreambuf() { +// close(); +// } + +// bool is_open() { +// return (m_wifi.isOpen()); +// } + +// bool open() { +// if(m_wifi.isOpen()) +// return true; + +// int rc = m_wifi.open("commodoreserver.com", 1541); +// Serial.printf("csstreambuf: connect to cserver returned: %d\r\n", rc); + +// if(rc == 1) { +// if(gbuf == nullptr) +// gbuf = new char[512]; +// if(pbuf == nullptr) +// pbuf = new char[512]; +// } + +// setp(pbuf, pbuf+512); + +// return rc == 1; +// } + +// void close() { +// Serial.printf("csstreambuf: closing\r\n"); +// if(m_wifi.isOpen()) { +// m_wifi.close(); +// } +// if(gbuf != nullptr) +// delete[] gbuf; +// if(pbuf != nullptr) +// delete[] pbuf; +// } + +// int underflow() override { +// Debug_printv("In underflow"); +// if (!m_wifi.isOpen()) { +// Debug_printv("In connection closed"); +// close(); +// return std::char_traits::eof(); +// } +// else if (this->gptr() == this->egptr()) { +// int readCount = 0; +// int attempts = 5; +// int wait = 500; + +// readCount = m_wifi.read((uint8_t*)gbuf, 512); + +// while( readCount <= 0 && (attempts--)>0 && m_wifi.isOpen()) { +// Debug_printv("got rc: %d, retrying", readCount); +// fnSystem.delay(wait); +// wait+=100; +// readCount = m_wifi.read((uint8_t*)gbuf, 512); +// } +// Debug_printv("read success: %d", readCount); +// this->setg(gbuf, gbuf, gbuf + readCount); +// } +// else { +// Debug_printv("else: %d - %d, (%d)", this->gptr(), this->egptr(), this->gbuf); +// } + +// return this->gptr() == this->egptr() +// ? std::char_traits::eof() +// : std::char_traits::to_int_type(*this->gptr()); +// }; + + +// int overflow(int ch = traits_type::eof()) override +// { +// //Debug_printv("in overflow"); + +// if (!m_wifi.isOpen()) { +// close(); +// return EOF; +// } + +// char* end = pptr(); +// if ( ch != EOF ) { +// *end ++ = ch; +// } + +// uint8_t* pBase = (uint8_t*)pbase(); + +// if ( m_wifi.write( pBase, end - pbase() ) == 0 ) { +// ch = EOF; +// } else if ( ch == EOF ) { +// ch = 0; +// } +// setp(pbuf, pbuf+512); + +// return ch; +// }; + +// int sync() { + +// if (!m_wifi.isOpen()) { +// close(); +// return 0; +// } +// if(pptr() == pbase()) { +// return 0; +// } +// else { +// //Debug_printv("in sync, written %d", pptr()-pbase()); +// uint8_t* buffer = (uint8_t*)pbase(); +// auto result = m_wifi.write(buffer, pptr()-pbase()); +// setp(pbuf, pbuf+512); +// return (result != 0) ? 0 : -1; +// } +// }; + +// friend class CServerSessionMgr; +// }; + +// /******************************************************** +// * Session manager +// ********************************************************/ + +// class CServerSessionMgr : public std::iostream { +// std::string m_user; +// std::string m_pass; +// csstreambuf buf; + +// protected: +// std::string currentDir; + +// bool establishSession(); + +// bool sendCommand(std::string); + +// bool traversePath(MFile* path); + +// bool isOK(); + +// std::string readLn(); + +// public: +// CServerSessionMgr(std::string user = "", std::string pass = "") : std::iostream(&buf), m_user(user), m_pass(pass) +// {}; + +// ~CServerSessionMgr() { +// sendCommand("quit"); +// }; + +// // read/write are used only by MStream +// size_t receive(uint8_t* buffer, size_t size) { +// if(buf.is_open()) +// return buf.m_wifi.read(buffer, size); +// else +// return 0; +// } + +// // read/write are used only by MStream +// size_t send(const uint8_t* buffer, size_t size) { +// if(buf.is_open()) +// return buf.m_wifi.write(buffer, size); +// else +// return 0; +// } + +// bool is_open() { +// return buf.is_open(); +// } + +// friend class CServerFile; +// friend class CServerIStream; +// friend class CServerOStream; +// }; + +// /******************************************************** +// * File implementations +// ********************************************************/ +// class CServerFile: public MFile { + +// public: +// CServerFile(std::string path, size_t size = 0): MFile(path), m_size(size) +// { +// media_blocks_free = 65535; +// media_block_size = 1; // blocks are already calculated +// //parseUrl(path); +// // Debug_printv("path[%s] size[%d]", path.c_str(), size); +// isPETSCII = true; +// }; + +// MStream* getDecodedStream(std::shared_ptr src) { return src.get(); }; +// MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override ; // has to return OPENED stream + +// //MFile* cd(std::string newDir); +// bool isDirectory() override; +// bool rewindDirectory() override; +// MFile* getNextFileInDir() override; +// bool mkDir() override ; + +// bool exists() override; +// bool remove() override; +// bool rename(std::string dest) { return false; }; +// time_t getLastWrite() override { return 0; }; +// time_t getCreationTime() override { return 0; }; +// uint32_t size() override; + +// bool isDir = true; +// bool dirIsOpen = false; + +// private: +// bool dirIsImage = false; +// size_t m_size; +// }; + +// /******************************************************** +// * Streams +// ********************************************************/ + +// // +// class CServerIStream: public MStream { + +// public: +// CServerIStream(std::string path) { +// url = path; +// } +// ~CServerIStream() { +// close(); +// } +// // MStream methods +// bool open() override; +// void close() override; + +// uint32_t available() override; +// uint32_t size() override; +// uint32_t position() override; +// size_t error() override; + +// virtual bool seek(uint32_t pos) { +// return false; +// }; + +// uint32_t read(uint8_t* buf, uint32_t size) override; +// uint32_t write(const uint8_t *buf, uint32_t size) override; +// bool isOpen() override; + +// protected: +// std::string url; +// bool _is_open; +// // uint32_t _size; +// // uint32_t m_bytesAvailable = 0; +// // uint32_t _position = 0; +// }; + + + +// /******************************************************** +// * FS +// ********************************************************/ + +// class CServerFileSystem: public MFileSystem +// { +// bool handles(std::string name) { +// return name == "cs:"; +// } + +// public: +// CServerFileSystem(): MFileSystem("c=server") {}; +// static CServerSessionMgr session; +// MFile* getFile(std::string path) override { +// return new CServerFile(path); +// } + +// }; + + + + +// #endif /* MEATLOAF_SCHEME_CS */ diff --git a/lib/meatloaf/service/gdrive.h b/lib/meatloaf/service/gdrive.h new file mode 100644 index 0000000..09b9131 --- /dev/null +++ b/lib/meatloaf/service/gdrive.h @@ -0,0 +1,6 @@ +// Google Drive +// +// https://developers.google.com/drive/api/reference/rest/v3 +// https://www.googleapis.com/discovery/v1/apis/drive/v3/rest +// + diff --git a/lib/meatloaf/service/ml.h b/lib/meatloaf/service/ml.h new file mode 100644 index 0000000..80bcab5 --- /dev/null +++ b/lib/meatloaf/service/ml.h @@ -0,0 +1,46 @@ +// // ML:// - Meatloaf Server Protocol +// // + + +// #ifndef MEATLOAF_SCHEME_ML +// #define MEATLOAF_SCHEME_ML + +// #include "network/http.h" + +// #include "peoples_url_parser.h" + + +// /******************************************************** +// * FS +// ********************************************************/ + +// class MLFileSystem: public MFileSystem +// { +// MFile* getFile(std::string path) override { +// if ( path.size() == 0 ) +// return nullptr; + +// //Debug_printv("MLFileSystem::getFile(%s)", path.c_str()); +// PeoplesUrlParser *urlParser = PeoplesUrlParser::parseURL( path ); +// std::string code = mstr::toUTF8(urlParser->name); + +// //Debug_printv("url[%s]", urlParser.name.c_str()); +// std::string ml_url = "https://api.meatloaf.cc/?" + code; +// //Debug_printv("ml_url[%s]", ml_url.c_str()); + +// //Debug_printv("url[%s]", ml_url.c_str()); + +// return new HttpFile(ml_url); +// } + +// bool handles(std::string name) { +// std::string pattern = "ml:"; +// return mstr::startsWith(name, pattern.c_str(), false); +// } + +// public: +// MLFileSystem(): MFileSystem("meatloaf") {}; +// }; + + +// #endif // MEATLOAF_SCHEME_ML \ No newline at end of file diff --git a/lib/meatloaf/tape/htap.h b/lib/meatloaf/tape/htap.h new file mode 100644 index 0000000..ed5fe01 --- /dev/null +++ b/lib/meatloaf/tape/htap.h @@ -0,0 +1,5 @@ +// .HTAP - High resolution tape image format +// https://www.manosoft.it/?page_id=4678 +// https://www.dropbox.com/s/3lsk1cqk6r4kocr/HTAP%20File%20Format%20SPECIFICATIONS%20V2.0.pdf?dl=0 +// + diff --git a/lib/meatloaf/tape/t64.cpp b/lib/meatloaf/tape/t64.cpp new file mode 100644 index 0000000..2662000 --- /dev/null +++ b/lib/meatloaf/tape/t64.cpp @@ -0,0 +1,218 @@ +#include "t64.h" + +#include "endianness.h" + +/******************************************************** + * Streams + ********************************************************/ + +bool T64IStream::seekEntry( std::string filename ) +{ + size_t index = 1; + mstr::replaceAll(filename, "\\", "/"); + bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); + + // Read Directory Entries + if ( filename.size() ) + { + while ( seekEntry( index ) ) + { + std::string entryFilename = entry.filename; + mstr::rtrimA0(entryFilename); + entryFilename = mstr::toUTF8(entryFilename); + + Debug_printv("filename[%s] entry.filename[%.16s]", filename.c_str(), entryFilename.c_str()); + + // Read Entry From Stream + if (filename == "*") // Match first PRG + { + filename = entryFilename; + return true; + } + else if ( filename == entryFilename ) // Match exact + { + return true; + } + else if ( wildcard ) + { + if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match + { + // Move stream pointer to start track/sector + return true; + } + } + + index++; + } + } + + entry.filename[0] = '\0'; + + return false; +} + +bool T64IStream::seekEntry( uint16_t index ) +{ + // Calculate Sector offset & Entry offset + index--; + uint16_t entryOffset = 0x40 + (index * sizeof(entry)); + + //Debug_printv("----------"); + //Debug_printv("index[%d] sectorOffset[%d] entryOffset[%d] entry_index[%d]", index, sectorOffset, entryOffset, entry_index); + + containerStream->seek(entryOffset); + containerStream->read((uint8_t *)&entry, sizeof(entry)); + + //Debug_printv("r[%d] file_type[%02X] file_name[%.16s]", r, entry.file_type, entry.filename); + + //if ( next_track == 0 && next_sector == 0xFF ) + entry_index = index + 1; + if ( entry.file_type == 0x00 ) + return false; + else + return true; +} + + +uint16_t T64IStream::readFile(uint8_t* buf, uint16_t size) { + uint16_t bytesRead = 0; + + if (_position < 2) + { + //Debug_printv("position[%d]", _position); + + // Send Starting Address + buf[0] = entry.start_address[_position]; + bytesRead++; + } + else + { + bytesRead += containerStream->read(buf, size); + } + + _position += bytesRead; + + return bytesRead; +} + +bool T64IStream::seekPath(std::string path) { + // Implement this to skip a queue of file streams to start of file by name + // this will cause the next read to return bytes of 'path' + seekCalled = true; + + entry_index = 0; + + // call image method to obtain file bytes here, return true on success: + if ( seekEntry(path) ) + { + //auto entry = containerImage->entry; + auto type = decodeType(entry.file_type).c_str(); + size_t start_address = UINT16_FROM_HILOBYTES(entry.start_address[1], entry.start_address[0]); + size_t end_address = UINT16_FROM_HILOBYTES(entry.end_address[1], entry.end_address[0]); + size_t data_offset = UINT32_FROM_LE_UINT32(entry.data_offset); + Debug_printv("filename [%.16s] type[%s] start_address[%zu] end_address[%zu] data_offset[%zu]", entry.filename, type, start_address, end_address, data_offset); + + // Calculate file size + _size = ( end_address - start_address ) + 2; + _position = 0; + + // Set position to beginning of file + containerStream->seek(entry.data_offset); + + Debug_printv("File Size: size[%d] available[%d] position[%d]", _size, available(), _position); + + return true; + } + else + { + Debug_printv( "Not found! [%s]", path.c_str()); + } + + return false; +}; + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* T64File::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new T64IStream(containerIstream); +} + + +bool T64File::isDirectory() { + //Debug_printv("pathInStream[%s]", pathInStream.c_str()); + if ( pathInStream == "" ) + return true; + else + return false; +}; + +bool T64File::rewindDirectory() { + dirIsOpen = true; + Debug_printv("streamFile->url[%s]", streamFile->url.c_str()); + auto image = ImageBroker::obtain(streamFile->url); + if ( image == nullptr ) + Debug_printv("image pointer is null"); + + image->resetEntryCounter(); + + // Read Header + image->seekHeader(); + + // Set Media Info Fields + media_header = mstr::format("%.16s", image->header.disk_name); + media_id = " T64 "; + media_blocks_free = 0; + media_block_size = image->block_size; + media_image = name; + //mstr::toUTF8(media_image); + + Debug_printv("media_header[%s] media_id[%s] media_blocks_free[%d] media_block_size[%d] media_image[%s]", media_header.c_str(), media_id.c_str(), media_blocks_free, media_block_size, media_image.c_str()); + + return true; +} + +MFile* T64File::getNextFileInDir() { + + if(!dirIsOpen) + rewindDirectory(); + + // Get entry pointed to by containerStream + auto image = ImageBroker::obtain(streamFile->url); + + if ( image->seekNextImageEntry() ) + { + std::string fileName = mstr::format("%.16s", image->entry.filename); + mstr::replaceAll(fileName, "/", "\\"); + mstr::rtrimA0(fileName); + //Debug_printv( "entry[%s]", (streamFile->url + "/" + fileName).c_str() ); + auto file = MFSOwner::File(streamFile->url + "/" + fileName); + file->extension = image->decodeType(image->entry.file_type); + return file; + } + else + { + //Debug_printv( "END OF DIRECTORY"); + dirIsOpen = false; + return nullptr; + } +} + + +uint32_t T64File::size() { + // Debug_printv("[%s]", streamFile->url.c_str()); + // use T64 to get size of the file in image + auto entry = ImageBroker::obtain(streamFile->url)->entry; + + //Debug_printv("end0[%d] end1[%d] start0[%d] start1[%d]", entry.end_address[0], entry.end_address[1], entry.start_address[0], entry.start_address[1]); + size_t end_address = UINT16_FROM_HILOBYTES(entry.end_address[1], entry.end_address[0]); + size_t start_address = UINT16_FROM_HILOBYTES(entry.start_address[1], entry.start_address[0]); + + size_t bytes = ( end_address - start_address ); + //Debug_printv("start_address[%d] end_address[%d] bytes[%d]", start_address, end_address, bytes); + + return bytes; +} diff --git a/lib/meatloaf/tape/t64.h b/lib/meatloaf/tape/t64.h new file mode 100644 index 0000000..4e9be4f --- /dev/null +++ b/lib/meatloaf/tape/t64.h @@ -0,0 +1,120 @@ +// .T64 - The T64 tape image format +// https://vice-emu.sourceforge.io/vice_17.html#SEC331 +// https://ist.uwaterloo.ca/~schepers/formats/T64.TXT +// + + +#ifndef MEATLOAF_MEDIA_T64 +#define MEATLOAF_MEDIA_T64 + +#include "meat_io.h" +#include "cbm_media.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class T64IStream : public CBMImageStream { + // override everything that requires overriding here + +public: + T64IStream(std::shared_ptr is) : CBMImageStream(is) { }; + +protected: + struct Header { + char disk_name[24]; + }; + + struct Entry { + uint8_t entry_type; + uint8_t file_type; + uint8_t start_address[2]; + uint8_t end_address[2]; + uint16_t free_1; + uint32_t data_offset; + uint32_t free_2; + char filename[16]; + }; + + void seekHeader() override { + containerStream->seek(0x28); + containerStream->read((uint8_t*)&header, 24); + } + + bool seekNextImageEntry() override { + return seekEntry(entry_index + 1); + } + + bool seekEntry( std::string filename ) override; + bool seekEntry( uint16_t index ) override; + + uint16_t readFile(uint8_t* buf, uint16_t size) override; + bool seekPath(std::string path) override; + + Header header; + Entry entry; + +private: + friend class T64File; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class T64File: public MFile { +public: + + T64File(std::string path, bool is_dir = true): MFile(path) { + isDir = is_dir; + + media_image = name; + isPETSCII = true; + }; + + ~T64File() { + // don't close the stream here! It will be used by shared ptr D64Util to keep reading image params + } + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; + + bool isDirectory() override; + bool rewindDirectory() override; + MFile* getNextFileInDir() override; + bool mkDir() override { return false; }; + + bool exists() override { return true; }; + bool remove() override { return false; }; + bool rename(std::string dest) override { return false; }; + time_t getLastWrite() override { return 0; }; + time_t getCreationTime() override { return 0; }; + uint32_t size() override; + + bool isDir = true; + bool dirIsOpen = false; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class T64FileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new T64File(path); + } + + bool handles(std::string fileName) override { + return byExtension(".t64", fileName); + } + + T64FileSystem(): MFileSystem("t64") {}; +}; + + +#endif /* MEATLOAF_MEDIA_T64 */ diff --git a/lib/meatloaf/tape/tap.cpp b/lib/meatloaf/tape/tap.cpp new file mode 100644 index 0000000..9e38976 --- /dev/null +++ b/lib/meatloaf/tape/tap.cpp @@ -0,0 +1,189 @@ +#include "tap.h" + +#include "endianness.h" + +/******************************************************** + * Streams + ********************************************************/ + +bool TAPIStream::seekEntry( std::string filename ) +{ + uint8_t index = 1; + mstr::rtrimA0(filename); + mstr::replaceAll(filename, "\\", "/"); + + // Read Directory Entries + if ( filename.size() ) + { + while ( seekEntry( index ) ) + { + std::string entryFilename = entry.filename; + mstr::rtrimA0(entryFilename); + Debug_printv("filename[%s] entry.filename[%.16s]", filename.c_str(), entryFilename.c_str()); + + // Read Entry From Stream + if (filename == "*") + { + filename = entryFilename; + } + + if ( mstr::compare(filename, entryFilename) ) + { + // Move stream pointer to start track/sector + return true; + } + index++; + } + } + + entry.filename[0] = '\0'; + + return false; +} + +bool TAPIStream::seekEntry( uint16_t index ) +{ + // Calculate Sector offset & Entry offset + index--; + uint16_t entryOffset = 0x40 + (index * sizeof(entry)); + + //Debug_printv("----------"); + //Debug_printv("index[%d] sectorOffset[%d] entryOffset[%d] entry_index[%d]", index, sectorOffset, entryOffset, entry_index); + + containerStream->seek(entryOffset); + containerStream->read((uint8_t *)&entry, sizeof(entry)); + + //Debug_printv("r[%d] file_type[%02X] file_name[%.16s]", r, entry.file_type, entry.filename); + + //if ( next_track == 0 && next_sector == 0xFF ) + entry_index = index + 1; + if ( entry.file_type == 0x00 ) + return false; + else + return true; +} + + +uint16_t TAPIStream::readFile(uint8_t* buf, uint16_t size) { + uint16_t bytesRead = 0; + + bytesRead += containerStream->read(buf, size); + _position += bytesRead; + + return bytesRead; +} + +bool TAPIStream::seekPath(std::string path) { + // Implement this to skip a queue of file streams to start of file by name + // this will cause the next read to return bytes of 'path' + seekCalled = true; + + entry_index = 0; + + // call image method to obtain file bytes here, return true on success: + if ( seekEntry(path) ) + { + //auto entry = containerImage->entry; + auto type = decodeType(entry.file_type).c_str(); + size_t start_address = UINT16_FROM_LE_UINT16(entry.start_address); + size_t end_address = UINT16_FROM_LE_UINT16(entry.end_address); + size_t data_offset = UINT32_FROM_LE_UINT32(entry.data_offset); + Debug_printv("filename [%.16s] type[%s] start_address[%zu] end_address[%zu] data_offset[%zu]", entry.filename, type, start_address, end_address, data_offset); + + // Calculate file size + _size = ( end_address - start_address ); + + // Set position to beginning of file + containerStream->seek(entry.data_offset); + + Debug_printv("File Size: size[%d] available[%d]", _size, available()); + + return true; + } + else + { + Debug_printv( "Not found! [%s]", path.c_str()); + } + + return false; +}; + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* TAPFile::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new TAPIStream(containerIstream); +} + + +bool TAPFile::isDirectory() { + //Debug_printv("pathInStream[%s]", pathInStream.c_str()); + if ( pathInStream == "" ) + return true; + else + return false; +}; + +bool TAPFile::rewindDirectory() { + dirIsOpen = true; + Debug_printv("streamFile->url[%s]", streamFile->url.c_str()); + auto image = ImageBroker::obtain(streamFile->url); + if ( image == nullptr ) + Debug_printv("image pointer is null"); + + image->resetEntryCounter(); + + // Read Header + image->seekHeader(); + + // Set Media Info Fields + media_header = mstr::format("%.24", image->header.disk_name); + media_id = "tap"; + media_blocks_free = 0; + media_block_size = image->block_size; + media_image = name; + //mstr::toUTF8(media_image); + + Debug_printv("media_header[%s] media_id[%s] media_blocks_free[%d] media_block_size[%d] media_image[%s]", media_header.c_str(), media_id.c_str(), media_blocks_free, media_block_size, media_image.c_str()); + + return true; +} + +MFile* TAPFile::getNextFileInDir() { + + if(!dirIsOpen) + rewindDirectory(); + + // Get entry pointed to by containerStream + auto image = ImageBroker::obtain(streamFile->url); + + if ( image->seekNextImageEntry() ) + { + std::string fileName = mstr::format("%.16s", image->entry.filename); + mstr::replaceAll(fileName, "/", "\\"); + //Debug_printv( "entry[%s]", (streamFile->url + "/" + fileName).c_str() ); + auto file = MFSOwner::File(streamFile->url + "/" + fileName); + file->extension = image->decodeType(image->entry.file_type); + return file; + } + else + { + //Debug_printv( "END OF DIRECTORY"); + dirIsOpen = false; + return nullptr; + } +} + + +uint32_t TAPFile::size() { + // Debug_printv("[%s]", streamFile->url.c_str()); + // use TAP to get size of the file in image + auto image = ImageBroker::obtain(streamFile->url); + + size_t bytes = UINT16_FROM_LE_UINT16(image->entry.end_address) - UINT16_FROM_LE_UINT16(image->entry.start_address); + + return bytes; +} diff --git a/lib/meatloaf/tape/tap.h b/lib/meatloaf/tape/tap.h new file mode 100644 index 0000000..aa8c3d0 --- /dev/null +++ b/lib/meatloaf/tape/tap.h @@ -0,0 +1,127 @@ +// .TAP - The raw tape image format +// +// https://en.wikipedia.org/wiki/Commodore_Datasette +// https://vice-emu.sourceforge.io/vice_17.html#SEC330 +// https://ist.uwaterloo.ca/~schepers/formats/TAP.TXT +// https://sourceforge.net/p/tapclean/gitcode/ci/master/tree/ +// https://github.com/binaryfields/zinc64/blob/master/doc/Analyzing%20C64%20tape%20loaders.txt +// https://web.archive.org/web/20170117094643/http://tapes.c64.no/ +// https://web.archive.org/web/20191021114418/http://www.subchristsoftware.com:80/finaltap.htm +// + + +#ifndef MEATLOAF_MEDIA_TAP +#define MEATLOAF_MEDIA_TAP + +#include "meat_io.h" +#include "cbm_media.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class TAPIStream : public CBMImageStream { + // override everything that requires overriding here + +public: + TAPIStream(std::shared_ptr is) : CBMImageStream(is) { }; + +protected: + struct Header { + char disk_name[24]; + }; + + struct Entry { + uint8_t entry_type; + uint8_t file_type; + uint16_t start_address; + uint16_t end_address; + uint16_t free_1; + uint32_t data_offset; + uint32_t free_2; + char filename[16]; + }; + + void seekHeader() override { + Debug_printv("here"); + containerStream->seek(0x28); + containerStream->read((uint8_t*)&header, sizeof(header)); + } + + bool seekNextImageEntry() override { + return seekEntry(entry_index + 1); + } + + bool seekEntry( std::string filename ) override; + bool seekEntry( uint16_t index ) override; + + uint16_t readFile(uint8_t* buf, uint16_t size) override; + bool seekPath(std::string path) override; + + Header header; + Entry entry; + +private: + friend class TAPFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class TAPFile: public MFile { +public: + + TAPFile(std::string path, bool is_dir = true): MFile(path) { + isDir = is_dir; + + media_image = name; + //mstr::toUTF8(media_image); + }; + + ~TAPFile() { + // don't close the stream here! It will be used by shared ptr D64Util to keep reading image params + } + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; + + bool isDirectory() override; + bool rewindDirectory() override; + MFile* getNextFileInDir() override; + bool mkDir() override { return false; }; + + bool exists() override { return true; }; + bool remove() override { return false; }; + bool rename(std::string dest) override { return false; }; + time_t getLastWrite() override { return 0; }; + time_t getCreationTime() override { return 0; }; + uint32_t size() override; + + bool isDir = true; + bool dirIsOpen = false; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class TAPFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new TAPFile(path); + } + + bool handles(std::string fileName) override { + return byExtension(".tap", fileName); + } + + TAPFileSystem(): MFileSystem("tap") {}; +}; + + +#endif /* MEATLOAF_MEDIA_TAP */ diff --git a/lib/meatloaf/tape/tcrt.cpp b/lib/meatloaf/tape/tcrt.cpp new file mode 100644 index 0000000..2d4d5e6 --- /dev/null +++ b/lib/meatloaf/tape/tcrt.cpp @@ -0,0 +1,231 @@ +#include "tcrt.h" + + +/******************************************************** + * Streams + ********************************************************/ + +bool TCRTIStream::seekEntry( std::string filename ) +{ + size_t index = 1; + mstr::replaceAll(filename, "\\", "/"); + bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); + + // Read Directory Entries + if ( filename.size() ) + { + while ( seekEntry( index ) ) + { + std::string entryFilename = entry.filename; + mstr::rtrimA0(entryFilename); + entryFilename = mstr::toUTF8(entryFilename); + + Debug_printv("filename[%s] entry.filename[%.16s]", filename.c_str(), entryFilename.c_str()); + + // Read Entry From Stream + if (filename == "*") // Match first PRG + { + filename = entryFilename; + return true; + } + else if ( filename == entryFilename ) // Match exact + { + return true; + } + else if ( wildcard ) + { + if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match + { + // Move stream pointer to start track/sector + return true; + } + } + + index++; + } + } + + entry.filename[0] = '\0'; + + return false; +} + +bool TCRTIStream::seekEntry( uint16_t index ) +{ + // Calculate Sector offset & Entry offset + index--; + uint16_t entryOffset = 0xE7 + (index * 32); + + //Debug_printv("----------"); + //Debug_printv("index[%d] entryOffset[%d] entry_index[%d]", (index + 1), entryOffset, entry_index); + + containerStream->seek(entryOffset); + containerStream->read((uint8_t *)&entry, sizeof(entry)); + + // uint32_t file_start_address = (0xD8 + (entry.file_start_address[0] << 8 | entry.file_start_address[1])); + // uint32_t file_size = (entry.file_size[0] | (entry.file_size[1] << 8) | (entry.file_size[2] << 16)) + 2; // 2 bytes for load address + // uint32_t file_load_address = entry.file_load_address[0] | entry.file_load_address[1] << 8; + + // Debug_printv("file_name[%.16s] file_type[%02X] data_offset[%X] file_size[%d] load_address[%04X]", entry.filename, entry.file_type, file_start_address, file_size, file_load_address); + + entry_index = index + 1; + if ( entry.file_type == 0xFF ) + return false; + else + return true; +} + +uint16_t TCRTIStream::readFile(uint8_t* buf, uint16_t size) { + uint16_t bytesRead = 0; + + if ( _position < 2) + { + Debug_printv("send load address[%4X]", m_load_address); + + buf[0] = m_load_address[_position]; + bytesRead++; + // if ( size > 1 ) + // { + // buf[0] = m_load_address[0]; + // buf[1] = m_load_address[1]; + // bytesRead += containerStream->read(buf, size); + // } + } + else + { + bytesRead += containerStream->read(buf, size); + } + + _position += bytesRead; + + return bytesRead; +} + +bool TCRTIStream::seekPath(std::string path) { + // Implement this to skip a queue of file streams to start of file by name + // this will cause the next read to return bytes of 'path' + seekCalled = true; + + entry_index = 0; + + // call image method to obtain file bytes here, return true on success: + if ( seekEntry(path) ) + { + //auto entry = containerImage->entry; + auto type = decodeType(mapType(entry.file_type)).c_str(); + Debug_printv("filename [%.16s] type[%s]", entry.filename, type); + + // Calculate file size + _size = (entry.file_size[0] | (entry.file_size[1] << 8) | (entry.file_size[2] << 16)) + 2; // 2 bytes for load address + + // Load Address + m_load_address[0] = entry.file_load_address[0]; + m_load_address[1] = entry.file_load_address[1]; + + // Set position to beginning of file + uint32_t file_start_address = (0xD8 + (entry.file_start_address[0] << 8 | entry.file_start_address[1])); + containerStream->seek(file_start_address); + + Debug_printv("File Size: size[%d] available[%d]", _size, available()); + + return true; + } + else + { + Debug_printv( "Not found! [%s]", path.c_str()); + } + + return false; +}; + +/******************************************************** + * File implementations + ********************************************************/ + +MStream* TCRTFile::getDecodedStream(std::shared_ptr containerIstream) { + Debug_printv("[%s]", url.c_str()); + + return new TCRTIStream(containerIstream); +} + + +bool TCRTFile::isDirectory() { + //Debug_printv("pathInStream[%s]", pathInStream.c_str()); + if ( pathInStream == "" ) + return true; + else + return false; +}; + +bool TCRTFile::rewindDirectory() { + dirIsOpen = true; + Debug_printv("streamFile->url[%s]", streamFile->url.c_str()); + auto image = ImageBroker::obtain(streamFile->url); + if ( image == nullptr ) + Debug_printv("image pointer is null"); + + image->resetEntryCounter(); + + // Read Header + image->seekHeader(); + + // Set Media Info Fields + media_header = mstr::format("%.16s", image->header.disk_name); + media_id = "TCRT"; + media_blocks_free = 0; + media_block_size = image->block_size; + media_image = name; + //mstr::toUTF8(media_image); + + Debug_printv("media_header[%s] media_id[%s] media_blocks_free[%d] media_block_size[%d] media_image[%s]", media_header.c_str(), media_id.c_str(), media_blocks_free, media_block_size, media_image.c_str()); + + return true; +} + +MFile* TCRTFile::getNextFileInDir() { + + if(!dirIsOpen) + rewindDirectory(); + + // Get entry pointed to by containerStream + auto image = ImageBroker::obtain(streamFile->url); + + bool r = false; + do + { + r = image->seekNextImageEntry(); + } while ( r && image->mapType(image->entry.file_type) == 0x00); // Skip hidden files + + if ( r ) + { + std::string fileName = mstr::format("%.16s", image->entry.filename); + mstr::replaceAll(fileName, "/", "\\"); + //Debug_printv( "entry[%s]", (streamFile->url + "/" + fileName).c_str() ); + auto file = MFSOwner::File(streamFile->url + "/" + fileName); + file->extension = image->decodeType(image->mapType(image->entry.file_type)); + return file; + } + else + { + //Debug_printv( "END OF DIRECTORY"); + dirIsOpen = false; + return nullptr; + } +} + + +uint32_t TCRTFile::size() { + //Debug_printv("[%s]", streamFile->url.c_str()); + // use TCRT to get size of the file in image + auto entry = ImageBroker::obtain(streamFile->url)->entry; + + //size_t blocks = (UINT16_FROM_LE_UINT16(image->entry.load_address) + image->entry.file_size)) / image->block_size; + //size_t blocks = 1; + + // 9E 60 00 + // 158 96 0 + + size_t bytes = (entry.file_size[0] | (entry.file_size[1] << 8) | (entry.file_size[2] << 16)) + 2; // 2 bytes for load address + + return bytes; +} diff --git a/lib/meatloaf/tape/tcrt.h b/lib/meatloaf/tape/tcrt.h new file mode 100644 index 0000000..bcee205 --- /dev/null +++ b/lib/meatloaf/tape/tcrt.h @@ -0,0 +1,171 @@ +// .TCRT - Tapecart File System +// https://github.com/ikorb/tapecart +// https://github.com/ikorb/tapecart/blob/master/doc/TCRT%20Format.md +// https://github.com/alexkazik/tapecart-browser/blob/master/doc/Tapecart-FileSystem.md + + +#ifndef MEATLOAF_MEDIA_TCRT +#define MEATLOAF_MEDIA_TCRT + +#include "meat_io.h" +#include "cbm_media.h" + + +/******************************************************** + * Streams + ********************************************************/ + +class TCRTIStream : public CBMImageStream { + // override everything that requires overriding here + +public: + TCRTIStream(std::shared_ptr is) : CBMImageStream(is) {}; + +protected: + struct Header { + char disk_name[16]; + }; + + struct Entry { + char filename[16]; + uint8_t file_type; + uint8_t file_start_address[2]; // from tcrt file system at 0xD8 + uint8_t file_size[3]; + uint8_t file_load_address[2]; + uint16_t bundle_compatibility; + uint16_t bundle_main_start; + uint16_t bundle_main_length; + uint16_t bundle_main_call_address; + }; + + void seekHeader() override { + containerStream->seek(0x18); + containerStream->read((uint8_t*)&header, sizeof(header)); + } + + bool seekNextImageEntry() override { + return seekEntry(entry_index + 1); + } + + bool seekEntry( std::string filename ) override; + bool seekEntry( uint16_t index ) override; + bool seekPath(std::string path) override; + + uint16_t readFile(uint8_t* buf, uint16_t size) override; + + Header header; + Entry entry; + + // Translate TCRT file type to standard CBM file type + uint8_t mapType(uint8_t file_type) + { + // Type + // 0x00 - 0x3f: An program which can be loaded into the C64 and executed. + // The load address from the FS is used (not stored in the file!). The load address + size must fit into the C64. + // 0x00: general program + // 0x01: game + // 0x02: utility + // 0x03: multimedia + // 0x04: demo + // 0x05: image + // 0x06: tune + // 0x38-0x3f: private use area + if ( file_type <= 0x3F ) + return 0x82; // PRG + + // 0x40 - 0x7f: A bundled file (see below) Same types as 0x00-0x3f (just for a bundle and not a prg) + // This mode is designed for games that need to load further files or store data like high scores or save games. + // It also cen be used for subdirectories. + if ( file_type >= 0x40 && file_type <= 0x7F ) + return 0x86; // DIR + + // 0x80 - 0xef: Data files may not displayed by a browser. + // 0x80: general data + // 0x81: text file (lower PETSCII) + // 0x82: koala image + // 0x83: hires image + // 0x84: fli image (multicolor) + if ( file_type == 0x81 ) + return 0x81; // SEQ + else if ( file_type >= 0x80 && file_type <= 0xEF ) + return 0x82; // PRG + + // 0xf0: separator - this name should be displayed just as a separator. size must be 0, all other info data (start, load address) is undefined + if ( file_type == 0xF0 ) + return 0x82; // PRG + + // 0xfe: System file! This file is (part of) the program that launches at power up. + // No other program should ever rename/delete this file or relocate the blocks. + // (The entry within the FS however can be moved around. This may cover the FS itself, but it's not required.) + //if ( file_type == 0xFE ) + // return 0x00; // DEL + + // 0xff: Marker for the first free entry. + + // All types not mentioned above are reserved. + return 0x00; // DEL + } + +private: + friend class TCRTFile; +}; + + +/******************************************************** + * File implementations + ********************************************************/ + +class TCRTFile: public MFile { +public: + + TCRTFile(std::string path, bool is_dir = true): MFile(path) { + isDir = is_dir; + + media_image = name; + isPETSCII = true; + }; + + ~TCRTFile() { + // don't close the stream here! It will be used by shared ptr D64Util to keep reading image params + } + + MStream* getDecodedStream(std::shared_ptr containerIstream) override; + + bool isDirectory() override; + bool rewindDirectory() override; + MFile* getNextFileInDir() override; + bool mkDir() override { return false; }; + + bool exists() override { return true; }; + bool remove() override { return false; }; + bool rename(std::string dest) override { return false; }; + time_t getLastWrite() override { return 0; }; + time_t getCreationTime() override { return 0; }; + uint32_t size() override; + + bool isDir = true; + bool dirIsOpen = false; +}; + + + +/******************************************************** + * FS + ********************************************************/ + +class TCRTFileSystem: public MFileSystem +{ +public: + MFile* getFile(std::string path) override { + return new TCRTFile(path); + } + + bool handles(std::string fileName) override { + return byExtension(".tcrt", fileName); + } + + TCRTFileSystem(): MFileSystem("tcrt") {}; +}; + + +#endif /* MEATLOAF_MEDIA_TCRT */ diff --git a/lib/meatloaf/wrappers/directory_stream.h b/lib/meatloaf/wrappers/directory_stream.h new file mode 100644 index 0000000..c6586da --- /dev/null +++ b/lib/meatloaf/wrappers/directory_stream.h @@ -0,0 +1,110 @@ +#ifndef MEATLOAF_WRAPPER_DIRECTORY_STREAM +#define MEATLOAF_WRAPPER_DIRECTORY_STREAM + +#include +#include + +#include "meat_io.h" + +class idirbuf : public std::filebuf { + const size_t BUFFER_SIZE = 256; + std::shared_ptr container; + char* buffer; + uint8_t phase = 0; // 0 = headers, 1 = files, 2 = footer + +public: + idirbuf() { + buffer = new char[BUFFER_SIZE]; + }; + + ~idirbuf() { + if(buffer != nullptr) + delete[] buffer; + } + + void open(std::shared_ptr c) { + container = c; + container->rewindDirectory(); // in case we're in the middle of reading here! + phase = 0; + } + + // something will be READING from this stream, that's why we need to implement underflow + int underflow() override { + // it is up to us how we will fill this buffer. We can write it single line at + // a time, or (if made big enough) with whole directory contents, it really + // doesn't matter, because when everything from the buffer is read, underflow + // will get called again + // + // I divided it into 3 phases, for header, files and footer, but again - any way is OK + if(phase == 0) { + // OK, so you are in headers. + // First you have to fill this buffer line by line or as whole. Doesn't matter. + // i.e. like this: + size_t written = lineToBuffer(69,"some header line"); + // When you're done, call this: + this->setg(buffer, buffer, buffer + written); + + // of course filling it at once would be easiest, as you just fill it here and set this: + //phase = 1; + // if you fill line by line incremet some count and set phase to 1 when all lines written... + } + else if(phase == 1) { + // So now we're in files list phase, let's try and get a file: + std::unique_ptr entry(container->getNextFileInDir()); + + if (entry != nullptr) { + // There's still a file in this dir, so... Same principle: + // we'll put BASIV V2 into buffer variable using this function: + auto readCount = fileToBasicV2(entry.get()); + // and set required pointers: + this->setg(buffer, buffer, buffer + readCount); + // we're putting just this oone file in our buffer and exiting + // underflow will be called to fill it with next file + } + else + phase = 2; // nope, no more files here. Let's change phase, so we can write footer + // in the same pass. Otherwise we'll get an EOF! + } + + if(phase == 2) { + // ok, so now the footer, again - write it into the buffer and call: + size_t written = lineToBuffer(69,"bytes free"); + this->setg(buffer, buffer, buffer + written); + phase = 3; + // next time underflow is called, we'll just return eof + } + else if(phase == 3) { + // we've past the footer, let's send eof + return std::char_traits::eof(); + } + + return this->gptr() == this->egptr() + ? std::char_traits::eof() + : std::char_traits::to_int_type(*this->gptr()); + }; + + size_t fileToBasicV2(MFile* file, long flags = 0L) { + // convert MFile to ASCII line + // we can use some additional FLAGS, i.e. for various CBM-style long directory format (they contain creation date and other info!) + return lineToBuffer(69,"directory entry line in ASCII"); // return length of BASIC line written to the buffer + } + + size_t lineToBuffer(uint16_t lineNumber, const std::string &lineContents) { + // 1. convert lineContents to PETSCII or whatever is required to send to the C64 + // 2. fill our buffer with next line pointer, line number and converted lineContents + // for(i...) buffer[i]=XXXX... + // 3. return how may bytes you put in the buffer: + return 69; + } +}; + +// void exampleStreamFn(std::shared_ptr container) { +// idirbuf dirbuffer; +// dirbuffer.open(container); +// std::istream dirStream(&dirbuffer); + +// // now you can just read BASIC V2 characters from dirStream +// } + + +#endif /* MEATLOAF_WRAPPER_DIRECTORY_STREAM */ diff --git a/lib/meatloaf/wrappers/iec_buffer.cpp b/lib/meatloaf/wrappers/iec_buffer.cpp new file mode 100644 index 0000000..08a4192 --- /dev/null +++ b/lib/meatloaf/wrappers/iec_buffer.cpp @@ -0,0 +1,156 @@ +// #ifdef BUILD_IEC +// #include "iec_buffer.h" + +// oiecstream iecStream; + +// /******************************************************** +// * oiecbuf +// * +// * A buffer for writing IEC data, handles sending EOI +// ********************************************************/ + + +// /******************************************************** +// * SAVE ops, pipe mode = _S_out, uses get area +// ********************************************************/ +// size_t oiecstream::receiveBytesViaIEC() { +// // we are in a SAVE operation here, so we are pulling bytes from C64 to file, by reading from IEC +// // underflow happened to our get buffer and this function was called, it has to read bytes from IEC +// // put them in gbuff and setg to point to them + +// // TODO: implement +// return 0; +// } + +// /******************************************************** +// * LOAD ops, pipe mode = _S_in, uses put area +// ********************************************************/ +// void oiecstream::flushpbuff() { +// // a seek was called on our pipe, meaning we want to change the location we are reading from +// // since there might be some bytes in the buffer from previous read operations, +// // waiting to be sent, we need to flush them, as they won't be! + +// setp(data, data+IEC_BUFFER_SIZE); // reset the beginning and ending buffer pointers +// } + +// size_t oiecstream::sendBytesViaIEC() { +// size_t written = 0; + +// // we are in a LOAD operation here, so we are pusing bytes from file to C64, by writing to IEC + +// // Serial.printf("buff :"); +// // for(auto i = pbase(); iIEC:"); +// for(auto b = pbase(); b < pptr()-1; b++) { +// //Serial.printf("%c",*b); +// //Serial.printf("%c[%.2X]",*b, *b); +// bool sendSuccess = m_iec->sendByte(*b); +// //bool sendSuccess = true; +// if(sendSuccess && !(IEC.flags bitand ATN_PULLED) ) written++; +// else if(!sendSuccess) { +// // JAIME: what should happen here? should the badbit be set when send returns false? +// setstate(badbit); +// setp(data+written, data+IEC_BUFFER_SIZE); // set pbase to point to next unwritten char +// Debug_printv("IEC acknowledged %d bytes, then failed\n", written); +// return written; +// } +// else { +// // ATN was pulled +// setp(data+written, data+IEC_BUFFER_SIZE); // set pbase to point to next unwritten char +// Debug_printv("IEC acknowledged %d bytes, then ATN was pulled\n", written); +// return written; +// } +// } + + +// // probably more bytes to come, so +// // here we wrote all buffer chars but the last one. +// // we will take this last byte, put it at position 0 and set pptr to 1 +// char lastChar = *(pbase()+written); +// setp(data, data+IEC_BUFFER_SIZE); // reset the beginning and ending buffer pointers +// pbump(1); // and set pptr to 1 to tell there's 1 byte in our buffer +// data[0] = lastChar; // let's put it at position 0 +// Debug_printv("---> LAST [%.2X]\n", data[0]); +// Debug_printv("IEC acknowledged %d bytes\n", written); + +// return written; +// } + +// int oiecstream::overflow(int ch) { +// if (!is_open()) +// { +// return EOF; +// } + +// Debug_printv("overflow for iec called, size=%d", pptr()-pbase()); +// char* end = pptr(); +// if ( ch != EOF ) { +// pbump(1); +// *end ++ = ch; +// } + +// size_t written = sendBytesViaIEC(); + +// if ( written == 0 ) { +// ch = EOF; +// } else if ( ch == EOF ) { +// ch = 0; +// } + +// return ch; +// }; + +// int oiecstream::sync() { +// if(pptr()-pbase() <= 1) { +// Debug_printv("sync for iec called - nothing more to write"); +// return 0; +// } +// else { +// Debug_printv("sync for iec called - buffer contains %d bytes", pptr()-pbase()); +// auto result = sendBytesViaIEC(); +// return (result != 0) ? 0 : -1; +// } +// }; + + +// /******************************************************** +// * oiecstream +// * +// * Standard C++ stream for writing to IEC +// ********************************************************/ + +// void oiecstream::putUtf8(U8Char* codePoint) { +// //Serial.printf("%c",codePoint->toPetscii()); +// //Debug_printv("oiecstream calling put"); +// auto c = codePoint->toPetscii(); +// put(codePoint->toPetscii()); +// } + +// // void oiecstream::writeLn(std::string line) { +// // // line is utf-8, convert to petscii + +// // std::string converted; +// // std::stringstream ss(line); + +// // while(!ss.eof()) { +// // U8Char codePoint(&ss); +// // converted+=codePoint.toPetscii(); +// // } + +// // Debug_printv("UTF8 converted to PETSCII:%s",converted.c_str()); + +// // (*this) << converted; +// // } +// #endif /* BUILD_IEC */ \ No newline at end of file diff --git a/lib/meatloaf/wrappers/iec_buffer.h b/lib/meatloaf/wrappers/iec_buffer.h new file mode 100644 index 0000000..43f1618 --- /dev/null +++ b/lib/meatloaf/wrappers/iec_buffer.h @@ -0,0 +1,98 @@ +// #ifdef BUILD_IEC +// #ifndef MEATLOAF_WRAPPER_IEC_BUFFER +// #define MEATLOAF_WRAPPER_IEC_BUFFER + +// #include "../../../include/debug.h" + +// #include "bus.h" + +// #include +// #include +// #include +// #include +// #include +// #include "U8Char.h" + +// #define IEC_BUFFER_SIZE 255 // 1024 + +// /******************************************************** +// * oiecstream +// * +// * Standard C++ stream for writing to IEC +// * +// * This is of course a bit counter-intuitive: +// * +// * Writing to iecstream == LOAD on C64 (pipe mode: _S_in), we're doing file -> IEC -> C64 +// * Reading from iestream == SAVE on C64 (pipe mode: _S_out), we're doing C64 -> IEC -> file +// ********************************************************/ + +// class oiecstream : private std::filebuf, public std::ostream { +// char* data = nullptr; +// systemBus* m_iec = nullptr; +// bool _is_open = false; + +// size_t sendBytesViaIEC(); +// size_t receiveBytesViaIEC(); + +// public: +// oiecstream(const oiecstream &copied) : std::ios(0), std::filebuf(), std::ostream( this ) { +// Debug_printv("oiecstream COPY constructor"); +// } + +// oiecstream() : std::ostream( this ) { +// Debug_printv("oiecstream constructor"); + +// data = new char[IEC_BUFFER_SIZE+1]; +// setp(data, data+IEC_BUFFER_SIZE); +// }; + +// ~oiecstream() { +// Debug_printv("oiecstream destructor"); + +// close(); + +// if(data != nullptr) +// delete[] data; +// } + + +// virtual void open(systemBus* iec) { +// m_iec = iec; +// setp(data, data+IEC_BUFFER_SIZE); +// if(iec != nullptr) +// { +// _is_open = true; +// clear(); +// } +// } + +// virtual void close() { +// sync(); + +// if(pptr()-pbase() == 1) { +// char last = data[0]; +// Debug_printv("closing, sending EOI with [%.2X] %c", last, last); +// m_iec->sendByte(last, true); +// setp(data, data+IEC_BUFFER_SIZE); +// } + +// _is_open = false; +// } + +// bool is_open() const { +// return _is_open; +// } + +// int overflow(int ch = std::filebuf::traits_type::eof()) override; + +// int sync() override; + +// void putUtf8(U8Char* codePoint); + +// void flushpbuff(); +// }; + +// extern oiecstream iecStream; + +// #endif /* MEATLOAF_WRAPPER_IEC_BUFFER */ +// #endif /* BUILD_IEC */ \ No newline at end of file diff --git a/lib/utils/U8Char.cpp b/lib/utils/U8Char.cpp new file mode 100644 index 0000000..ca9dbfd --- /dev/null +++ b/lib/utils/U8Char.cpp @@ -0,0 +1,171 @@ +#include "U8Char.h" +#include "punycode.h" + +// from https://style64.org/petscii/ + +// PETSCII table in UTF8, non-mappable characters mapped to Private Use Area E000-F8FF +const char16_t U8Char::utf8map[] = { +// ---0, ---1, ---2, ---3, ---4, ---5, ---6, ---7, ---8, ---9, --10, --11, --12, --13, --14, --15 + 0xE000, 0xE001, 0xE002, 3, 0xE003, 0xE004, 0xE005, 0xE006, 0xE007, 0xE008, 0xE009, 0xE00A, 0xE00B, 10, 0xE00C, 0xE00D, + 0xE00E, 0xE00F, 0xE010, 0xE011, 0x8, 0xE012, 0xE013, 0xE014, 0xE015, 0xE016, 0xE017, 0xE018, 0xE019, 0xE01A, 0xE01B, 0xE01C, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, // punct + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, // numbers + + 0x40, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // a-z + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x5b, 0xa3, 0x5d, 0x2191, 0x2190, + + 0x2500, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // A-Z + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, + 0x253c, 0xE012, 0x2502, 0xE013, 0xE014, + + 0xE015, 0xE016, 0xE017, 0xE018, 0xE019, 0xE01A, 0xE01B, 0xE01C, 0xE01D, 0xE01E, 0xE01F, 0xE020, 0xE021, 0x2028, 0xE022, 0xE023, // control codes + 0xE024, 0xE025, 0xE026, 0xE027, 0xE028, 0xE029, 0xE02A, 0xE02B, 0xE02C, 0xE02D, 0xE02E, 0xE02F, 0xE030, 0xE031, 0xE032, 0xE033, + + 0xa0, 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x2592, 0xE034, 0xE035, 0xE036, 0xE037, 0x251c, 0x2597, 0x2514, 0x2510, 0x2582, // tables etc. + 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0xE038, 0xE039, 0xE03A, 0x2583, 0x2713, 0x2596, 0x259d, 0x2518, 0x2598, 0x259a, + 0x2500, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // A-Z + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x253c, 0xE03B, 0x2502, 0xE03C, 0xE03D, + 0xa0, 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x2592, 0xE03F, 0xE040, 0xE041, 0xE042, 0x251c, 0x2597, 0x2514, 0x2510, 0x2582, // tables etc. + 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0xE043, 0xE044, 0xE045, 0x2583, 0x2713, 0x2596, 0x259d, 0x2518, 0x2598, 0xE046 + +}; + +void U8Char::fromUtf8Stream(std::istream* reader) { + uint8_t byte = reader->get(); + if(byte<=0x7f) { + ch = byte; + } + else if((byte & 0b11100000) == 0b11000000) { + uint16_t hi = ((uint16_t)(byte & 0b1111)) << 6; + uint16_t lo = (reader->get() & 0b111111); + ch = hi | lo; + } + else if((byte & 0b11110000) == 0b11100000) { + uint16_t hi = ((uint16_t)(byte & 0b111)) << 12; + uint16_t mi = ((uint16_t)(reader->get() & 0b111111)) << 6; + uint16_t lo = reader->get() & 0b111111; + ch = hi | mi | lo; + } + else { + ch = 0; + } +}; + +size_t U8Char::fromCharArray(char* reader) { + uint8_t byte = reader[0]; + if(byte<=0x7f) { + ch = byte; + return 1; + } + else if((byte & 0b11100000) == 0b11000000) { + uint16_t hi = ((uint16_t)(byte & 0b1111)) << 6; + uint16_t lo = (reader[1] & 0b111111); + ch = hi | lo; + return 2; + } + else if((byte & 0b11110000) == 0b11100000) { + uint16_t hi = ((uint16_t)(byte & 0b111)) << 12; + uint16_t mi = ((uint16_t)(reader[1] & 0b111111)) << 6; + uint16_t lo = reader[2] & 0b111111; + ch = hi | mi | lo; + return 3; + } + else { + ch = 0; + return 1; + } +}; + +std::string U8Char::toUtf8() { + if(ch==0) { + return std::string(1, missing); + } + else if(ch>=0x01 && ch<=0x7f) { + // For code points in the range 0x0000 to 0x007F (1-byte sequences): + // Directly represent the code point in binary. + // Format: 0xxxxxxx + return std::string(1, char(ch)); + } + else if(ch>=0x80 && ch<=0x7ff) { + // First byte: 110xxxxx, where the x's are the first 5 bits of the code point. + char upper = (uint8_t)((ch>>6) & 0b11111) | 0b11000000; + // Second byte: 10xxxxxx, where the x's are the next 6 bits of the code point. + auto lower = (uint8_t)(ch & 0b111111) | 0b10000000; + char arr[] = { (char)upper, (char)lower, '\0'}; + return std::string(arr); + } + else { + // First byte: 1110xxxx, where the x's are the first 4 bits of the code point. + auto hi = (uint8_t)((ch>>12) & 0b00001111) | 0b11100000; + // Second byte: 10xxxxxx, where the x's are the next 6 bits. + auto mid = (uint8_t)((ch>>6) & 0b00111111) | 0b10000000; + // Third byte: 10xxxxxx, where the x's are the last 6 bits. + auto lower = (uint8_t)(ch & 0b00111111) | 0b10000000; + char arr[] = { (char)hi, (char)mid, (char)lower, '\0'}; + return std::string(arr); + } +} + +uint8_t U8Char::toPetscii() { + for(int i = 0; i<256; i++) { + if(utf8map[i]==ch) + return i; + } + return missing; +} + +// for punycode we need utf8 converted to uint32_t +// workflows: +// char* ascii_punycode -> uint32_t* -> char* utf8 +// char* utf8 -> uint32_t* -> char* ascii_punycode + +// convert utf8 encoded string to array of uint32_t, return length of output_unicode32 +size_t U8Char::toUnicode32(std::string& input_utf8, uint32_t* output_unicode32, size_t max_output_length) { + size_t input_length = input_utf8.length(); + size_t output_length = 0; + size_t i = 0; + char* asChar = (char *)input_utf8.c_str(); + + while(i +#include + +/******************************************************** + * U8Char + * + * A minimal wide char implementation that can handle UTF8 + * and convert it to PETSCII + ********************************************************/ + +class U8Char { + static const char16_t utf8map[]; + const char missing = '?'; + void fromUtf8Stream(std::istream* reader); + +public: + char16_t ch; + U8Char(const uint16_t codepoint): ch(codepoint) {}; + U8Char(std::istream* reader) { + fromUtf8Stream(reader); + } + U8Char(const char petscii) { + ch = utf8map[(uint8_t)petscii]; + } + + size_t fromCharArray(char* reader); + + std::string toUtf8(); + uint8_t toPetscii(); + size_t toUnicode32(std::string& input_utf8, uint32_t* output_unicode32, size_t max_output_length); + std::string fromUnicode32(uint32_t* input_unicode32, size_t input_length); + static std::string toPunycode(std::string utf8String); + static std::string fromPunycode(std::string punycodeString); +}; + +#endif /* MEATLOAF_UTILS_U8CHAR */ diff --git a/lib/utils/endianness.h b/lib/utils/endianness.h new file mode 100644 index 0000000..6e55a36 --- /dev/null +++ b/lib/utils/endianness.h @@ -0,0 +1,15 @@ + +// Retruns a uint16 value given two bytes in high-low order +#define UINT16_FROM_HILOBYTES(high, low) ((uint16_t)high << 8 | low) + +// Returns a uint16 value from the little-endian version +#define UINT16_FROM_LE_UINT16(_ui16) \ + (_ui16 << 8 | _ui16 >> 8) +// Returns a uint32 value from the little-endian version +#define UINT32_FROM_LE_UINT32(_ui32) \ + ((_ui32 >> 24 & 0x000000FF) | (_ui32 >> 8 & 0x0000FF00) | (_ui32 << 8 & 0x00FF0000) | (_ui32 << 24 & 0xFF000000)) + +// Returns the high byte (MSB) of a uint16 value +#define HIBYTE_FROM_UINT16(value) ((uint8_t)((value >> 8) & 0xFF)) +// Returns the low byte (LSB) of a uint16 value +#define LOBYTE_FROM_UINT16(value) ((uint8_t)(value & 0xFF)) diff --git a/lib/utils/peoples_url_parser.cpp b/lib/utils/peoples_url_parser.cpp new file mode 100644 index 0000000..e6f426d --- /dev/null +++ b/lib/utils/peoples_url_parser.cpp @@ -0,0 +1,281 @@ +// This code uses code from the Meatloaf Project: +// Meatloaf - A Commodore 64/128 multi-device emulator +// https://github.com/idolpx/meatloaf +// Copyright(C) 2020 James Johnston +// +// Meatloaf is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Meatloaf is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Meatloaf. If not, see . + + +#include "peoples_url_parser.h" + +#include +#include +#include + +#include "../../include/debug.h" + +#include "string_utils.h" + +void PeoplesUrlParser::processHostPort(std::string hostPort) { + auto byColon = mstr::split(hostPort,':',2); + host = byColon[0]; + if(byColon.size()>1) { + port = byColon[1]; + } +} + +void PeoplesUrlParser::processAuthorityPath(std::string authorityPath) { + // /path + // authority:80/path + // authority:100 + // authority + auto bySlash = mstr::split(authorityPath,'/',2); + + processHostPort(bySlash[0]); + if(bySlash.size()>1) { + // hasPath + path = bySlash[1]; + } + +} + +void PeoplesUrlParser::processUserPass(std::string userPass) { + // user:pass + auto byColon = mstr::split(userPass,':',2); + if(byColon.size()==1) { + user = byColon[0]; + } + else { + user = byColon[0]; + password = byColon[1]; + } +} + +void PeoplesUrlParser::processAuthority(std::string pastTheColon) { + // //user:password@/path + // //user:password@host:80/path + // // host:100 + // // host:30/path + auto byAtSign = mstr::split(pastTheColon,'@', 2); + + if(byAtSign.size()==1) { + // just address, port, path + processAuthorityPath(mstr::drop(byAtSign[0],2)); + } + else { + // user:password + processUserPass(mstr::drop(byAtSign[0],2)); + // address, port, path + processAuthorityPath(byAtSign[1]); + } +} + +void PeoplesUrlParser::cleanPath() { + if(path.size() == 0) + return; + + while(mstr::endsWith(path,"/")) { + path=mstr::dropLast(path, 1); + } + mstr::replaceAll(path, "//", "/"); +} + +void PeoplesUrlParser::processPath() { + if(path.size() == 0) + return; + + auto pathParts = mstr::split(path, '/'); + auto queryParts = mstr::split(*(--pathParts.end()), '?'); + auto fragmentParts = mstr::split(*(--queryParts.end()), '#'); + + // path + if(pathParts.size() > 1) + name = *(--pathParts.end()); + else + name = path; + + // filename + if(queryParts.size() > 1) + name = queryParts.front(); + + auto nameParts = mstr::split(name, '.'); + + // base name + if(nameParts.size() > 1) + base_name = nameParts.front(); + + // extension + if(nameParts.size() > 1) + extension = *(--nameParts.end()); + + // query + if(queryParts.size() > 1) + query = *(fragmentParts.begin()); + + // fragment + if(fragmentParts.size() > 1) + fragment = *(--fragmentParts.end()); +} + + +std::string PeoplesUrlParser::pathToFile(void) +{ + if (name.size() > 0) + return path.substr(0, path.size() - name.size() - 1); + else + return path; +} + +std::string PeoplesUrlParser::root(void) +{ + // set root URL + std::string root; + if ( scheme.size() ) + root = scheme + ":"; + + if ( host.size() ) + root += "//"; + + if ( user.size() ) + { + root += user; + if ( password.size() ) + root += ':' + password; + root += '@'; + } + + root += host; + + if ( port.size() ) + root += ':' + port; + + //Debug_printv("root[%s]", root.c_str()); + return root; +} + +std::string PeoplesUrlParser::base(void) +{ + // set base URL + //Debug_printv("base[%s]", (root() + "/" + path).c_str()); + if ( !mstr::startsWith(path, "/") ) + path = "/" + path; + + cleanPath(); + + return root() + pathToFile() ; +} + + + +uint16_t PeoplesUrlParser::getPort() { + return std::stoi(port); +} + + +PeoplesUrlParser* PeoplesUrlParser::parseURL(const std::string &u) { + PeoplesUrlParser *url = new PeoplesUrlParser; + url->resetURL(u); + return url; +} + +void PeoplesUrlParser::resetURL(const std::string u) { + + if ( u.empty() ) + return; + + //Debug_printv("u[%s]", u.c_str()); + + url = u; + + //Debug_printv("Before [%s]", url.c_str()); + + auto byColon = mstr::split(url, ':', 2); + + scheme = ""; + path = ""; + user = ""; + password = ""; + host = ""; + port = ""; + + if(byColon.size()==1) { + // no scheme, good old local path + path = byColon[0]; + } + else + { + scheme = byColon[0]; + + auto pastTheColon = byColon[1]; // don't visualise! + + if(pastTheColon[0]=='/' && pastTheColon[1]=='/') { + // //user:pass@/path + // //user:pass@authority:80/path + // //authority:100 + // //authority:30/path + + processAuthority(pastTheColon); + } + else { + // we have just a plain old path + // /path + // user@server + // etc. + path = pastTheColon; + } + } + + // Clean things up before exiting + cleanPath(); + processPath(); + rebuildURL(); + + //dump(); + + return; +} + +std::string PeoplesUrlParser::rebuildURL(void) +{ + // set full URL + if ( !mstr::startsWith(path, "/") ) + path = "/" + path; + + cleanPath(); + + url = root() + path; + //Debug_printv("url[%s]", url.c_str()); + // url += name; + // Debug_printv("url[%s]", url.c_str()); + // if ( query.size() ) + // url += '?' + query; + // if ( fragment.size() ) + // url += '#' + fragment; + + return url; +} + + +// void dump() { +// printf("scheme: %s\r\n", scheme.c_str()); +// printf("user pass: %s -- %s\r\n", user.c_str(), pass.c_str()); +// printf("host port: %s -- %s\r\n", host.c_str(), port.c_str()); +// printf("path: %s\r\n", path.c_str()); +// printf("name: %s\r\n", name.c_str()); +// printf("extension: %s\r\n", extension.c_str()); +// printf("root: %s\r\n", root().c_str()); +// printf("base: %s\r\n", base().c_str()); +// printf("pathToFile: %s\r\n", pathToFile().c_str()); +// } + diff --git a/lib/utils/peoples_url_parser.h b/lib/utils/peoples_url_parser.h new file mode 100644 index 0000000..e8f942e --- /dev/null +++ b/lib/utils/peoples_url_parser.h @@ -0,0 +1,80 @@ +// This code uses code from the Meatloaf Project: +// Meatloaf - A Commodore 64/128 multi-device emulator +// https://github.com/idolpx/meatloaf +// Copyright(C) 2020 James Johnston +// +// Meatloaf is free software : you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Meatloaf is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Meatloaf. If not, see . + +#ifndef MEATLOAF_PUP_H +#define MEATLOAF_PUP_H + +#include + +class PeoplesUrlParser +{ +private: + void processHostPort(std::string hostPort); + void processAuthorityPath(std::string authorityPath); + void processUserPass(std::string userPass); + void processAuthority(std::string pastTheColon); + void cleanPath(); + void processPath(); + +protected: + PeoplesUrlParser() {}; + +public: + ~PeoplesUrlParser() {}; + + std::string url; + std::string scheme; + std::string user; + std::string password; + std::string host; + std::string port; + std::string path; + std::string name; + std::string base_name; + std::string extension; + std::string query; + std::string fragment; + + std::string pathToFile(void); + std::string root(void); + std::string base(void); + + uint16_t getPort(); + + static PeoplesUrlParser* parseURL(const std::string &u); + void resetURL(const std::string u); + std::string rebuildURL(void); + + + // void dump() { + // printf("scheme: %s\r\n", scheme.c_str()); + // printf("user pass: %s -- %s\r\n", user.c_str(), password.c_str()); + // printf("host port: %s -- %s\r\n", host.c_str(), port.c_str()); + // printf("path: %s\r\n", path.c_str()); + // printf("name: %s\r\n", name.c_str()); + // printf("extension: %s\r\n", extension.c_str()); + // printf("query: %s\r\n", query.c_str()); + // printf("fragment: %s\r\n", fragment.c_str()); + // printf("root: %s\r\n", root().c_str()); + // printf("base: %s\r\n", base().c_str()); + // printf("pathToFile: %s\r\n", pathToFile().c_str()); + // } + +}; + +#endif // MEATLOAF_PUP_H \ No newline at end of file diff --git a/lib/utils/punycode.cpp b/lib/utils/punycode.cpp new file mode 100644 index 0000000..dd686ca --- /dev/null +++ b/lib/utils/punycode.cpp @@ -0,0 +1,316 @@ +/** + * Copyright (C) 2011 by Ben Noordhuis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "punycode.h" + +#include +#include +#include +#include + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +/* punycode parameters, see http://tools.ietf.org/html/rfc3492#section-5 */ +#define BASE 36 +#define TMIN 1 +#define TMAX 26 +#define SKEW 38 +#define DAMP 700 +#define INITIAL_N 128 +#define INITIAL_BIAS 72 + +static uint32_t adapt_bias (uint32_t delta, unsigned n_points, int is_first) +{ + uint32_t k; + + delta /= is_first ? DAMP : 2; + delta += delta / n_points; + + /* while delta > 455: delta /= 35 */ + for (k = 0; delta > ((BASE - TMIN) * TMAX) / 2; k += BASE) + { + delta /= (BASE - TMIN); + } + + return k + (((BASE - TMIN + 1) * delta) / (delta + SKEW)); +} + +static char encode_digit (int c) +{ + assert (c >= 0 && c <= BASE - TMIN); + if (c > 25) + { + return c + 22; /* '0'..'9' */ + } + else + { + return c + 'a'; /* 'a'..'z' */ + } +} + +/* Encode as a generalized variable-length integer. Returns number of bytes written. */ +static size_t encode_var_int (const size_t bias, const size_t delta, char *const dst, size_t dstlen) +{ + size_t i, k, q, t; + + i = 0; + k = BASE; + q = delta; + + while (i < dstlen) + { + if (k <= bias) + { + t = TMIN; + } + else + if (k >= bias + TMAX) + { + t = TMAX; + } + else + { + t = k - bias; + } + + if (q < t) + { + break; + } + + dst[i++] = encode_digit (t + (q - t) % (BASE - t)); + + q = (q - t) / (BASE - t); + k += BASE; + } + + if (i < dstlen) + { + dst[i++] = encode_digit (q); + } + + return i; +} + +static size_t decode_digit (uint32_t v) +{ + if (isdigit (v)) + { + return 22 + (v - '0'); + } + if (islower (v)) + { + return v - 'a'; + } + if (isupper (v)) + { + return v - 'A'; + } + return SIZE_MAX; +} + +size_t punycode_encode (const uint32_t *const src, const size_t srclen, char *const dst, size_t *const dstlen) +{ + size_t b, h; + size_t delta, bias; + size_t m, n; + size_t si, di; + + for (si = 0, di = 0; si < srclen && di < *dstlen; si++) + { + if (src[si] < 128) + { + dst[di++] = src[si]; + } + } + + b = h = di; + + /* Write out delimiter if any basic code points were processed. */ + if (di > 0 && di < *dstlen) + { + dst[di++] = '-'; + } + + n = INITIAL_N; + bias = INITIAL_BIAS; + delta = 0; + + for (; h < srclen && di < *dstlen; n++, delta++) + { + /* Find next smallest non-basic code point. */ + for (m = SIZE_MAX, si = 0; si < srclen; si++) + { + if (src[si] >= n && src[si] < m) + { + m = src[si]; + } + } + + if ((m - n) > (SIZE_MAX - delta) / (h + 1)) + { + /* OVERFLOW */ + assert (0 && "OVERFLOW"); + goto fail; + } + + delta += (m - n) * (h + 1); + n = m; + + for (si = 0; si < srclen; si++) + { + if (src[si] < n) + { + if (++delta == 0) + { + /* OVERFLOW */ + assert (0 && "OVERFLOW"); + goto fail; + } + } + else + if (src[si] == n) + { + di += encode_var_int (bias, delta, &dst[di], *dstlen - di); + bias = adapt_bias (delta, h + 1, h == b); + delta = 0; + h++; + } + } + } + +fail: + /* Tell the caller how many bytes were written to the output buffer. */ + *dstlen = di; + + /* Return how many Unicode code points were converted. */ + return si; +} + +size_t punycode_decode (const char *const src, const size_t srclen, uint32_t *const dst, size_t *const dstlen) +{ + const char *p; + size_t b, n, t; + size_t i, k, w; + size_t si, di; + size_t digit; + size_t org_i; + size_t bias; + + /* Ensure that the input contains only ASCII characters. */ + for (si = 0; si < srclen; si++) + { + if (src[si] & 0x80) + { + *dstlen = 0; + return 0; + } + } + + /* Reverse-search for delimiter in input. */ + for (p = src + srclen - 1; p > src && *p != '-'; p--); + b = p - src; + + /* Copy basic code points to output. */ + di = min (b, *dstlen); + + for (i = 0; i < di; i++) + { + dst[i] = src[i]; + } + + i = 0; + n = INITIAL_N; + bias = INITIAL_BIAS; + + for (si = b + (b > 0); si < srclen && di < *dstlen; di++) + { + org_i = i; + + for (w = 1, k = BASE; di < *dstlen; k += BASE) + { + digit = decode_digit (src[si++]); + + if (digit == SIZE_MAX) + { + goto fail; + } + + if (digit > (SIZE_MAX - i) / w) + { + /* OVERFLOW */ + assert (0 && "OVERFLOW"); + goto fail; + } + + i += digit * w; + + if (k <= bias) + { + t = TMIN; + } + else + if (k >= bias + TMAX) + { + t = TMAX; + } + else + { + t = k - bias; + } + + if (digit < t) + { + break; + } + + if (w > SIZE_MAX / (BASE - t)) + { + /* OVERFLOW */ + assert (0 && "OVERFLOW"); + goto fail; + } + + w *= BASE - t; + } + + bias = adapt_bias (i - org_i, di + 1, org_i == 0); + + if (i / (di + 1) > SIZE_MAX - n) + { + /* OVERFLOW */ + assert (0 && "OVERFLOW"); + goto fail; + } + + n += i / (di + 1); + i %= (di + 1); + + memmove (dst + i + 1, dst + i, (di - i) * sizeof (uint32_t)); + dst[i++] = n; + } + +fail: + /* Tell the caller how many bytes were written to the output buffer. */ + *dstlen = di; + + return si; +} \ No newline at end of file diff --git a/lib/utils/punycode.h b/lib/utils/punycode.h new file mode 100644 index 0000000..6f8eafc --- /dev/null +++ b/lib/utils/punycode.h @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 by Ben Noordhuis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef PUNYCODE_H_ +#define PUNYCODE_H_ + +#include +#include + +/** + * Convert Unicode to Punycode. Returns the number of Unicode characters that were converted. + */ +size_t punycode_encode(const uint32_t *src, size_t srclen, char *dst, size_t *dstlen); + +/** + * Convert Punycode to Unicode. Returns the number of bytes that were converted. + */ +size_t punycode_decode(const char *src, size_t srclen, uint32_t *dst, size_t *dstlen); + +#endif /* punycode.h */ \ No newline at end of file diff --git a/lib/utils/string_utils.cpp b/lib/utils/string_utils.cpp new file mode 100644 index 0000000..2295a91 --- /dev/null +++ b/lib/utils/string_utils.cpp @@ -0,0 +1,570 @@ +#include "string_utils.h" + +//#include "../../include/petscii.h" +#include "../../include/debug.h" +#include "U8Char.h" + +#include +#include +#include +#include +#include +#include + +// Copy string to char buffer +void copyString(const std::string& input, char *dst, size_t dst_size) +{ + strncpy(dst, input.c_str(), dst_size - 1); + dst[dst_size - 1] = '\0'; +} + +constexpr unsigned int hash(const char *s, int off = 0) { + return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off]; +} + +namespace mstr { + + // trim from start (in place) + void ltrim(std::string &s) + { + s.erase( + s.begin(), + std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); + } + + // trim from end (in place) + void rtrim(std::string &s) + { + s.erase( + std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); + } + void rtrimA0(std::string &s) + { + s.erase( + std::find_if(s.rbegin(), s.rend(), [](int ch) { return !isA0Space(ch); }).base(), s.end()); + } + + // trim from both ends (in place) + void trim(std::string &s) + { + ltrim(s); + rtrim(s); + } + + // is space or petscii shifted space + bool isA0Space(int ch) + { + return ch == '\xA0' || std::isspace(ch); + } + + + + std::string drop(std::string str, size_t count) { + if(count>str.length()) + return ""; + else + return str.substr(count); + } + + std::string dropLast(std::string str, size_t count) { + if(count>str.length()) + return ""; + else + return str.substr(0, str.length()-count); + } + + bool startsWith(std::string s, const char *pattern, bool case_sensitive) + { + if (s.empty() && pattern == nullptr) + return true; + if (s.empty() || pattern == nullptr) + return false; + if(s.length() split(std::string toSplit, char ch, int limit) { + std::vector parts; + + limit--; + + while(limit > 0 && toSplit.size()>0) { + auto pos = toSplit.find(ch); + if(pos == std::string::npos) { + parts.push_back(toSplit); + return parts; + } + parts.push_back(toSplit.substr(0, pos)); + + toSplit = toSplit.substr(pos+1); + + limit--; + } + parts.push_back(toSplit); + + return parts; + } + + std::string joinToString(std::vector::iterator* start, std::vector::iterator* end, std::string separator) { + std::string res; + + if((*start)>=(*end)) + { + //Debug_printv("start >= end"); + return std::string(); + } + + + for(auto i = (*start); i<(*end); i++) + { + //Debug_printv("b %d res [%s]", i, res.c_str()); + res+=(*i); + if(i<(*end)) + res+=separator; + + //Debug_printv("a %d res [%s]", i, res.c_str()); + } + //Debug_printv("res[%s] length[%d] size[%d]", res.c_str(), res.length(), res.size()); + + return res.erase(res.length()-1,1); + } + + std::string joinToString(std::vector strings, std::string separator) { + auto st = strings.begin(); + auto ed = strings.end(); + return joinToString(&st, &ed, separator); + } + + + std::string urlEncode(const std::string &s) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (std::string::const_iterator i = s.begin(), n = s.end(); i != n; ++i) + { + std::string::value_type c = (*i); + + // Keep alphanumeric and other accepted characters intact + if (isalnum((unsigned char)c) || c == '-' || c == '_' || c == '.' || c == '~' || c == '/' || c == ' ') + { + escaped << c; + continue; + } + + // Any other characters are percent-encoded + escaped << std::uppercase; + escaped << '%' << std::setw(2) << int((unsigned char)c); + escaped << std::nouppercase; + } + + return escaped.str(); + } + + std::string urlDecode(std::string s){ + std::string ret; + char ch; + int i, ii, len = s.length(); + + for (i = 0; i < len; i++) + { + if (s[i] != '%') + { + if (s[i] == '+') + ret += ' '; + else + ret += s[i]; + } + else + { + sscanf(s.substr(i + 1, 2).c_str(), "%x", &ii); + ch = static_cast(ii); + ret += ch; + i += 2; + } + } + + return ret; + } + + std::string format(const char *format, ...) + { + // Format our string + va_list args; + va_start(args, format); + char text[vsnprintf(NULL, 0, format, args) + 1]; + vsnprintf(text, sizeof text, format, args); + va_end(args); + + return text; + } + + std::string formatBytes(uint64_t size) + { + std::string byteSuffixes[9] = { "", "K", "M", "G", "T"}; //, "P", "E", "Z", "Y" }; + uint8_t i = 0; + double n = 0; + + //Debug_printv("bytes[%llu]", size); + do + { + n = size / std::pow(1024, ++i); + //Debug_printv("i[%d] n[%llu]", i, n); + } + while ( n >= 1 ); + + n = size / std::pow(1024, --i); + return format("%.2f %s", n, byteSuffixes[i].c_str()); + } + + + void cd( std::string &path, std::string newDir) + { + //Debug_printv("cd requested: [%s]", newDir.c_str()); + + // OK to clarify - coming here there should be ONLY path or magicSymbol-path combo! + // NO "cd:xxxxx", no "/cd:xxxxx" ALLOWED here! ****************** + // + // if you want to support LOAD"CDxxxxxx" just parse/drop the CD BEFORE calling this function + // and call it ONLY with the path you want to change into! + + if(newDir[0]=='/' && newDir[1]=='/') { + if(newDir.size()==2) { + // user entered: CD:// or CD// + // means: change to the root of roots + path = "/"; // chedked, works ad flash root! + return; + } + else { + // user entered: CD://DIR or CD//DIR + // means: change to a dir in root of roots + path = mstr::drop(newDir,2); + return; + } + } + // else if(newDir[0]=='/' || newDir[0]=='^') { + // if(newDir.size()==1) { + // // user entered: CD:/ or CD/ + // // means: change to container root + // // *** might require a fix for flash fs! + // //return MFSOwner::File(streamPath); + // return MFSOwner::File("/"); + // } + // else { + // // user entered: CD:/DIR or CD/DIR + // // means: change to a dir in container root + // return localRoot(mstr::drop(newDir,1)); + // } + // } + else if(newDir[0]=='_') { + if(newDir.size()==1) { + // user entered: CD:_ or CD_ + // means: go up one directory + path = parent(path); + return; + } + else { + // user entered: CD:_DIR or CD_DIR + // means: go to a directory in the same directory as this one + path = parent(path, mstr::drop(newDir,1)); + return; + } + } + + if(newDir[0]=='.' && newDir[1]=='.') { + if(newDir.size()==2) { + // user entered: CD:.. or CD.. + // means: go up one directory + path = parent(path); + return; + } + else { + // user entered: CD:..DIR or CD..DIR + // meaning: Go back one directory + path = localParent(path, mstr::drop(newDir,2)); + return; + } + } + + //Debug_printv("> url[%s] newDir[%s]", url.c_str(), newDir.c_str()); + // Add new directory to path + if ( !mstr::endsWith(path, "/") && newDir.size() ) + path.push_back('/'); + + path += newDir; + return; + } + + + std::string parent(std::string path, std::string plus) + { + //Debug_printv("url[%s] path[%s]", url.c_str(), path.c_str()); + + // drop last dir + // add plus + if(path.empty()) { + // from here we can go only to flash root! + return "/"; + } + else { + int lastSlash = path.find_last_of('/'); + if ( lastSlash == path.size() - 1 ) { + lastSlash = path.find_last_of('/', path.size() - 2); + } + std::string newDir = mstr::dropLast(path, path.size() - lastSlash); + if(!plus.empty()) + newDir+= ("/" + plus); + return newDir; + } + } + + std::string localParent(std::string path, std::string plus) + { + //Debug_printv("url[%s] path[%s]", url.c_str(), path.c_str()); + // drop last dir + // check if it isn't shorter than streamFile + // add plus + int lastSlash = path.find_last_of('/'); + if ( lastSlash == path.size() - 1 ) { + lastSlash = path.find_last_of('/', path.size() - 2); + } + std::string parent = mstr::dropLast(path, path.size() - lastSlash); + // if(parent.length()-streamFile->url.length()>1) + // parent = streamFile->url; + return parent + "/" + plus; + } +} \ No newline at end of file diff --git a/lib/utils/string_utils.h b/lib/utils/string_utils.h new file mode 100644 index 0000000..b76744e --- /dev/null +++ b/lib/utils/string_utils.h @@ -0,0 +1,70 @@ +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include +#include + +#include + +void copyString(const std::string& input, char *dst, size_t dst_size); + +// inline constexpr auto hash_djb2a(const std::string_view sv) { +// unsigned long hash{ 5381 }; +// for (unsigned char c : sv) { +// hash = ((hash << 5) + hash) ^ c; +// } +// return hash; +// } + +// inline constexpr auto operator"" _sh(const char *str, size_t len) { +// return hash_djb2a(std::string_view{ str, len }); +// } + +namespace mstr { + std::string drop(std::string str, size_t count); + std::string dropLast(std::string str, size_t count); + + bool startsWith(std::string s, const char *pattern, bool case_sensitive = true); + bool endsWith(std::string s, const char *pattern, bool case_sensitive = true); + + bool equals(std::string &s1, std::string &s2, bool case_sensitive = true); + bool equals(std::string &s1, char *s2, bool case_sensitive = true); + bool equals(const char* s1, const char *s2, bool case_sensitive); + bool contains(std::string &s1, const char *s2, bool case_sensitive = true); + bool compare(std::string &s1, std::string &s2, bool case_sensitive = true); // s1 is Wildcard string, s2 is potential match + + std::vector split(std::string toSplit, char ch, int limit = 9999); + void toLower(std::string &s); + void toUpper(std::string &s); + + void ltrim(std::string &s); + void rtrim(std::string &s); + void rtrimA0(std::string &s); + void trim(std::string &s); + + void replaceAll(std::string &s, const std::string &search, const std::string &replace); + + std::string joinToString(std::vector::iterator* start, std::vector::iterator* end, std::string separator); + std::string joinToString(std::vector, std::string separator); + + std::string urlEncode(const std::string &s); + std::string urlDecode(std::string s); + + // void toASCII(std::string &s); + // void toPETSCII(std::string &s); + std::string toUTF8(std::string &petsciiInput); + std::string toPETSCII2(std::string &utfInputString); + + bool isText(std::string &s); + bool isNumeric(std::string &s); + bool isA0Space(int ch); + void A02Space(std::string &s); + + std::string format(const char *format, ...); + std::string formatBytes(uint64_t value); + + void cd(std::string &path, std::string newDir); + std::string parent(std::string path, std::string plus = ""); + std::string localParent(std::string path, std::string plus); +} +#endif \ No newline at end of file diff --git a/modify_path.py b/modify_path.py new file mode 100644 index 0000000..3c385ed --- /dev/null +++ b/modify_path.py @@ -0,0 +1,3 @@ +import os + +os.environ["path"] += "/usr/bin" \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..38a7eac --- /dev/null +++ b/platformio.ini @@ -0,0 +1,91 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +[env] +; Common settings for all enivornments +build_type = debug +lib_ldf_mode = deep+ +upload_speed = 460800 ;921600 +;upload_port = COM1 ; Windows +;upload_port = /dev/ttyUSB0 ; Linux +upload_port = /dev/cu.usbserial-1450 ; Mac +monitor_speed = 460800 ;921600 +;monitor_port = COM1 ; Windows +;monitor_port = /dev/ttyUSB0 ; Linux +monitor_port = /dev/cu.usbserial-1450 ; Mac +monitor_filters = esp32_exception_decoder +check_skip_packages = yes +;debug_tool = esp-prog +;debug_init_break = tbreak setup + +; Build flags used by ALL boards go here +build_flags = + ;-D ${meatloaf.build_platform} + ;-D ${meatloaf.flash_filesystem} + -D CMAKE_EXPORT_COMPILE_COMMANDS=ON + + ; ; Default WiFi + ; -D WIFI_SSID=\"${meatloaf.wifi_ssid}\" + ; -D WIFI_PASSWORD=\"${meatloaf.wifi_pass}\" + + ; Firmware Version + -D MEATLOAF_MAX + + ; IEC Hardware Options + ; These options are also useful for reusing a PI1541 Hat with Meatloaf + ;-D IEC_SPLIT_LINES ; hardware uses seperate input/output lines + ;-D IEC_INVERTED_LINES ; hardware inverts the signals + + ; Other Hardware Options + ;-D NO_BUTTONS ; if your hardware has no physical buttons + ;-D SD_CARD ; if your hardware has an sd card slot + ;-D LED_STRIP ; if your hardware has an LED strip + ;-D PIEZO_BUZZER ; if your hardware has a piezo buzzer + ;-D PARALLEL_BUS ; if your hardware has userport parallel interface + ;-D JTAG ; enable use with JTAG debugger + ;-D BLUETOOTH_SUPPORT ; enable BlueTooth support + + ; Service Options + ;-D ENABLE_ZIMODEM ; enable Zimodem functionality + ;-D ENABLE_SSDP ; enable Simple Service Discovery Protocol service + + ; GPIO Expander Selection (select only one if available) + ;-D GPIOX_PCF8575 + ;-D GPIOX_MCP23017 + ;-D GPIOX_XRA1405 + + ; DEBUG Options + -D DEBUG_SPEED=${env.monitor_speed} + -D DATA_STREAM + ;-D VERBOSE_TNFS + ;-D VERBOSE_DISK + ;-D VERBOSE_HTTP + ;-D DEBUG_TIMING + ;-D NO_VIRTUAL_KEYBOARD + ;-D DBUG2 ; enable monitor messages for a release build + + +[env:native] +; Add Code Coverage Reporting +; https://piolabs.com/blog/insights/test-coverage-on-unit-testing.html +; https://blog.leon0399.ru/platformio-coverage-github-actions +platform = native +test_filter = native/* +extra_scripts = +; pre:modify_path.py + post:deploy.py +build_flags = + ${env.build_flags} + -D TEST_NATIVE + -D BUILD_IEC + ;-lgcov + ;--coverage + -std=c++11 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..49d0c82 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,42 @@ + +#include "test_parsers.h" +#include "test_punycode.h" +#include "test_archive.h" +#include "test_meatloaf_filesystems.h" + +bool pt = false; // Run Parser Tests +bool pct = true; // Run Punycode Tests +bool at = true; // Run Archive Tests +bool mlfs = false; // Run Meatloaf File System Tests + +int main(int argc, char **argv) +{ + // URL Parser + if ( pt ) + { + test_parsers("https://user:password@c64.meatloaf.cc:64128/some/long/path/to/file.ext?query#fragment"); + test_parsers("https://meatloaf.cc"); + test_parsers("ftp://user:pass@domain.com/path/file.ext"); + test_parsers("/path/archive.zip/file.ext"); + test_parsers("https://api.meatloaf.cc/?fb64"); + test_parsers("/fb64"); + test_parsers("/path/to/file.d64/file 1 #ipx"); + } + + // Punycode + if ( pct ) + test_punycode(); + + // Archive + if ( at ) + test_archive(); + + // Meatloaf File System + if ( mlfs ) + { + test_meatloaf_mfile_properties("goonies.d64"); + test_meatloaf_mfile_directory("goonies.d64"); + test_meatloaf_mfile_directory("ultima iii.d81"); + test_meatloaf_mfile_properties("ultima iii.d81"); + } +} \ No newline at end of file diff --git a/src/test_archive.cpp b/src/test_archive.cpp new file mode 100755 index 0000000..8e61f3c --- /dev/null +++ b/src/test_archive.cpp @@ -0,0 +1,201 @@ +#include "test_archive.h" +#include + +struct archive *a; + +/* Returns pointer and size of next block of data from archive. */ +// The read callback returns the number of bytes read, zero for end-of-file, or a negative failure code as above. +// It also returns a pointer to the block of data read. +// https://github.com/libarchive/libarchive/wiki/LibarchiveIO +// +// This callback is just a way to get bytes from srcStream into libarchive for processing +ssize_t cb_read(struct archive *a, void *userData, const void **buff) +{ + // ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + // // 1. we have to call srcStr.read(...) + // ssize_t bc = streamData->srcStream->read(streamData->srcBuffer, ArchiveStream::buffSize); + // //std::string dump((char*)streamData->srcBuffer, bc); + // //Debug_printv("Libarch pulling data from src MStream, got bytes:%d", bc); + // //Debug_printv("Dumping bytes: %s", dump.c_str()); + // // 2. set *buff to the bufer read in 1. + // *buff = streamData->srcBuffer; + // // 3. return read bytes count + // return bc; + + FILE* file = ((ArchiveStreamData*)userData)->file; + + char buffer[4096]; // Assuming a buffer size of 100 bytes + size_t bytesRead = fread(buffer, 1, sizeof(buffer), file); + + // Check if reading was successful + if (bytesRead > 0) { + *buff = buffer; + printf("cb_read: ZIPFILE->LIBARCH %zu bytes from the file\n", bytesRead); + } else if(bytesRead == 0) { + printf("cb_read: End of file reached\n"); + } else { + printf("cb_read: Error reading from the file %zu\n", bytesRead); + } + + return bytesRead; +} + +/* +It must return the number of bytes actually skipped, or a negative failure code if skipping cannot be done. +It can skip fewer bytes than requested but must never skip more. +Only positive/forward skips will ever be requested. +If skipping is not provided or fails, libarchive will call the read() function and simply ignore any data that it does not need. + +* Skips at most request bytes from archive and returns the skipped amount. +* This may skip fewer bytes than requested; it may even skip zero bytes. +* If you do skip fewer bytes than requested, libarchive will invoke your +* read callback and discard data as necessary to make up the full skip. +*/ +// https://github.com/libarchive/libarchive/wiki/LibarchiveIO +int64_t cb_skip(struct archive *a, void *userData, int64_t request) +{ + std::cout << "cb_skip called" << request << "\n"; + // Debug_printv("bytes[%d]", request); + // ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + + // if (streamData->srcStream->isOpen()) + // { + // bool rc = streamData->srcStream->seek(request, SEEK_CUR); + // return (rc) ? request : ARCHIVE_WARN; + // } + // else + // { + // Debug_printv("ERROR! skip failed"); + // return ARCHIVE_FATAL; + // } + return ARCHIVE_FATAL; +} + +int64_t cb_seek(struct archive *a, void *userData, int64_t offset, int whence) +{ + // Debug_printv("offset[%d] whence[%d] (0=begin, 1=curr, 2=end)", offset, whence); + // ArchiveStreamData *streamData = (ArchiveStreamData *)userData; + + // if (streamData->srcStream->isOpen()) + // { + // bool rc = streamData->srcStream->seek(offset, whence); + // return (rc) ? offset : ARCHIVE_WARN; + // } + // else + // { + // Debug_printv("ERROR! seek failed"); + // return ARCHIVE_FATAL; + // } + + FILE* file = ((ArchiveStreamData*)userData)->file; + auto rc = fseek(file, offset, whence); + + std::cout << "cb_seek called for:" << offset << "," << whence << + " rc:" << rc << "\n"; + + return (rc==0) ? ARCHIVE_OK : ARCHIVE_FATAL; +} + +int cb_close(struct archive *a, void *userData) +{ + std::cout << "cb_close called\n"; + // ArchiveStreamData *src_str = (ArchiveStreamData *)userData; + + // Debug_printv("Libarch wants to close, but we do nothing here..."); + + // // do we want to close srcStream here??? + FILE* file = ((ArchiveStreamData*)userData)->file; + + if(file) + fclose(file); + return (ARCHIVE_OK); +} + +int cb_open(struct archive *arch, void *userData) +{ + std::cout << "cb_open called\n"; + // maybe we can use open for something? Check if stream is open? + + const char *filePath = "message2.zip"; + //const char *filePath = "_arch.7z"; + + // Open the file in binary mode for reading + ((ArchiveStreamData*)userData)->file = fopen(filePath, "rb"); + std::cout << "end of cb_open call\n"; + + return (ARCHIVE_OK); +} + + +void read_dir() +{ + struct archive_entry *entry; + char buffer[4096]; // Assuming a buffer size of 100 bytes + + while(archive_read_next_header(a, &entry) == ARCHIVE_OK) + { + std::cout << "entry name: " << archive_entry_pathname(entry) << std::endl; + } +} + +// https://github.com/mpv-player/mpv/blob/d8c2e33a5d3840045a84cd5fa22c9c601fb1a0ae/stream/stream_libarchive.c#L315 +// funkcja: +// archive_entry_open +// -> reopen_archive otwiera stumień i potem +// -> mp_archive_next_entry +// -> iteruje archive_read_next_header +// wszystko, co ciekawe musi dziać się w mp_archive_new_raw + +void read_zipped() +{ + struct archive_entry *entry; + char buffer[4096]; // Assuming a buffer size of 100 bytes + + while(archive_read_next_header(a, &entry) == ARCHIVE_OK) + { + std::cout << "=== FOUND FILE: " << archive_entry_pathname(entry) << std::endl; + // archive_read_data_skip(a); + while(int r = archive_read_data(a, buffer, 4096)) + { + if(r < 0) { + // -10 retry -20 warn -25 failed -30 fatal + std::cout << "archive_read_data failed with: " << r << std::endl; + std::cout << "error: " << archive_error_string(a) << "\n"; + + break; + } + else + { + std::string data(buffer, r); + std::cout << "LIBARCH->UNZIPPED data count: " << r << "\n"; + // "\n>>>>>>>>>>>>>>>>\n" << data << "\n<<<<<<<<<<<<<<<<<<<<" << std::endl; + } + } + std::cout << "attempting to get the next header\n"; + } +} + + + +void testArchive() +{ + std::cout << "archive_reaa_new\n"; + a = archive_read_new(); + std::cout << "archive filters\n"; + archive_read_support_filter_all(a); + archive_read_support_format_all(a); + + archive_read_set_read_callback(a, cb_read); + // archive_read_set_skip_callback(a, cb_skip); + archive_read_set_seek_callback(a, cb_seek); + archive_read_set_close_callback(a, cb_close); + archive_read_set_open_callback(a, cb_open); + archive_read_set_callback_data(a, &streamData); + std::cout << "archive_read_open\n"; + int r = archive_read_open1(a); + std::cout << "let's read the zip!\n"; + read_zipped(); + archive_read_close(a); + archive_read_free(a); + //std::cout << "Archive created: " << archive_path << std::endl; +} \ No newline at end of file diff --git a/src/test_archive.h b/src/test_archive.h new file mode 100755 index 0000000..12d832b --- /dev/null +++ b/src/test_archive.h @@ -0,0 +1,18 @@ +#ifndef TEST_ARCHIVE_H +#define TEST_ARCHIVE_H + +#include +#include +#include + +void test_archive(); + +class ArchiveStreamData { +public: + uint8_t *srcBuffer = nullptr; + FILE *file = nullptr; // a stream that is able to serve bytes of this archive +}; + +static ArchiveStreamData streamData; + +#endif // TEST_ARCHIVE_H \ No newline at end of file diff --git a/src/test_meatloaf_filesystems.cpp b/src/test_meatloaf_filesystems.cpp new file mode 100644 index 0000000..c6fe497 --- /dev/null +++ b/src/test_meatloaf_filesystems.cpp @@ -0,0 +1,58 @@ +#include "test_meatloaf_filesystems.h" +#include + +#include +#include + +#include "meat_io.h" +#include "meat_stream.h" +#include "meat_buffer.h" + + +void test_meatloaf_mfile_directory(std::string path) +{ + DIR *dir; + struct dirent *ent; + struct stat st; + + if ((dir = opendir ( path.c_str() )) != NULL) { + /* print all the files and directories within directory */ + while ((ent = readdir (dir)) != NULL) { + stat(ent->d_name, &st); + printf ("%8lld %-30s %8hu\r\n", st.st_size, ent->d_name, st.st_mode); + } + closedir (dir); + } else { + /* could not open directory */ + printf ("error"); + } +} + +void test_meatloaf_mfile_properties(std::string path) +{ + std::unique_ptr testMFile = Meat::New( path ); + + printf("MFile [%s]\r\n", testMFile->url.c_str()); + printf("Scheme: [%s]\r\n", testMFile->scheme.c_str()); + printf("User: [%s]\r\n", testMFile->user.c_str()); + printf("Password: [%s]\r\n", testMFile->password.c_str()); + printf("Host: [%s]\r\n", testMFile->host.c_str()); + printf("Port: [%s]\r\n", testMFile->port.c_str()); + printf("Path: [%s]\r\n", testMFile->path.c_str()); + printf("\r\n-\r\n"); + printf("Name: [%s]\r\n", testMFile->name.c_str()); + printf("Base Name: [%s]\r\n", testMFile->base_name.c_str()); + printf("Extension: [%s]\r\n", testMFile->extension.c_str()); + printf("Query: [%s]\r\n", testMFile->query.c_str()); + printf("Fragment: [%s]\r\n", testMFile->fragment.c_str()); + printf("\r\n-\r\n"); + printf("pathInStream: [%s]\r\n", testMFile->pathInStream.c_str()); + if ( testMFile->streamFile ) + printf("streamFile: [%s]\r\n", testMFile->streamFile->url.c_str()); + printf("\r\n-\r\n"); + printf("exists: [%d]\r\n", testMFile->exists()); + printf("size: [%d]\r\n", testMFile->size()); + printf("isText: [%d]\r\n", testMFile->isText()); + printf("isDirectory: [%d]\r\n", testMFile->isDirectory()); + printf("-------------------------------\r\n"); +} diff --git a/src/test_meatloaf_filesystems.h b/src/test_meatloaf_filesystems.h new file mode 100644 index 0000000..ae65565 --- /dev/null +++ b/src/test_meatloaf_filesystems.h @@ -0,0 +1,9 @@ +#ifndef TEST_MEATLOAF_FILESYSTEM_H +#define TEST_MEATLOAF_FILESYSTEM_H + +#include + +void test_meatloaf_mfile_directory(std::string path); +void test_meatloaf_mfile_properties(std::string path); + +#endif // TEST_MEATLOAF_FILESYSTEM_H \ No newline at end of file diff --git a/src/test_parsers.cpp b/src/test_parsers.cpp new file mode 100644 index 0000000..8312cc1 --- /dev/null +++ b/src/test_parsers.cpp @@ -0,0 +1,44 @@ +#include "test_parsers.h" + +#include "EdUrlParser.h" +#include "peoples_url_parser.h" + +void test_parsers(std::string path) +{ + test_EdUrlParser(path); + test_PeoplesUrlParser(path); + printf("==================================\r\n\r\n"); +} + +void test_EdUrlParser(std::string path) +{ + EdUrlParser *url = EdUrlParser::parseUrl( path ); + + + printf("EdUP [%s]\r\n", url->mRawUrl.c_str()); + printf("Scheme: [%s]\r\n", url->scheme.c_str()); + printf("Host: [%s]\r\n", url->hostName.c_str()); + printf("Port: [%s]\r\n", url->port.c_str()); + printf("Path: [%s]\r\n", url->path.c_str()); + printf("Query: [%s]\r\n", url->query.c_str()); + printf("Fragment: [%s]\r\n", url->fragment.c_str()); +} + +void test_PeoplesUrlParser(std::string path) +{ + PeoplesUrlParser *url = PeoplesUrlParser::parseURL( path ); + + printf("PUP [%s]\r\n", url->url.c_str()); + printf("Scheme: [%s]\r\n", url->scheme.c_str()); + printf("User: [%s]\r\n", url->user.c_str()); + printf("Password: [%s]\r\n", url->password.c_str()); + printf("Host: [%s]\r\n", url->host.c_str()); + printf("Port: [%s]\r\n", url->port.c_str()); + printf("Path: [%s]\r\n-\r\n", url->path.c_str()); + printf("Name: [%s]\r\n", url->name.c_str()); + printf("Base Name: [%s]\r\n", url->base_name.c_str()); + printf("Extension: [%s]\r\n", url->extension.c_str()); + printf("Query: [%s]\r\n", url->query.c_str()); + printf("Fragment: [%s]\r\n", url->fragment.c_str()); + printf("----------------------------------\r\n"); +} diff --git a/src/test_parsers.h b/src/test_parsers.h new file mode 100644 index 0000000..0995100 --- /dev/null +++ b/src/test_parsers.h @@ -0,0 +1,10 @@ +#ifndef TEST_PARSERS_H +#define TEST_PARSERS_H + +#include + +void test_parsers(std::string path); +void test_EdUrlParser(std::string path); +void test_PeoplesUrlParser(std::string path); + +#endif // TEST_PARSERS_H \ No newline at end of file diff --git a/src/test_punycode.cpp b/src/test_punycode.cpp new file mode 100644 index 0000000..41e8f9b --- /dev/null +++ b/src/test_punycode.cpp @@ -0,0 +1,37 @@ +#include "U8Char.h" +#include "punycode.h" +#include +#include "archive.h" + +void test_punycode() +{ + //std::string chinese = "文件档案名"; + // 0x6587 0x4EF6 0x6863 0x6848 0x540D + // E6 96 87 E4 BB B6 E6 A1 A3 E6 A1 88 E5 90 8D + const uint32_t chineseAsUnicode[] = {0x6587, 0x4ef6, 0x6843, 0x684c, 0x540d}; + char asPunycode[256]; + size_t dstlen = sizeof asPunycode; + // size_t punycode_encode(const uint32_t *const src, const size_t srclen, char *const dst, size_t *const dstlen) + punycode_encode(chineseAsUnicode, 5, asPunycode, &dstlen); + std::string punycode(asPunycode, dstlen); + printf("Chinese U32 as punycode:'%s' (should be 5nqx7jp0rbsckb)\n", punycode.c_str()); + + std::string asUnicode = U8Char::fromPunycode("5nqx7jp0rbsckb"); + printf("Chinese UTF8 from the above punycode:'%s (should be 文件档案名)'\n", asUnicode.c_str()); + + std::string punycode2 = U8Char::toPunycode(asUnicode); + printf("Chinese text as from punycode again:'%s'\n", punycode2.c_str()); + + + // uint32_t asU32[1024]; + //char asPunycode[1024]; + //dstlen = sizeof asPunycode; + // size_t n_converted; + // U8Char temp(' '); + + // Debug_printv("Calling toUnicode32\n"); + // size_t conv_len = temp.toUnicode32(asUnicode, asU32, sizeof asU32); + // Debug_printv("Conv len=%d, encoding now...\n", conv_len); + // n_converted = punycode_encode(asU32, conv_len, asPunycode, &dstlen); + +} diff --git a/src/test_punycode.h b/src/test_punycode.h new file mode 100644 index 0000000..109abb5 --- /dev/null +++ b/src/test_punycode.h @@ -0,0 +1,6 @@ +#ifndef TEST_PUNYCODE_H +#define TEST_PUNYCODE_H + +void test_punycode(); + +#endif // TEST_PUNYCODE_H \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html