initial commit

This commit is contained in:
Jaime Idolpx 2024-01-08 11:58:15 -06:00
parent 8320b4f9dd
commit 094e3708e4
148 changed files with 16589 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.pio
.vscode
bin

4
CMakeLists.txt Normal file
View File

@ -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)

40
deploy.py Normal file
View File

@ -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\<env name>\partitions.bin
# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin
# - 0x10000 | ~\ESPEasy\.pio\build\<env name>/<built binary>.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)

39
include/README Normal file
View File

@ -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

131
include/ansi_codes.h Normal file
View File

@ -0,0 +1,131 @@
/*
* This is free and unencumbered software released into the public domain.
*
* For more information, please refer to <https://unlicense.org>
*
* 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"

209
include/cbm_defines.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#ifndef CBMDEFINES_H
#define CBMDEFINES_H
#include <cstdint>
// 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

48
include/debug.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef _DEBUG_H_
#define _DEBUG_H_
#include <string>
#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_

View File

@ -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 && BUF[POS]==' ';POS++)
#define WALK_UNTIL(POS, LEN, BUF, DELC) for(;POS<LEN && BUF[POS]!=DELC;POS++)
#define WALK_UNTIL2(POS, LEN, BUF, DELI1, DELI2) for(;POS<LEN && BUF[POS]!=DELI1 && BUF[POS]!=DELI2 ;POS++)
#define WALK_UNTIL3(POS, LEN, BUF, DELI1, DELI2, DELI3) for(;POS<LEN && BUF[POS]!=DELI1 && BUF[POS]!=DELI2 && BUF[POS]!=DELI3;POS++)
#define CHECK_REMAIN_END(POS, LEN, REQ_LEN) if(LEN-POS < REQ_LEN) {_url_errorno=100; goto __PARSE_END; }
#define WALK_CHAR(POS, BUF, DELI) if(BUF[POS++] != DELI) goto __PARSE_END
int __kv_callback_map(void* list, string k, string v);
int __kv_callback_vec(void* list, string k, string v);
EdUrlParser::EdUrlParser() {
}
EdUrlParser::~EdUrlParser() {
}
string EdUrlParser::urlDecode(const string &str) {
int _url_errorno = 0;
size_t pos = 0, per = 0;
size_t len = str.size();
const char* buf = str.c_str();
string decstr;
_url_errorno = 0;
for (per = pos = 0;;) {
WALK_UNTIL2(pos, len, buf, '%', '+');
decstr.append(buf, per, pos - per);
if (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<string>* 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<string, string> *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<string, string>*)list;
(*map)[k] = v;
return map->size();
}
int __kv_callback_vec(void* list, string k, string v) {
auto *vec = (vector<query_kv_t>*)list;
query_kv_t t ={k, v};
vec->push_back(t);
return vec->size();
}
bool EdUrlParser::isValidUrl()
{
return !scheme.empty() && !(path.empty() && port.empty());
}

View File

@ -0,0 +1,55 @@
/*
* EdUrlParser.h
*
* Created on: Nov 25, 2014
* Author: netmind
*/
#ifndef EDURLPARSER_H_
#define EDURLPARSER_H_
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
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<string> *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<string, string> *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_ */

71
lib/bus/bus.h Normal file
View File

@ -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

980
lib/bus/iec/iec.cpp Normal file
View File

