mirror of
https://github.com/idolpx/libMeatloaf.git
synced 2025-12-05 20:28:50 -05:00
initial commit
This commit is contained in:
parent
8320b4f9dd
commit
094e3708e4
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.pio
|
||||
.vscode
|
||||
bin
|
||||
4
CMakeLists.txt
Normal file
4
CMakeLists.txt
Normal 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
40
deploy.py
Normal 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
39
include/README
Normal 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
131
include/ansi_codes.h
Normal 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
209
include/cbm_defines.h
Normal 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
48
include/debug.h
Normal 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_
|
||||
291
lib/EdUrlParser/EdUrlParser.cpp
Normal file
291
lib/EdUrlParser/EdUrlParser.cpp
Normal 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());
|
||||
}
|
||||
55
lib/EdUrlParser/EdUrlParser.h
Normal file
55
lib/EdUrlParser/EdUrlParser.h
Normal 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
71
lib/bus/bus.h
Normal 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
980
lib/bus/iec/iec.cpp
Normal 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
582
lib/bus/iec/iec.h
Normal 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
911
lib/device/iec/drive.cpp
Normal 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
79
lib/device/iec/drive.h
Normal 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();
|
||||
};
|
||||
30
lib/meatloaf/MIOException.h
Normal file
30
lib/meatloaf/MIOException.h
Normal 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
|
||||
364
lib/meatloaf/archive/archive_ml.cpp
Normal file
364
lib/meatloaf/archive/archive_ml.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
181
lib/meatloaf/archive/archive_ml.h
Normal file
181
lib/meatloaf/archive/archive_ml.h
Normal 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
|
||||
4
lib/meatloaf/archive/cvt.h
Normal file
4
lib/meatloaf/archive/cvt.h
Normal 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
|
||||
//
|
||||
5
lib/meatloaf/archive/lbr.h
Normal file
5
lib/meatloaf/archive/lbr.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// .LBR - LiBRary containers
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/LBR.TXT
|
||||
// https://github.com/talas/lbrtool
|
||||
//
|
||||
0
lib/meatloaf/archive/lha.h
Normal file
0
lib/meatloaf/archive/lha.h
Normal file
4
lib/meatloaf/archive/lnx.h
Normal file
4
lib/meatloaf/archive/lnx.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// .LNX - LyNX containers
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/LNX.TXT
|
||||
//
|
||||
0
lib/meatloaf/archive/lzh.h
Normal file
0
lib/meatloaf/archive/lzh.h
Normal file
4
lib/meatloaf/archive/sda.h
Normal file
4
lib/meatloaf/archive/sda.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// .SDA, .ARC - Self-Dissolving Archive
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/SDA.TXT
|
||||
//
|
||||
4
lib/meatloaf/archive/sfx.h
Normal file
4
lib/meatloaf/archive/sfx.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// .SFX, .LHA - Self-Extracting Archive
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/SFX.TXT
|
||||
//
|
||||
4
lib/meatloaf/archive/spy.h
Normal file
4
lib/meatloaf/archive/spy.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// .SPY - SPYne containers
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/SPYNE.TXT
|
||||
//
|
||||
0
lib/meatloaf/archive/wr3.h
Normal file
0
lib/meatloaf/archive/wr3.h
Normal file
4
lib/meatloaf/archive/wra.h
Normal file
4
lib/meatloaf/archive/wra.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// .WRA, .WR3 - Wraptor and Wraptor 3
|
||||
//
|
||||
// https://ist.uwaterloo.ca/~schepers/formats/WRA-WR3.TXT
|
||||
//
|
||||
6
lib/meatloaf/archive/zipcode.h
Normal file
6
lib/meatloaf/archive/zipcode.h
Normal 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
|
||||
//
|
||||
4
lib/meatloaf/cartridge/crt.h
Normal file
4
lib/meatloaf/cartridge/crt.h
Normal 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
|
||||
//
|
||||
4
lib/meatloaf/cartridge/crt/easyfs.h
Normal file
4
lib/meatloaf/cartridge/crt/easyfs.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// EasyFlash 3 Cart File System
|
||||
// https://skoe.de/easyflash/develdocs/
|
||||
// https://bitbucket.org/skoe/easyflash/src/master/
|
||||
//
|
||||
0
lib/meatloaf/cartridge/crt/normal.h
Normal file
0
lib/meatloaf/cartridge/crt/normal.h
Normal file
105
lib/meatloaf/cbm_media.cpp
Normal file
105
lib/meatloaf/cbm_media.cpp
Normal 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
218
lib/meatloaf/cbm_media.h
Normal 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
|
||||
11
lib/meatloaf/container/d8b.cpp
Normal file
11
lib/meatloaf/container/d8b.cpp
Normal 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);
|
||||
}
|
||||
112
lib/meatloaf/container/d8b.h
Normal file
112
lib/meatloaf/container/d8b.h
Normal 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 */
|
||||
11
lib/meatloaf/container/dfi.cpp
Normal file
11
lib/meatloaf/container/dfi.cpp
Normal 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);
|
||||
}
|
||||
204
lib/meatloaf/container/dfi.h
Normal file
204
lib/meatloaf/container/dfi.h
Normal 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 */
|
||||
505
lib/meatloaf/device/flash.cpp
Normal file
505
lib/meatloaf/device/flash.cpp
Normal 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
167
lib/meatloaf/device/flash.h
Normal 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
|
||||
90
lib/meatloaf/device/psram.cpp
Normal file
90
lib/meatloaf/device/psram.cpp
Normal 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
202
lib/meatloaf/device/psram.h
Normal 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
49
lib/meatloaf/device/sd.h
Executable 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
4
lib/meatloaf/disk/d40.h
Normal 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
468
lib/meatloaf/disk/d64.cpp
Normal 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
341
lib/meatloaf/disk/d64.h
Normal 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
11
lib/meatloaf/disk/d71.cpp
Normal 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
118
lib/meatloaf/disk/d71.h
Normal 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
11
lib/meatloaf/disk/d80.cpp
Normal 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
103
lib/meatloaf/disk/d80.h
Normal 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
11
lib/meatloaf/disk/d81.cpp
Normal 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
113
lib/meatloaf/disk/d81.h
Normal 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
11
lib/meatloaf/disk/d82.cpp
Normal 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
122
lib/meatloaf/disk/d82.h
Normal 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
11
lib/meatloaf/disk/d90.cpp
Normal 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
157
lib/meatloaf/disk/d90.h
Normal 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
5
lib/meatloaf/disk/dhd.h
Normal 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
9
lib/meatloaf/disk/dmk.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// .DMK - David M Keil’s disk format. A low-level disk image file format used by TRS-80 Model I & Model III,
|
||||
// Tandy Color Computer, Dragon 32 & 64, and MSX emulators
|
||||
//
|
||||
// https://electrickery.hosting.philpem.me.uk/comp/trs80-4p/dmkeilImages/trstech.htm
|
||||
// https://retrocomputing.stackexchange.com/questions/15282/understanding-the-dmk-disk-image-file-format-used-by-trs-80-emulators
|
||||
// http://cpmarchives.classiccmp.org//trs80/mirrors/www.discover-net.net/~dmkeil/coco/cocotech.htm
|
||||
// https://archive.worldofdragon.org/index.php?title=Tape%5CDisk_Preservation#DMK_File_Format
|
||||
// https://github.com/lutris/openmsx/blob/master/doc/DMK-Format-Details.txt
|
||||
//
|
||||
12
lib/meatloaf/disk/dnp.cpp
Normal file
12
lib/meatloaf/disk/dnp.cpp
Normal 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
91
lib/meatloaf/disk/dnp.h
Normal 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
11
lib/meatloaf/disk/dsk.cpp
Normal 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
125
lib/meatloaf/disk/dsk.h
Normal 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
6
lib/meatloaf/disk/dxm.h
Normal 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
2
lib/meatloaf/disk/f64.h
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
// https://cbm8bit.com/8bit/commodore/server/Unrenamed%20Achives/browse/c64/f64
|
||||
0
lib/meatloaf/disk/fdi.h
Normal file
0
lib/meatloaf/disk/fdi.h
Normal file
11
lib/meatloaf/disk/g64.cpp
Normal file
11
lib/meatloaf/disk/g64.cpp
Normal 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
104
lib/meatloaf/disk/g64.h
Normal 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
0
lib/meatloaf/disk/hdd.h
Normal file
6
lib/meatloaf/disk/jvc.h
Normal file
6
lib/meatloaf/disk/jvc.h
Normal 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
0
lib/meatloaf/disk/m2i.h
Normal file
4
lib/meatloaf/disk/nib.h
Normal file
4
lib/meatloaf/disk/nib.h
Normal 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
0
lib/meatloaf/disk/p64.h
Normal file
5
lib/meatloaf/disk/scp.h
Normal file
5
lib/meatloaf/disk/scp.h
Normal 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
4
lib/meatloaf/disk/vdk.h
Normal 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
6
lib/meatloaf/disk/woz.h
Normal 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
4
lib/meatloaf/disk/x64.h
Normal 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
0
lib/meatloaf/disk/z64.h
Normal file
36
lib/meatloaf/file/p00.cpp
Normal file
36
lib/meatloaf/file/p00.cpp
Normal 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
119
lib/meatloaf/file/p00.h
Normal 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
2
lib/meatloaf/file/prg.h
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// .PRG - RAW CBM PRG file
|
||||
//
|
||||
5
lib/meatloaf/file/vsf.h
Normal file
5
lib/meatloaf/file/vsf.h
Normal 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
152
lib/meatloaf/iec_pipe.h
Normal 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
47
lib/meatloaf/link/url.h
Normal 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
|
||||
47
lib/meatloaf/link/webloc.h
Normal file
47
lib/meatloaf/link/webloc.h
Normal 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
|
||||
0
lib/meatloaf/loaders/koa.h
Normal file
0
lib/meatloaf/loaders/koa.h
Normal file
0
lib/meatloaf/loaders/psid.h
Normal file
0
lib/meatloaf/loaders/psid.h
Normal file
0
lib/meatloaf/loaders/sid.h
Normal file
0
lib/meatloaf/loaders/sid.h
Normal file
42
lib/meatloaf/make_unique.h
Normal file
42
lib/meatloaf/make_unique.h
Normal 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
689
lib/meatloaf/meat_buffer.h
Normal 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
601
lib/meatloaf/meat_io.cpp
Normal 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
223
lib/meatloaf/meat_io.h
Normal 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
138
lib/meatloaf/meat_stream.h
Normal 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
1
lib/meatloaf/network/afp.h
Executable file
|
|
@ -0,0 +1 @@
|
|||
// AFP:// - Apple File Protocol
|
||||
6
lib/meatloaf/network/codenet.h
Normal file
6
lib/meatloaf/network/codenet.h
Normal 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
1
lib/meatloaf/network/fsp.h
Executable file
|
|
@ -0,0 +1 @@
|
|||
// FSP:// - File Service Protocol
|
||||
1
lib/meatloaf/network/ftp.h
Executable file
1
lib/meatloaf/network/ftp.h
Executable file
|
|
@ -0,0 +1 @@
|
|||
// FTP:// - File Transfer Protocol
|
||||
575
lib/meatloaf/network/http.cpp
Normal file
575
lib/meatloaf/network/http.cpp
Normal 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
194
lib/meatloaf/network/http.h
Executable 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 */
|
||||
22
lib/meatloaf/network/ipfs.cpp
Normal file
22
lib/meatloaf/network/ipfs.cpp
Normal 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
122
lib/meatloaf/network/ipfs.h
Normal 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
|
||||
3
lib/meatloaf/network/nc.h
Normal file
3
lib/meatloaf/network/nc.h
Normal 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
Loading…
Reference in New Issue
Block a user