@ -0,0 +1,980 @@
//#ifdef BUILD_IEC
#include "iec.h"
#include <cstring>
#include <memory>
#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<std::string> systemBus::selectProtocol()
{
// //Debug_printv("protocol[%d]", detected_protocol);
// switch(detected_protocol)
// {
// #ifdef MEATLOAF_MAX
// case PROTOCOL_SAUCEDOS:
// {
// auto p = std::make_shared<SauceDOS>();
// return std::static_pointer_cast<IECProtocol>(p);
// }
// #endif
// case PROTOCOL_JIFFYDOS:
// {
// auto p = std::make_shared<JiffyDOS>();
// return std::static_pointer_cast<IECProtocol>(p);
// }
// #ifdef PARALLEL_BUS
// case PROTOCOL_DOLPHINDOS:
// {
// auto p = std::make_shared<DolphinDOS>();
// return std::static_pointer_cast<IECProtocol>(p);
// }
// #endif
// default:
// {
// #ifdef PARALLEL_BUS
// PARALLEL.bus_state = PBUS_IDLE;
// #endif
// auto p = std::make_shared<CPBStandardSerial>();
// return std::static_pointer_cast<IECProtocol>(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<std::string>().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 */

582
lib/bus/iec/iec.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
//
// 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 <cstdint>
#include <forward_list>
// #include <freertos/FreeRTOS.h>
// #include <freertos/queue.h>
#include <utility>
#include <string>
#include <map>
#include <queue>
#include <memory>
//#include <driver/gpio.h>
//#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 <soc/gpio_reg.h>
#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<std::string> response_queue;
/**
* @brief status override (for binary commands)
*/
std::string status_override;
/**
* @brief tokenized payload
*/
std::vector<std::string> pt;
std::vector<uint8_t> 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<virtualDevice *> _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<std::string> protocol = nullptr;
/**
* @brief Switch to detected bus protocol
*/
std::shared_ptr<std::string> 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 */

911
lib/device/iec/drive.cpp Normal file
View File

@ -0,0 +1,911 @@
#include "drive.h"
#include <unordered_map>
#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<MStream> 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<MStream>(_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<MStream>(_base->meatStream());
new_stream->open();
}
else
{
Debug_printv("OTHER \"%s\"", _base->url.c_str());
new_stream = std::shared_ptr<MStream>(_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<MStream> 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<MFile> entry = std::unique_ptr<MFile>( _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<MStream> istream = std::static_pointer_cast<MStream>(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<MStream> istream = std::static_pointer_cast<MStream>(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

79
lib/device/iec/drive.h Normal file
View File

@ -0,0 +1,79 @@
#include <stdint.h>
#include <string>
#include <unordered_map>
#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<MFile> _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<MStream> currentStream;
bool registerStream (uint8_t channel);
std::shared_ptr<MStream> 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<uint16_t, std::shared_ptr<MStream>> streams;
std::unordered_map<uint16_t, uint16_t> streamLastByte;
public:
void sendListing();
bool sendFile();
bool saveFile();
};

View File

@ -0,0 +1,30 @@
#ifndef MEATLOAF_EXCEPTION
#define MEATLOAF_EXCEPTION
#include <exception>
// 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

View File

@ -0,0 +1,364 @@
#include "archive_ml.h"
#include <archive.h>
#include <archive_entry.h>
#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<MStream> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<MStream> 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<MStream>(this->getSourceStream());
if(dirStream->isOpen())
{
return true;
}
else
{
Debug_printv("opening Archive for dir nok");
return false;
}
}

View File

@ -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 <archive.h>
// #include <archive_entry.h>
// #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<MStream> 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<MStream> 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<MStream> 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<MStream> 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

View File

@ -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
//

View File

@ -0,0 +1,5 @@
// .LBR - LiBRary containers
//
// https://ist.uwaterloo.ca/~schepers/formats/LBR.TXT
// https://github.com/talas/lbrtool
//

View File

View File

@ -0,0 +1,4 @@
// .LNX - LyNX containers
//
// https://ist.uwaterloo.ca/~schepers/formats/LNX.TXT
//

View File

View File

@ -0,0 +1,4 @@
// .SDA, .ARC - Self-Dissolving Archive
//
// https://ist.uwaterloo.ca/~schepers/formats/SDA.TXT
//

View File

@ -0,0 +1,4 @@
// .SFX, .LHA - Self-Extracting Archive
//
// https://ist.uwaterloo.ca/~schepers/formats/SFX.TXT
//

View File

@ -0,0 +1,4 @@
// .SPY - SPYne containers
//
// https://ist.uwaterloo.ca/~schepers/formats/SPYNE.TXT
//

View File

View File

@ -0,0 +1,4 @@
// .WRA, .WR3 - Wraptor and Wraptor 3
//
// https://ist.uwaterloo.ca/~schepers/formats/WRA-WR3.TXT
//

View File

@ -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
//

View File

@ -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
//

View File

@ -0,0 +1,4 @@
// EasyFlash 3 Cart File System
// https://skoe.de/easyflash/develdocs/
// https://bitbucket.org/skoe/easyflash/src/master/
//

View File

105
lib/meatloaf/cbm_media.cpp Normal file
View File

@ -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<std::string, CBMImageStream*> ImageBroker::repo;

218
lib/meatloaf/cbm_media.h Normal file
View File

@ -0,0 +1,218 @@
#ifndef MEATLOAF_CBM_MEDIA
#define MEATLOAF_CBM_MEDIA
#include "meat_io.h"
#include <map>
#include <bitset>
#include <unordered_map>
#include <sstream>
#include "../../include/debug.h"
#include "string_utils.h"
/********************************************************
* Streams
********************************************************/
class CBMImageStream: public MStream {
public:
CBMImageStream(std::shared_ptr<MStream> 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<MStream> 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<std::string, CBMImageStream*> repo;
public:
template<class T> 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<CBMImageStream>(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

View File

@ -0,0 +1,11 @@
#include "d8b.h"
/********************************************************
* File implementations
********************************************************/
MStream* D8BFile::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new D8BIStream(containerIstream);
}

View File

@ -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<MStream> is) : D64IStream(is)
{
// D8B Partition Info
std::vector<BlockAllocationMap> 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<std::string, std::string> 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<MStream> 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 */

View File

@ -0,0 +1,11 @@
#include "dfi.h"
/********************************************************
* File implementations
********************************************************/
MStream* DFIFile::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new DFIIStream(containerIstream);
}

View File

@ -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<MStream> is) : D64IStream(is)
{
// DFI Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

View File

@ -0,0 +1,505 @@
#include "flash.h"
#include <sys/stat.h>
#include <unistd.h>
#include <sstream>
#include <iomanip>
#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<MStream> 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());
// }
}

167
lib/meatloaf/device/flash.h Normal file
View File

@ -0,0 +1,167 @@
#ifndef MEATLOAF_DEVICE_FLASH
#define MEATLOAF_DEVICE_FLASH
#include "meat_io.h"
#include "make_unique.h"
#include <dirent.h>
#include <string.h>
#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<MStream> 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<FlashHandle>();
//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<FlashHandle> handle;
};
#endif // MEATLOAF_DEVICE_FLASH

View File

@ -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 <stdio.h>
// #include <stdbool.h>
// #include <stdint.h>
// #include <inttypes.h>
// #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;
// }

202
lib/meatloaf/device/psram.h Normal file
View File

@ -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 <stdio.h>
// #include <stdbool.h>
// #include <stdint.h>
// #include <inttypes.h>
// #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 <cstdint>
// #include <string>
// #include <cstring>
// #include <unordered_map>
// #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<MStream> 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

49
lib/meatloaf/device/sd.h Executable file
View File

@ -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 <dirent.h>
#include <string.h>
#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

4
lib/meatloaf/disk/d40.h Normal file
View File

@ -0,0 +1,4 @@
// .D40 - 2040, 3040 disk image format
//
// http://www.baltissen.org/newhtm/diskimag.htm
//

468
lib/meatloaf/disk/d64.cpp Normal file
View File

@ -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<uint8_t> 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<MStream> 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<D64IStream>(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<D64IStream>(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<D64IStream>(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<D64IStream>(streamFile->url)->entry;
uint32_t bytes = UINT16_FROM_LE_UINT16(entry.blocks);
return bytes;
}

341
lib/meatloaf/disk/d64.h Normal file
View File

@ -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 <map>
#include <bitset>
#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<BlockAllocationMap> 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<Partition> partitions;
std::vector<uint8_t> sectorsPerTrack = { 17, 18, 19, 21 };
std::vector<uint8_t> 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<MStream> is) : CBMImageStream(is)
{
// D64 Partition Info
std::vector<BlockAllocationMap> 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<std::string, std::string> 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<uint8_t> 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<MStream> 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 */

11
lib/meatloaf/disk/d71.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "d71.h"
/********************************************************
* File implementations
********************************************************/
MStream* D71File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new D71IStream(containerIstream);
}

118
lib/meatloaf/disk/d71.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// D71 Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

11
lib/meatloaf/disk/d80.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "d80.h"
/********************************************************
* File implementations
********************************************************/
MStream* D80File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new D80IStream(containerIstream);
}

103
lib/meatloaf/disk/d80.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// D80 Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

11
lib/meatloaf/disk/d81.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "d81.h"
/********************************************************
* File implementations
********************************************************/
MStream* D81File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
//Debug_printv("[%s]", url.c_str());
return new D81IStream(containerIstream);
}

113
lib/meatloaf/disk/d81.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// D81 Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

11
lib/meatloaf/disk/d82.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "d82.h"
/********************************************************
* File implementations
********************************************************/
MStream* D82File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new D82IStream(containerIstream);
}

122
lib/meatloaf/disk/d82.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// D82 Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

11
lib/meatloaf/disk/d90.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "d90.h"
/********************************************************
* File implementations
********************************************************/
MStream* D90File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new D90IStream(containerIstream);
}

157
lib/meatloaf/disk/d90.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// D90 Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

5
lib/meatloaf/disk/dhd.h Normal file
View File

@ -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/
//

9
lib/meatloaf/disk/dmk.h Normal file
View File

@ -0,0 +1,9 @@
// .DMK - David M Keils 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
//

12
lib/meatloaf/disk/dnp.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "dnp.h"
/********************************************************
* File implementations
********************************************************/
MStream* DNPFile::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new DNPIStream(containerIstream);
}

91
lib/meatloaf/disk/dnp.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// DNP Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

11
lib/meatloaf/disk/dsk.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "dsk.h"
/********************************************************
* File implementations
********************************************************/
MStream* DSKFile::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
//Debug_printv("[%s]", url.c_str());
return new DSKIStream(containerIstream);
}

125
lib/meatloaf/disk/dsk.h Normal file
View File

@ -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<MStream> is) : D64IStream(is)
{
// DSK Partition Info
std::vector<BlockAllocationMap> 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<MStream> 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 */

6
lib/meatloaf/disk/dxm.h Normal file
View File

@ -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
//

2
lib/meatloaf/disk/f64.h Normal file
View File

@ -0,0 +1,2 @@
// https://cbm8bit.com/8bit/commodore/server/Unrenamed%20Achives/browse/c64/f64

0
lib/meatloaf/disk/fdi.h Normal file
View File

11
lib/meatloaf/disk/g64.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "g64.h"
/********************************************************
* File implementations
********************************************************/
MStream* G64File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
Debug_printv("[%s]", url.c_str());
return new G64IStream(containerIstream);
}

104
lib/meatloaf/disk/g64.h Normal file
View File

@ -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<MStream> 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<MStream> 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 */

0
lib/meatloaf/disk/hdd.h Normal file
View File

6
lib/meatloaf/disk/jvc.h Normal file
View File

@ -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
//

0
lib/meatloaf/disk/m2i.h Normal file
View File

4
lib/meatloaf/disk/nib.h Normal file
View File

@ -0,0 +1,4 @@
// .NIB - Commodore 1541/1571 nibbler disk image
//
// https://github.com/markusC64/nibtools
//

0
lib/meatloaf/disk/p64.h Normal file
View File

5
lib/meatloaf/disk/scp.h Normal file
View File

@ -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
//

4
lib/meatloaf/disk/vdk.h Normal file
View File

@ -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
//

6
lib/meatloaf/disk/woz.h Normal file
View File

@ -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
//

4
lib/meatloaf/disk/x64.h Normal file
View File

@ -0,0 +1,4 @@
// .X64 - Disk Image Format
//
// https://vice-emu.sourceforge.io/vice_17.html#SEC350
//

0
lib/meatloaf/disk/z64.h Normal file
View File

36
lib/meatloaf/file/p00.cpp Normal file
View File

@ -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<MStream> 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<P00IStream>(streamFile->url);
return image->size();
}

119
lib/meatloaf/file/p00.h Normal file
View File

@ -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<MStream> 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<MStream> 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

2
lib/meatloaf/file/prg.h Normal file
View File

@ -0,0 +1,2 @@
// .PRG - RAW CBM PRG file
//

5
lib/meatloaf/file/vsf.h Normal file
View File

@ -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
//

152
lib/meatloaf/iec_pipe.h Normal file
View File

@ -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 */

47
lib/meatloaf/link/url.h Normal file
View File

@ -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<MFile>(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

View File

@ -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

View File

View File

View File

View File

@ -0,0 +1,42 @@
#ifndef MEATLOAF_DEFINES_MKUNIQUE_H
#define MEATLOAF_DEFINES_MKUNIQUE_H
#if __cplusplus < 201703L
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
namespace std {
template<class T> struct _Unique_if {
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]> {
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
#endif
#endif

689
lib/meatloaf/meat_buffer.h Normal file
View File

@ -0,0 +1,689 @@
#ifndef MEATLOAF_BUFFER
#define MEATLOAF_BUFFER
#include <memory>
#include <fstream>
#include "meat_io.h"
#include "../../include/debug.h"
namespace Meat
{
/********************************************************
* C++ Input MFile buffer
********************************************************/
struct my_char_traits : public std::char_traits<char>
{
typedef std::char_traits<char>::int_type int_type;
static constexpr int_type nda() noexcept
{
return static_cast<int>(_MEAT_NO_DATA_AVAIL);
}
};
template <class charT, class traits = my_char_traits>
class mfilebuf : public std::basic_filebuf<charT, traits>
{
std::unique_ptr<MStream> mstream;
std::unique_ptr<MFile> 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 <em>pending sequence</em>.
*
* 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<char>::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<char>::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<char>::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<char>::eof()
: std::char_traits<char>::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
* <em>some effect</em> 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 <typename _CharT, typename _Traits = std::char_traits<_CharT>>
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<char_type, traits_type> __filebuf_type;
typedef std::basic_ios<char_type, traits_type> __ios_type;
typedef std::basic_iostream<char_type, traits_type> __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 <typename _Path, typename _Require = _If_fs_path<_Path>>
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 <typename _Path>
_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<char> 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 */

601
lib/meatloaf/meat_io.cpp Normal file
View File

@ -0,0 +1,601 @@
#include "meat_io.h"
#include <dirent.h>
#include <sys/stat.h>
#include <algorithm>
#include <vector>
#include <sstream>
#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<MFileSystem*> 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<MFile> 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<std::string> 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<std::string>::iterator &begin, std::vector<std::string>::iterator &end, std::vector<std::string>::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<MStream> 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<MFile>(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;
}

223
lib/meatloaf/meat_io.h Normal file
View File

@ -0,0 +1,223 @@
#ifndef MEATLOAF_FILE
#define MEATLOAF_FILE
#include <memory>
#include <string>
#include <vector>
#include <fstream>
//#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<MStream> 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<std::string> &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<MFileSystem*> availableFS;
static MFile* File(std::string name);
static MFile* File(std::shared_ptr<MFile> file);
static MFile* File(MFile* file);
static MFileSystem* scanPathLeft(std::vector<std::string> paths, std::vector<std::string>::iterator &pathIterator);
static std::string existsLocal( std::string path );
static MFileSystem* testScan(std::vector<std::string>::iterator &begin, std::vector<std::string>::iterator &end, std::vector<std::string>::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<MFile> _Single_file;
};
// Creates a unique_ptr<MFile> for a given url
/**
* @brief Creates a unique_ptr<MFile> instance froma given url
* @param url The url to the file.
* @return @c unique_ptr<MFile>
*/
template<class MFile>
typename _Unique_mf::_Single_file
New(std::string url) {
return std::unique_ptr<MFile>(MFSOwner::File(url));
}
/**
* @brief Creates a unique_ptr<MFile> instance froma given url
* @param url The url to the file.
* @return @c unique_ptr<MFile>
*/
template<class MFile>
typename _Unique_mf::_Single_file
New(const char* url) {
return std::unique_ptr<MFile>(MFSOwner::File(std::string(url)));
}
/**
* @brief Creates a unique_ptr<MFile> instance froma given MFile
* @param file The url to the file.
* @return @c unique_ptr<MFile>
*/
template<class MFile>
typename _Unique_mf::_Single_file
New(MFile* mFile) {
return std::unique_ptr<MFile>(MFSOwner::File(mFile->url));
}
/**
* @brief Wraps MFile* into unique_ptr<MFile> so it closes itself as required
* @param file The url to the file.
* @return @c unique_ptr<MFile>
*/
template<class MFile>
typename _Unique_mf::_Single_file
Wrap(MFile* file) {
return std::unique_ptr<MFile>(file);
}
}
#endif // MEATLOAF_FILE

138
lib/meatloaf/meat_stream.h Normal file
View File

@ -0,0 +1,138 @@
#ifndef MEATLOAF_STREAM
#define MEATLOAF_STREAM
#include <unordered_map>
/********************************************************
* 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<std::string, std::string> 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<uint8_t> trackSectorOffset ) { return false; };
};
#endif // MEATLOAF_STREAM

1
lib/meatloaf/network/afp.h Executable file
View File

@ -0,0 +1 @@
// AFP:// - Apple File Protocol

View File

@ -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
//

1
lib/meatloaf/network/fsp.h Executable file
View File

@ -0,0 +1 @@
// FSP:// - File Service Protocol

1
lib/meatloaf/network/ftp.h Executable file
View File

@ -0,0 +1 @@
// FTP:// - File Transfer Protocol

View File

@ -0,0 +1,575 @@
// #include "http.h"
// #include <esp_idf_version.h>
// #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<std::string, std::string> 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<MStream> 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<int(char*, char*)> &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; i<pos; i++) {
// char c;
// int rc = esp_http_client_read(m_http, &c, 1);
// if(rc == -1)
// return false;
// }
// }
// else {
// auto delta = pos-_position;
// // skipping forward let's skip a proper amount of bytes - requires some buffer
// for(int i = 0; i<delta; i++) {
// char c;
// int rc = esp_http_client_read(m_http, &c, 1);
// if(rc == -1)
// return false;
// }
// }
// _position = pos;
// Debug_printv("stream opened[%s]", url.c_str());
// return true;
// }
// else
// return false;
// }
// uint32_t MeatHttpClient::read(uint8_t* buf, uint32_t size) {
// if (_is_open) {
// auto bytesRead= esp_http_client_read(m_http, (char *)buf, size );
// if(bytesRead>0) {
// _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;
// }

194
lib/meatloaf/network/http.h Executable file
View File

@ -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 <esp_http_client.h>
// #include <functional>
// #include <map>
// #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<int(char*, char*)> onHeader = [] (char* key, char* value){
// //Debug_printv("HTTP_EVENT_ON_HEADER, key=%s, value=%s", key, value);
// return 0;
// };
// std::map<std::string, std::string> headers;
// public:
// MeatHttpClient() {
// }
// ~MeatHttpClient() {
// close();
// }
// void setHeaders(std::map<std::string, std::string> &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<int(char*, char*)> &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<MStream> src);
// //void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
// };
// /********************************************************
// * Streams
// ********************************************************/
// class HttpIStream: public MStream {
// std::map<std::string, std::string> headers;
// public:
// HttpIStream(std::string path) {
// url = path;
// };
// HttpIStream(std::string path, std::ios_base::openmode m, std::map<std::string, std::string> &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 */

View File

@ -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;
// }

122
lib/meatloaf/network/ipfs.h Normal file
View File

@ -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

View File

@ -0,0 +1,3 @@
// NC:// - Netcat
// https://nc110.sourceforge.io/
//

Some files were not shown because too many files have changed in this diff Show More