Compare commits

...

31 Commits

Author SHA1 Message Date
David Given
979b550178 Looks like our string_view fix hasn't worked --- tweak. 2025-08-21 01:19:19 +02:00
David Given
9062a531f3 Migrate the 40track etc extension configs to actual options. Add the
ability to have --group=value options to make this cleaner.
2025-08-21 00:53:50 +02:00
David Given
e2a6fbcf3c Update a few places which used -c for other purposes. 2025-08-20 21:30:06 +02:00
David Given
ec16931f3a Update documentation for -c. 2025-08-20 21:23:02 +02:00
David Given
0ec0ca7495 Config files are now specified with -c, rather than via filename
arguments, because otherwise you get really unhelpful error messages
when you get things wrong.
2025-08-20 21:19:34 +02:00
David Given
51fa7c9371 Fix broken link.
Closes: #799
2025-08-20 00:23:21 +02:00
David Given
6c69f10fe7 Merge pull request #821 from davidgiven/protobuf
Expose the .app on OSX (in a zipfile).
2025-08-20 00:17:43 +02:00
David Given
206e85a356 Expose the .app on OSX (in a zipfile).
Closes: #800
2025-08-20 00:02:43 +02:00
David Given
8d7dd4867b Merge pull request #820 from davidgiven/protobuf
Apply the fix from #811 to make everything build against Protobuf 31.
2025-08-19 23:43:26 +02:00
David Given
d1524f78fb Apply the fix from #811 to make everything build against Protobuf 31. 2025-08-19 23:28:19 +02:00
David Given
b26735d520 Merge pull request #819 from davidgiven/fl2
Add some flux file manipulation tools.
2025-08-19 22:58:05 +02:00
David Given
603baee777 Fix a subtle bug that was causing misparsing of indexed fields on OSX. I hate
C++.
2025-08-19 22:43:45 +02:00
David Given
e105b7f498 Merge from master. 2025-08-19 20:13:41 +02:00
David Given
bb3fbccb50 Merge pull request #818 from davidgiven/convert
Add a fe-convert (plus all the necessary backend work).
2025-08-19 19:59:12 +02:00
David Given
c8edcd963d Merge pull request #817 from davidgiven/ab
Update ab.
2025-08-19 01:32:40 +02:00
David Given
3b60cdc707 Remove the -j from the build scripts for OSX. 2025-08-19 01:15:47 +02:00
David Given
ea061d65c9 Update ab. 2025-08-19 01:14:33 +02:00
David Given
da64c0237f Update documentation. 2025-08-19 01:11:40 +02:00
David Given
d2b1602881 Add a working fluxfile cp. 2025-08-19 00:55:53 +02:00
David Given
1afd45068c Merge. 2025-08-19 00:18:59 +02:00
David Given
f01b30e112 Make fluxfile rm work. 2025-08-19 00:18:47 +02:00
David Given
b5f7fbe14e Finally come up with a fluxfile ls I can live with. 2025-08-18 23:59:57 +02:00
David Given
996fdbc0f5 More overhauling of the proto layer; fluxfile ls now works. 2025-08-18 00:37:42 +02:00
David Given
9ff3e3b42a Finally make the getters and setters work with repeated fields. 2025-08-17 23:04:14 +02:00
David Given
0a5604521e Merge in fluxfile stuff. 2025-08-17 21:12:27 +02:00
David Given
0419df4b2d Another archival checkin... 2025-08-13 23:00:08 +02:00
David Given
70bdcd0978 Non-functioning archival checkin. 2025-08-12 20:31:54 +01:00
David Given
022df995aa Update for newer C++. 2025-08-11 16:21:03 +01:00
David Given
dcbe7ec41d Raw import of alphanum. 2025-08-11 16:14:27 +01:00
David Given
df4d27eefe Better support for repeated fields in the config language. Add a helper
for showing all config fields in a proto.
2025-08-10 22:22:58 +01:00
David Given
8f233f55e9 Add fluxfile ls. 2025-07-28 23:20:41 +01:00
78 changed files with 1746 additions and 589 deletions

View File

@@ -65,12 +65,14 @@ jobs:
run: |
brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: gmake -C fluxengine -j2
run: gmake -C fluxengine
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}.fluxengine.${{ runner.arch }}.pkg
path: fluxengine/FluxEngine.pkg
path: |
fluxengine/FluxEngine.pkg
fluxengine/FluxEngine.app.zip
build-windows:
runs-on: windows-latest

View File

@@ -97,8 +97,9 @@ jobs:
- name: make
run: |
gmake -j2
gmake
mv FluxEngine.pkg FluxEngine-${{ runner.arch }}.pkg
mv FluxEngine.app.zip FluxEngine-${{ runner.arch }}.app.zip
- name: tag
uses: EndBug/latest-tag@latest
@@ -115,6 +116,7 @@ jobs:
tag: dev
assets: |
FluxEngine-${{ runner.arch }}.pkg
FluxEngine-${{ runner.arch }}.app.zip
fail-if-no-assets: false
- name: release
@@ -123,6 +125,7 @@ jobs:
name: Development build ${{ env.RELEASE_DATE }}
files: |
FluxEngine-${{ runner.arch }}.pkg
FluxEngine-${{ runner.arch }}.app.zip
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -263,6 +263,11 @@ As an exception, `dep/lexy` contains a partial copy of the lexy package, written
by foonathen@github, taken from https://github.com/foonathan/lexy. It is BSL 1.0
licensed. Please see the contents of the directory for the full text.
As an exception, `dep/alphanum` contains a copy of the alphanum package,
written by Dave Koelle, taken from
https://web.archive.org/web/20210207124255/davekoelle.com/alphanum.html. It is
MIT licensed. Please see the source for the full text.
__Important:__ Because of all these exceptions, if you distribute the
FluxEngine package as a whole, you must comply with the terms of _all_ of the
licensing terms. This means that __effectively the FluxEngine package is

View File

@@ -10,6 +10,7 @@ import re
# Hack for building on Fedora/WSL; executables get the .exe extension,
# build the build system detects it as Linux.
import build.toolchain
toolchain.Toolchain.EXE = "$(EXT)"
package(name="protobuf_lib", package="protobuf")
@@ -27,7 +28,7 @@ else:
("acorndfs", "", "--200"),
("agat", "", ""),
("amiga", "", ""),
("apple2", "", "--140 40track_drive"),
("apple2", "", "--140 --drivetype=40"),
("atarist", "", "--360"),
("atarist", "", "--370"),
("atarist", "", "--400"),
@@ -37,17 +38,17 @@ else:
("atarist", "", "--800"),
("atarist", "", "--820"),
("bk", "", ""),
("brother", "", "--120 40track_drive"),
("brother", "", "--120 --drivetype=40"),
("brother", "", "--240"),
(
"commodore",
"scripts/commodore1541_test.textpb",
"--171 40track_drive",
"--171 --drivetype=40",
),
(
"commodore",
"scripts/commodore1541_test.textpb",
"--192 40track_drive",
"--192 --drivetype=40",
),
("commodore", "", "--800"),
("commodore", "", "--1620"),
@@ -59,17 +60,17 @@ else:
("ibm", "", "--1232"),
("ibm", "", "--1440"),
("ibm", "", "--1680"),
("ibm", "", "--180 40track_drive"),
("ibm", "", "--160 40track_drive"),
("ibm", "", "--320 40track_drive"),
("ibm", "", "--360 40track_drive"),
("ibm", "", "--180 --drivetype=40"),
("ibm", "", "--160 --drivetype=40"),
("ibm", "", "--320 --drivetype=40"),
("ibm", "", "--360 --drivetype=40"),
("ibm", "", "--720_96"),
("ibm", "", "--720_135"),
("mac", "scripts/mac400_test.textpb", "--400"),
("mac", "scripts/mac800_test.textpb", "--800"),
("n88basic", "", ""),
("rx50", "", ""),
("tartu", "", "--390 40track_drive"),
("tartu", "", "--390 --drivetype=40"),
("tartu", "", "--780"),
("tids990", "", ""),
("victor9k", "", "--612"),
@@ -108,6 +109,13 @@ export(
"brother240tool$(EXT)": "tools+brother240tool",
"upgrade-flux-file$(EXT)": "tools+upgrade-flux-file",
}
| ({"FluxEngine.pkg": "src/gui+fluxengine_pkg"} if config.osx else {}),
| (
{
"FluxEngine.pkg": "src/gui+fluxengine_pkg",
"FluxEngine.app.zip": "src/gui+fluxengine_app_zip",
}
if config.osx
else {}
),
deps=["tests", "src/formats+docs", "scripts+mkdocindex"] + corpustests,
)

View File

@@ -87,7 +87,12 @@ $(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES) &:
include $(OBJ)/build.mk
MAKEFLAGS += -r -j$(shell nproc)
ifeq ($(OSX),yes)
MAKEFLAGS += -r -j$(shell sysctl -n hw.logicalcpu)
else
MAKEFLAGS += -r -j$(shell nproc)
endif
.DELETE_ON_ERROR:
.PHONY: update-ab

2
dep/alphanum/UPSTREAM.md Normal file
View File

@@ -0,0 +1,2 @@
Downloaded from:
https://web.archive.org/web/20210918044134/http://davekoelle.com/files/alphanum.hpp

450
dep/alphanum/alphanum.h Normal file
View File

@@ -0,0 +1,450 @@
#ifndef ALPHANUM__HPP
#define ALPHANUM__HPP
/*
Released under the MIT License - https://opensource.org/licenses/MIT
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* $Header: /code/doj/alphanum.hpp,v 1.3 2008/01/28 23:06:47 doj Exp $ */
#include <cassert>
#include <functional>
#include <string>
#include <sstream>
#ifdef ALPHANUM_LOCALE
#include <cctype>
#endif
#ifdef DOJDEBUG
#include <iostream>
#include <typeinfo>
#endif
// TODO: make comparison with hexadecimal numbers. Extend the alphanum_comp() function by traits to choose between decimal and hexadecimal.
namespace doj
{
// anonymous namespace for functions we use internally. But if you
// are coding in C, you can use alphanum_impl() directly, since it
// uses not C++ features.
namespace {
// if you want to honour the locale settings for detecting digit
// characters, you should define ALPHANUM_LOCALE
#ifdef ALPHANUM_LOCALE
/** wrapper function for ::isdigit() */
bool alphanum_isdigit(int c)
{
return isdigit(c);
}
#else
/** this function does not consider the current locale and only
works with ASCII digits.
@return true if c is a digit character
*/
bool alphanum_isdigit(const char c)
{
return c>='0' && c<='9';
}
#endif
/**
compare l and r with strcmp() semantics, but using
the "Alphanum Algorithm". This function is designed to read
through the l and r strings only one time, for
maximum performance. It does not allocate memory for
substrings. It can either use the C-library functions isdigit()
and atoi() to honour your locale settings, when recognizing
digit characters when you "#define ALPHANUM_LOCALE=1" or use
it's own digit character handling which only works with ASCII
digit characters, but provides better performance.
@param l NULL-terminated C-style string
@param r NULL-terminated C-style string
@return negative if l<r, 0 if l equals r, positive if l>r
*/
int alphanum_impl(const char *l, const char *r)
{
enum mode_t { STRING, NUMBER } mode=STRING;
while(*l && *r)
{
if(mode == STRING)
{
char l_char, r_char;
while((l_char=*l) && (r_char=*r))
{
// check if this are digit characters
const bool l_digit=alphanum_isdigit(l_char), r_digit=alphanum_isdigit(r_char);
// if both characters are digits, we continue in NUMBER mode
if(l_digit && r_digit)
{
mode=NUMBER;
break;
}
// if only the left character is a digit, we have a result
if(l_digit) return -1;
// if only the right character is a digit, we have a result
if(r_digit) return +1;
// compute the difference of both characters
const int diff=l_char - r_char;
// if they differ we have a result
if(diff != 0) return diff;
// otherwise process the next characters
++l;
++r;
}
}
else // mode==NUMBER
{
#ifdef ALPHANUM_LOCALE
// get the left number
char *end;
unsigned long l_int=strtoul(l, &end, 0);
l=end;
// get the right number
unsigned long r_int=strtoul(r, &end, 0);
r=end;
#else
// get the left number
unsigned long l_int=0;
while(*l && alphanum_isdigit(*l))
{
// TODO: this can overflow
l_int=l_int*10 + *l-'0';
++l;
}
// get the right number
unsigned long r_int=0;
while(*r && alphanum_isdigit(*r))
{
// TODO: this can overflow
r_int=r_int*10 + *r-'0';
++r;
}
#endif
// if the difference is not equal to zero, we have a comparison result
const long diff=l_int-r_int;
if(diff != 0)
return diff;
// otherwise we process the next substring in STRING mode
mode=STRING;
}
}
if(*r) return -1;
if(*l) return +1;
return 0;
}
}
/**
Compare left and right with the same semantics as strcmp(), but with the
"Alphanum Algorithm" which produces more human-friendly
results. The classes lT and rT must implement "std::ostream
operator<< (std::ostream&, const Ty&)".
@return negative if left<right, 0 if left==right, positive if left>right.
*/
template <typename lT, typename rT>
int alphanum_comp(const lT& left, const rT& right)
{
#ifdef DOJDEBUG
std::clog << "alphanum_comp<" << typeid(left).name() << "," << typeid(right).name() << "> " << left << "," << right << std::endl;
#endif
std::ostringstream l; l << left;
std::ostringstream r; r << right;
return alphanum_impl(l.str().c_str(), r.str().c_str());
}
/**
Compare l and r with the same semantics as strcmp(), but with
the "Alphanum Algorithm" which produces more human-friendly
results.
@return negative if l<r, 0 if l==r, positive if l>r.
*/
template <>
int alphanum_comp<std::string>(const std::string& l, const std::string& r)
{
#ifdef DOJDEBUG
std::clog << "alphanum_comp<std::string,std::string> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l.c_str(), r.c_str());
}
////////////////////////////////////////////////////////////////////////////
// now follow a lot of overloaded alphanum_comp() functions to get a
// direct call to alphanum_impl() upon the various combinations of c
// and c++ strings.
/**
Compare l and r with the same semantics as strcmp(), but with
the "Alphanum Algorithm" which produces more human-friendly
results.
@return negative if l<r, 0 if l==r, positive if l>r.
*/
int alphanum_comp(char* l, char* r)
{
assert(l);
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<char*,char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r);
}
int alphanum_comp(const char* l, const char* r)
{
assert(l);
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<const char*,const char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r);
}
int alphanum_comp(char* l, const char* r)
{
assert(l);
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<char*,const char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r);
}
int alphanum_comp(const char* l, char* r)
{
assert(l);
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<const char*,char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r);
}
int alphanum_comp(const std::string& l, char* r)
{
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<std::string,char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l.c_str(), r);
}
int alphanum_comp(char* l, const std::string& r)
{
assert(l);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<char*,std::string> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r.c_str());
}
int alphanum_comp(const std::string& l, const char* r)
{
assert(r);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<std::string,const char*> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l.c_str(), r);
}
int alphanum_comp(const char* l, const std::string& r)
{
assert(l);
#ifdef DOJDEBUG
std::clog << "alphanum_comp<const char*,std::string> " << l << "," << r << std::endl;
#endif
return alphanum_impl(l, r.c_str());
}
////////////////////////////////////////////////////////////////////////////
/**
Functor class to compare two objects with the "Alphanum
Algorithm". If the objects are no std::string, they must
implement "std::ostream operator<< (std::ostream&, const Ty&)".
*/
template<class Ty>
struct alphanum_less
{
bool operator()(const Ty& left, const Ty& right) const
{
return alphanum_comp(left, right) < 0;
}
};
}
#ifdef TESTMAIN
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <set>
#include <vector>
int main()
{
// testcases for the algorithm
assert(doj::alphanum_comp("","") == 0);
assert(doj::alphanum_comp("","a") < 0);
assert(doj::alphanum_comp("a","") > 0);
assert(doj::alphanum_comp("a","a") == 0);
assert(doj::alphanum_comp("","9") < 0);
assert(doj::alphanum_comp("9","") > 0);
assert(doj::alphanum_comp("1","1") == 0);
assert(doj::alphanum_comp("1","2") < 0);
assert(doj::alphanum_comp("3","2") > 0);
assert(doj::alphanum_comp("a1","a1") == 0);
assert(doj::alphanum_comp("a1","a2") < 0);
assert(doj::alphanum_comp("a2","a1") > 0);
assert(doj::alphanum_comp("a1a2","a1a3") < 0);
assert(doj::alphanum_comp("a1a2","a1a0") > 0);
assert(doj::alphanum_comp("134","122") > 0);
assert(doj::alphanum_comp("12a3","12a3") == 0);
assert(doj::alphanum_comp("12a1","12a0") > 0);
assert(doj::alphanum_comp("12a1","12a2") < 0);
assert(doj::alphanum_comp("a","aa") < 0);
assert(doj::alphanum_comp("aaa","aa") > 0);
assert(doj::alphanum_comp("Alpha 2","Alpha 2") == 0);
assert(doj::alphanum_comp("Alpha 2","Alpha 2A") < 0);
assert(doj::alphanum_comp("Alpha 2 B","Alpha 2") > 0);
assert(doj::alphanum_comp(1,1) == 0);
assert(doj::alphanum_comp(1,2) < 0);
assert(doj::alphanum_comp(2,1) > 0);
assert(doj::alphanum_comp(1.2,3.14) < 0);
assert(doj::alphanum_comp(3.14,2.71) > 0);
assert(doj::alphanum_comp(true,true) == 0);
assert(doj::alphanum_comp(true,false) > 0);
assert(doj::alphanum_comp(false,true) < 0);
std::string str("Alpha 2");
assert(doj::alphanum_comp(str,"Alpha 2") == 0);
assert(doj::alphanum_comp(str,"Alpha 2A") < 0);
assert(doj::alphanum_comp("Alpha 2 B",str) > 0);
assert(doj::alphanum_comp(str,strdup("Alpha 2")) == 0);
assert(doj::alphanum_comp(str,strdup("Alpha 2A")) < 0);
assert(doj::alphanum_comp(strdup("Alpha 2 B"),str) > 0);
#if 1
// show usage of the comparison functor with a set
std::set<std::string, doj::alphanum_less<std::string> > s;
s.insert("Xiph Xlater 58");
s.insert("Xiph Xlater 5000");
s.insert("Xiph Xlater 500");
s.insert("Xiph Xlater 50");
s.insert("Xiph Xlater 5");
s.insert("Xiph Xlater 40");
s.insert("Xiph Xlater 300");
s.insert("Xiph Xlater 2000");
s.insert("Xiph Xlater 10000");
s.insert("QRS-62F Intrinsia Machine");
s.insert("QRS-62 Intrinsia Machine");
s.insert("QRS-60F Intrinsia Machine");
s.insert("QRS-60 Intrinsia Machine");
s.insert("Callisto Morphamax 7000 SE2");
s.insert("Callisto Morphamax 7000 SE");
s.insert("Callisto Morphamax 7000");
s.insert("Callisto Morphamax 700");
s.insert("Callisto Morphamax 600");
s.insert("Callisto Morphamax 5000");
s.insert("Callisto Morphamax 500");
s.insert("Callisto Morphamax");
s.insert("Alpha 2A-900");
s.insert("Alpha 2A-8000");
s.insert("Alpha 2A");
s.insert("Alpha 200");
s.insert("Alpha 2");
s.insert("Alpha 100");
s.insert("Allegia 60 Clasteron");
s.insert("Allegia 52 Clasteron");
s.insert("Allegia 51B Clasteron");
s.insert("Allegia 51 Clasteron");
s.insert("Allegia 500 Clasteron");
s.insert("Allegia 50 Clasteron");
s.insert("40X Radonius");
s.insert("30X Radonius");
s.insert("20X Radonius Prime");
s.insert("20X Radonius");
s.insert("200X Radonius");
s.insert("10X Radonius");
s.insert("1000X Radonius Maximus");
// print sorted set to cout
std::copy(s.begin(), s.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
// show usage of comparision functor with a map
typedef std::map<std::string, int, doj::alphanum_less<std::string> > m_t;
m_t m;
m["z1.doc"]=1;
m["z10.doc"]=2;
m["z100.doc"]=3;
m["z101.doc"]=4;
m["z102.doc"]=5;
m["z11.doc"]=6;
m["z12.doc"]=7;
m["z13.doc"]=8;
m["z14.doc"]=9;
m["z15.doc"]=10;
m["z16.doc"]=11;
m["z17.doc"]=12;
m["z18.doc"]=13;
m["z19.doc"]=14;
m["z2.doc"]=15;
m["z20.doc"]=16;
m["z3.doc"]=17;
m["z4.doc"]=18;
m["z5.doc"]=19;
m["z6.doc"]=20;
m["z7.doc"]=21;
m["z8.doc"]=22;
m["z9.doc"]=23;
// print sorted map to cout
for(m_t::iterator i=m.begin(); i!=m.end(); ++i)
std::cout << i->first << '\t' << i->second << std::endl;
// show usage of comparison functor with an STL algorithm on a vector
std::vector<std::string> v;
// vector contents are reversed sorted contents of the old set
std::copy(s.rbegin(), s.rend(), std::back_inserter(v));
// now sort the vector with the algorithm
std::sort(v.begin(), v.end(), doj::alphanum_less<std::string>());
// and print the vector to cout
std::copy(v.begin(), v.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
#endif
return 0;
}
#endif
#endif

8
dep/alphanum/build.py Normal file
View File

@@ -0,0 +1,8 @@
from build.c import clibrary
clibrary(
name="alphanum",
srcs=[],
hdrs={"dep/alphanum/alphanum.h": "./alphanum.h"},
)

View File

@@ -31,9 +31,9 @@ they might require nudging as the side order can't be reliably autodetected.
To read:
- `fluxengine read acornadfs --160 -s drive:0 -o acornadfs.img`
- `fluxengine read acornadfs --320 -s drive:0 -o acornadfs.img`
- `fluxengine read acornadfs --640 -s drive:0 -o acornadfs.img`
- `fluxengine read acornadfs --800 -s drive:0 -o acornadfs.img`
- `fluxengine read acornadfs --1600 -s drive:0 -o acornadfs.img`
- `fluxengine read -c acornadfs --160 -s drive:0 -o acornadfs.img`
- `fluxengine read -c acornadfs --320 -s drive:0 -o acornadfs.img`
- `fluxengine read -c acornadfs --640 -s drive:0 -o acornadfs.img`
- `fluxengine read -c acornadfs --800 -s drive:0 -o acornadfs.img`
- `fluxengine read -c acornadfs --1600 -s drive:0 -o acornadfs.img`

View File

@@ -24,13 +24,13 @@ requires a bit of fiddling as they have the same tracks on twice.
To read:
- `fluxengine read acorndfs --100 -s drive:0 -o acorndfs.img`
- `fluxengine read acorndfs --200 -s drive:0 -o acorndfs.img`
- `fluxengine read -c acorndfs --100 -s drive:0 -o acorndfs.img`
- `fluxengine read -c acorndfs --200 -s drive:0 -o acorndfs.img`
To write:
- `fluxengine write acorndfs --100 -d drive:0 -i acorndfs.img`
- `fluxengine write acorndfs --200 -d drive:0 -i acorndfs.img`
- `fluxengine write -c acorndfs --100 -d drive:0 -i acorndfs.img`
- `fluxengine write -c acorndfs --200 -d drive:0 -i acorndfs.img`
## References

View File

@@ -37,7 +37,7 @@ based on what looks right. If anyone knows _anything_ about these disks,
To read:
- `fluxengine read aeslanier -s drive:0 -o aeslanier.img`
- `fluxengine read -c aeslanier -s drive:0 -o aeslanier.img`
## References

View File

@@ -20,11 +20,11 @@ profile.
To read:
- `fluxengine read agat -s drive:0 -o agat.img`
- `fluxengine read -c agat -s drive:0 -o agat.img`
To write:
- `fluxengine write agat -d drive:0 -i agat.img`
- `fluxengine write -c agat -d drive:0 -i agat.img`
## References

View File

@@ -26,11 +26,11 @@ distinctly subpar and not particularly good at detecting errors.
To read:
- `fluxengine read amiga -s drive:0 -o amiga.adf`
- `fluxengine read -c amiga -s drive:0 -o amiga.adf`
To write:
- `fluxengine write amiga -d drive:0 -i amiga.adf`
- `fluxengine write -c amiga -d drive:0 -i amiga.adf`
## References

View File

@@ -43,8 +43,8 @@ kayinfo.lbr
To read:
- `fluxengine read ampro --400 -s drive:0 -o ampro.img`
- `fluxengine read ampro --800 -s drive:0 -o ampro.img`
- `fluxengine read -c ampro --400 -s drive:0 -o ampro.img`
- `fluxengine read -c ampro --800 -s drive:0 -o ampro.img`
## References

View File

@@ -58,13 +58,13 @@ volume.
To read:
- `fluxengine read apple2 --140 -s drive:0 -o apple2.img`
- `fluxengine read apple2 --640 -s drive:0 -o apple2.img`
- `fluxengine read -c apple2 --140 -s drive:0 -o apple2.img`
- `fluxengine read -c apple2 --640 -s drive:0 -o apple2.img`
To write:
- `fluxengine write apple2 --140 -d drive:0 -i apple2.img`
- `fluxengine write apple2 --640 -d drive:0 -i apple2.img`
- `fluxengine write -c apple2 --140 -d drive:0 -i apple2.img`
- `fluxengine write -c apple2 --640 -d drive:0 -i apple2.img`
## References

View File

@@ -29,25 +29,25 @@ Be aware that many PC drives (including mine) won't do the 82 track formats.
To read:
- `fluxengine read atarist --360 -s drive:0 -o atarist.img`
- `fluxengine read atarist --370 -s drive:0 -o atarist.img`
- `fluxengine read atarist --400 -s drive:0 -o atarist.img`
- `fluxengine read atarist --410 -s drive:0 -o atarist.img`
- `fluxengine read atarist --720 -s drive:0 -o atarist.img`
- `fluxengine read atarist --740 -s drive:0 -o atarist.img`
- `fluxengine read atarist --800 -s drive:0 -o atarist.img`
- `fluxengine read atarist --820 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --360 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --370 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --400 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --410 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --720 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --740 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --800 -s drive:0 -o atarist.img`
- `fluxengine read -c atarist --820 -s drive:0 -o atarist.img`
To write:
- `fluxengine write atarist --360 -d drive:0 -i atarist.img`
- `fluxengine write atarist --370 -d drive:0 -i atarist.img`
- `fluxengine write atarist --400 -d drive:0 -i atarist.img`
- `fluxengine write atarist --410 -d drive:0 -i atarist.img`
- `fluxengine write atarist --720 -d drive:0 -i atarist.img`
- `fluxengine write atarist --740 -d drive:0 -i atarist.img`
- `fluxengine write atarist --800 -d drive:0 -i atarist.img`
- `fluxengine write atarist --820 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --360 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --370 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --400 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --410 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --720 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --740 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --800 -d drive:0 -i atarist.img`
- `fluxengine write -c atarist --820 -d drive:0 -i atarist.img`
## References

View File

@@ -22,9 +22,9 @@ on what was available at the time, with the same format on both.
To read:
- `fluxengine read bk -s drive:0 -o bk800.img`
- `fluxengine read -c bk -s drive:0 -o bk800.img`
To write:
- `fluxengine write bk -d drive:0 -i bk800.img`
- `fluxengine write -c bk -d drive:0 -i bk800.img`

View File

@@ -44,13 +44,13 @@ investigate.
To read:
- `fluxengine read brother --120 -s drive:0 -o brother.img`
- `fluxengine read brother --240 -s drive:0 -o brother.img`
- `fluxengine read -c brother --120 -s drive:0 -o brother.img`
- `fluxengine read -c brother --240 -s drive:0 -o brother.img`
To write:
- `fluxengine write brother --120 -d drive:0 -i brother.img`
- `fluxengine write brother --240 -d drive:0 -i brother.img`
- `fluxengine write -c brother --120 -d drive:0 -i brother.img`
- `fluxengine write -c brother --240 -d drive:0 -i brother.img`
Dealing with misaligned disks
-----------------------------

View File

@@ -54,18 +54,18 @@ A CMD FD2000 disk (a popular third-party Commodore disk drive)
To read:
- `fluxengine read commodore --171 -s drive:0 -o commodore.d64`
- `fluxengine read commodore --192 -s drive:0 -o commodore.d64`
- `fluxengine read commodore --800 -s drive:0 -o commodore.d64`
- `fluxengine read commodore --1042 -s drive:0 -o commodore.d64`
- `fluxengine read commodore --1620 -s drive:0 -o commodore.d64`
- `fluxengine read -c commodore --171 -s drive:0 -o commodore.d64`
- `fluxengine read -c commodore --192 -s drive:0 -o commodore.d64`
- `fluxengine read -c commodore --800 -s drive:0 -o commodore.d64`
- `fluxengine read -c commodore --1042 -s drive:0 -o commodore.d64`
- `fluxengine read -c commodore --1620 -s drive:0 -o commodore.d64`
To write:
- `fluxengine write commodore --171 -d drive:0 -i commodore.d64`
- `fluxengine write commodore --192 -d drive:0 -i commodore.d64`
- `fluxengine write commodore --800 -d drive:0 -i commodore.d64`
- `fluxengine write commodore --1620 -d drive:0 -i commodore.d64`
- `fluxengine write -c commodore --171 -d drive:0 -i commodore.d64`
- `fluxengine write -c commodore --192 -d drive:0 -i commodore.d64`
- `fluxengine write -c commodore --800 -d drive:0 -i commodore.d64`
- `fluxengine write -c commodore --1620 -d drive:0 -i commodore.d64`
## References

View File

@@ -33,7 +33,7 @@ images.
To read:
- `fluxengine read eco1 -s drive:0 -o eco1.img`
- `fluxengine read -c eco1 -s drive:0 -o eco1.img`
## References

View File

@@ -15,5 +15,5 @@ format itself is yet another IBM scheme variant.
To read:
- `fluxengine read epsonpf10 -s drive:0 -o epsonpf10.img`
- `fluxengine read -c epsonpf10 -s drive:0 -o epsonpf10.img`

View File

@@ -36,7 +36,7 @@ touch](https://github.com/davidgiven/fluxengine/issues/new).
To read:
- `fluxengine read f85 -s drive:0 -o f85.img`
- `fluxengine read -c f85 -s drive:0 -o f85.img`
## References

View File

@@ -30,7 +30,7 @@ I don't have access to one of those disks.
To read:
- `fluxengine read fb100 -s drive:0 -o fb100.img`
- `fluxengine read -c fb100 -s drive:0 -o fb100.img`
## References

View File

@@ -23,17 +23,17 @@ encoding scheme.
To read:
- `fluxengine read hplif --264 -s drive:0 -o hplif.img`
- `fluxengine read hplif --608 -s drive:0 -o hplif.img`
- `fluxengine read hplif --616 -s drive:0 -o hplif.img`
- `fluxengine read hplif --770 -s drive:0 -o hplif.img`
- `fluxengine read -c hplif --264 -s drive:0 -o hplif.img`
- `fluxengine read -c hplif --608 -s drive:0 -o hplif.img`
- `fluxengine read -c hplif --616 -s drive:0 -o hplif.img`
- `fluxengine read -c hplif --770 -s drive:0 -o hplif.img`
To write:
- `fluxengine write hplif --264 -d drive:0 -i hplif.img`
- `fluxengine write hplif --608 -d drive:0 -i hplif.img`
- `fluxengine write hplif --616 -d drive:0 -i hplif.img`
- `fluxengine write hplif --770 -d drive:0 -i hplif.img`
- `fluxengine write -c hplif --264 -d drive:0 -i hplif.img`
- `fluxengine write -c hplif --608 -d drive:0 -i hplif.img`
- `fluxengine write -c hplif --616 -d drive:0 -i hplif.img`
- `fluxengine write -c hplif --770 -d drive:0 -i hplif.img`
## References

View File

@@ -55,30 +55,30 @@ image format. FluxEngine will use these parameters.
To read:
- `fluxengine read ibm --auto -s drive:0 -o ibm.img`
- `fluxengine read ibm --160 -s drive:0 -o ibm.img`
- `fluxengine read ibm --180 -s drive:0 -o ibm.img`
- `fluxengine read ibm --320 -s drive:0 -o ibm.img`
- `fluxengine read ibm --360 -s drive:0 -o ibm.img`
- `fluxengine read ibm --720_96 -s drive:0 -o ibm.img`
- `fluxengine read ibm --720_135 -s drive:0 -o ibm.img`
- `fluxengine read ibm --1200 -s drive:0 -o ibm.img`
- `fluxengine read ibm --1232 -s drive:0 -o ibm.img`
- `fluxengine read ibm --1440 -s drive:0 -o ibm.img`
- `fluxengine read ibm --1680 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --auto -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --160 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --180 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --320 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --360 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --720_96 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --720_135 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --1200 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --1232 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --1440 -s drive:0 -o ibm.img`
- `fluxengine read -c ibm --1680 -s drive:0 -o ibm.img`
To write:
- `fluxengine write ibm --160 -d drive:0 -i ibm.img`
- `fluxengine write ibm --180 -d drive:0 -i ibm.img`
- `fluxengine write ibm --320 -d drive:0 -i ibm.img`
- `fluxengine write ibm --360 -d drive:0 -i ibm.img`
- `fluxengine write ibm --720_96 -d drive:0 -i ibm.img`
- `fluxengine write ibm --720_135 -d drive:0 -i ibm.img`
- `fluxengine write ibm --1200 -d drive:0 -i ibm.img`
- `fluxengine write ibm --1232 -d drive:0 -i ibm.img`
- `fluxengine write ibm --1440 -d drive:0 -i ibm.img`
- `fluxengine write ibm --1680 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --160 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --180 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --320 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --360 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --720_96 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --720_135 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --1200 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --1232 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --1440 -d drive:0 -i ibm.img`
- `fluxengine write -c ibm --1680 -d drive:0 -i ibm.img`
Mixed-format disks
------------------

View File

@@ -15,5 +15,5 @@ track! Other than that it's another IBM scheme variation.
To read:
- `fluxengine read icl30 -s drive:0 -o icl30.img`
- `fluxengine read -c icl30 -s drive:0 -o icl30.img`

View File

@@ -47,13 +47,13 @@ standard for disk images is to omit it. If you want them, specify that you want
To read:
- `fluxengine read mac --400 -s drive:0 -o mac.dsk`
- `fluxengine read mac --800 -s drive:0 -o mac.dsk`
- `fluxengine read -c mac --400 -s drive:0 -o mac.dsk`
- `fluxengine read -c mac --800 -s drive:0 -o mac.dsk`
To write:
- `fluxengine write mac --400 -d drive:0 -i mac.dsk`
- `fluxengine write mac --800 -d drive:0 -i mac.dsk`
- `fluxengine write -c mac --400 -d drive:0 -i mac.dsk`
- `fluxengine write -c mac --800 -d drive:0 -i mac.dsk`
## References

View File

@@ -63,11 +63,11 @@ need to apply extra options to change the format if desired.
To read:
- `fluxengine read micropolis -s drive:0 -o micropolis.img`
- `fluxengine read -c micropolis -s drive:0 -o micropolis.img`
To write:
- `fluxengine write micropolis -d drive:0 -i micropolis.img`
- `fluxengine write -c micropolis -d drive:0 -i micropolis.img`
## References

View File

@@ -52,10 +52,10 @@ Words are all stored little-endian.
To read:
- `fluxengine read mx --110 -s drive:0 -o mx.img`
- `fluxengine read mx --220ds -s drive:0 -o mx.img`
- `fluxengine read mx --220ss -s drive:0 -o mx.img`
- `fluxengine read mx --440 -s drive:0 -o mx.img`
- `fluxengine read -c mx --110 -s drive:0 -o mx.img`
- `fluxengine read -c mx --220ds -s drive:0 -o mx.img`
- `fluxengine read -c mx --220ss -s drive:0 -o mx.img`
- `fluxengine read -c mx --440 -s drive:0 -o mx.img`
## References

View File

@@ -18,9 +18,9 @@ boot ROM could only read single density data.)
To read:
- `fluxengine read n88basic -s drive:0 -o n88basic.img`
- `fluxengine read -c n88basic -s drive:0 -o n88basic.img`
To write:
- `fluxengine write n88basic -d drive:0 -i n88basic.img`
- `fluxengine write -c n88basic -d drive:0 -i n88basic.img`

View File

@@ -31,15 +31,15 @@ equivalent to .img images.
To read:
- `fluxengine read northstar --87 -s drive:0 -o northstar.nsi`
- `fluxengine read northstar --175 -s drive:0 -o northstar.nsi`
- `fluxengine read northstar --350 -s drive:0 -o northstar.nsi`
- `fluxengine read -c northstar --87 -s drive:0 -o northstar.nsi`
- `fluxengine read -c northstar --175 -s drive:0 -o northstar.nsi`
- `fluxengine read -c northstar --350 -s drive:0 -o northstar.nsi`
To write:
- `fluxengine write northstar --87 -d drive:0 -i northstar.nsi`
- `fluxengine write northstar --175 -d drive:0 -i northstar.nsi`
- `fluxengine write northstar --350 -d drive:0 -i northstar.nsi`
- `fluxengine write -c northstar --87 -d drive:0 -i northstar.nsi`
- `fluxengine write -c northstar --175 -d drive:0 -i northstar.nsi`
- `fluxengine write -c northstar --350 -d drive:0 -i northstar.nsi`
## References

View File

@@ -24,9 +24,9 @@ and, oddly, swapped sides.
To read:
- `fluxengine read psos -s drive:0 -o pme.img`
- `fluxengine read -c psos -s drive:0 -o pme.img`
To write:
- `fluxengine write psos -d drive:0 -i pme.img`
- `fluxengine write -c psos -d drive:0 -i pme.img`

View File

@@ -40,9 +40,9 @@ for assistance with this!
To read:
- `fluxengine read rolandd20 -s drive:0 -o rolandd20.img`
- `fluxengine read -c rolandd20 -s drive:0 -o rolandd20.img`
To write:
- `fluxengine write rolandd20 -d drive:0 -i rolandd20.img`
- `fluxengine write -c rolandd20 -d drive:0 -i rolandd20.img`

View File

@@ -15,9 +15,9 @@ vanilla single-sided IBM scheme variation.
To read:
- `fluxengine read rx50 -s drive:0 -o rx50.img`
- `fluxengine read -c rx50 -s drive:0 -o rx50.img`
To write:
- `fluxengine write rx50 -d drive:0 -i rx50.img`
- `fluxengine write -c rx50 -d drive:0 -i rx50.img`

View File

@@ -26,7 +26,7 @@ this is completely correct, so don't trust it!
To read:
- `fluxengine read smaky6 -s drive:0 -o smaky6.img`
- `fluxengine read -c smaky6 -s drive:0 -o smaky6.img`
## References

View File

@@ -34,13 +34,13 @@ FluxEngine supports reading and writing Tartu disks with CP/M filesystem access.
To read:
- `fluxengine read tartu --390 -s drive:0 -o tartu.img`
- `fluxengine read tartu --780 -s drive:0 -o tartu.img`
- `fluxengine read -c tartu --390 -s drive:0 -o tartu.img`
- `fluxengine read -c tartu --780 -s drive:0 -o tartu.img`
To write:
- `fluxengine write tartu --390 -d drive:0 -i tartu.img`
- `fluxengine write tartu --780 -d drive:0 -i tartu.img`
- `fluxengine write -c tartu --390 -d drive:0 -i tartu.img`
- `fluxengine write -c tartu --780 -d drive:0 -i tartu.img`
## References

View File

@@ -26,11 +26,11 @@ FluxEngine will read and write these (but only the DSDD MFM variant).
To read:
- `fluxengine read tids990 -s drive:0 -o tids990.img`
- `fluxengine read -c tids990 -s drive:0 -o tids990.img`
To write:
- `fluxengine write tids990 -d drive:0 -i tids990.img`
- `fluxengine write -c tids990 -d drive:0 -i tids990.img`
## References

View File

@@ -20,8 +20,8 @@ on the precise format.
To read:
- `fluxengine read tiki --90 -s drive:0 -o tiki.img`
- `fluxengine read tiki --200 -s drive:0 -o tiki.img`
- `fluxengine read tiki --400 -s drive:0 -o tiki.img`
- `fluxengine read tiki --800 -s drive:0 -o tiki.img`
- `fluxengine read -c tiki --90 -s drive:0 -o tiki.img`
- `fluxengine read -c tiki --200 -s drive:0 -o tiki.img`
- `fluxengine read -c tiki --400 -s drive:0 -o tiki.img`
- `fluxengine read -c tiki --800 -s drive:0 -o tiki.img`

View File

@@ -46,13 +46,13 @@ FluxEngine can read and write both the single-sided and double-sided variants.
To read:
- `fluxengine read victor9k --612 -s drive:0 -o victor9k.img`
- `fluxengine read victor9k --1224 -s drive:0 -o victor9k.img`
- `fluxengine read -c victor9k --612 -s drive:0 -o victor9k.img`
- `fluxengine read -c victor9k --1224 -s drive:0 -o victor9k.img`
To write:
- `fluxengine write victor9k --612 -d drive:0 -i victor9k.img`
- `fluxengine write victor9k --1224 -d drive:0 -i victor9k.img`
- `fluxengine write -c victor9k --612 -d drive:0 -i victor9k.img`
- `fluxengine write -c victor9k --1224 -d drive:0 -i victor9k.img`
## References

View File

@@ -31,7 +31,7 @@ system.
To read:
- `fluxengine read zilogmcz -s drive:0 -o zilogmcz.img`
- `fluxengine read -c zilogmcz -s drive:0 -o zilogmcz.img`
## References

View File

@@ -15,7 +15,7 @@ If possible, try using the GUI, which should provide simplified access for most
common operations.
<div style="text-align: center">
<a href="doc/screenshot-details.png"><img src="doc/screenshot-details.png" style="width:60%" alt="screenshot of the GUI in action"></a>
<a href="screenshot-details.png"><img src="screenshot-details.png" style="width:60%" alt="screenshot of the GUI in action"></a>
</div>
### Core concepts
@@ -82,16 +82,16 @@ Here are some sample invocations:
```
# Read an PC 1440kB disk, producing a disk image with the default name
# (ibm.img)
$ fluxengine read ibm --1440
$ fluxengine read -c ibm --1440
# Write a PC 1440kB disk to drive 1
$ fluxengine write ibm --1440 -i image.img -d drive:1
$ fluxengine write -c ibm --1440 -i image.img -d drive:1
# Read a Eco1 CP/M disk, making a copy of the flux into a file
$ fluxengine read eco1 --copy-flux-to copy.flux -o eco1.ldbs
$ fluxengine read -c eco1 --copy-flux-to copy.flux -o eco1.ldbs
# Rerun the decode from the flux file, tweaking the parameters
$ fluxengine read eco1 -s copy.flux -o eco1.ldbs --cylinders=1
$ fluxengine read -c eco1 -s copy.flux -o eco1.ldbs --cylinders=1
```
### Configuration
@@ -146,14 +146,14 @@ different task. Run each one with `--help` to get a full list of
(non-configuration-setting) options; this describes only basic usage of the
more common tools.
- `fluxengine read <profile> <options> -s <flux source> -o <image output>`
- `fluxengine read -c <profile> <options> -s <flux source> -o <image output>`
Reads flux (possibly from a disk) and decodes it into a file system image.
`<profile>` is a reference to an internal input configuration file
describing the format. `<options>` may be any combination of options
defined by the profile.
- `fluxengine write <profile> -i <image input> -d <flux destination>`
- `fluxengine write -c <profile> -i <image input> -d <flux destination>`
Reads a filesystem image and encodes it into flux (possibly writing to a
disk). `<profile>` is a reference to an internal output configuration file
@@ -377,6 +377,27 @@ FluxEngine also supports a number of file system image formats. When using the
correctness. Individual records are separated by three `\\0` bytes and tracks
are separated by four `\\0` bytes; tracks are emitted in CHS order.
### Manipulating flux files
`fluxengine fluxfile` contains a set of tools for examining or manipulating
individual flux files. (Note that this only works on flux files themselves, not
sources.)
- `fluxfile ls -f <fluxfile>`
Shows all the components inside a flux file.
- `fluxfile rm -f <fluxfile> -t <tracks>`
Removes flux from a flux file. All reads are removed from the specified track;
use the normal `c0h0` syntax, including using ranges, such as `c0-9h0-1`.
- `fluxfile cp -i <input fluxfile> -o <output fluxfile> -t <tracks>`
Copies flux from one file to another. All reads are copied from the source
file and _appended_ to the relevant track in the destination file. You can use
ranges etc.
### High density disks
High density disks use a different magnetic medium to low and double density
@@ -468,7 +489,7 @@ containing valuable historical data, and you want to read them.
Typically I do this:
```
$ fluxengine read brother240 -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
$ fluxengine read -c brother240 -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
```
This will read the disk in drive 0 and write out an information CSV file. It'll
@@ -478,7 +499,7 @@ settings, I can rerun the decode without having to physically touch the disk
like this:
```
$ fluxengine read brother -s brother.flux -o brother.img --decoder.write_csv_to=brother.csv
$ fluxengine read -c brother -s brother.flux -o brother.img --decoder.write_csv_to=brother.csv
```
Apart from being drastically faster, this avoids touching the (potentially

View File

@@ -2,9 +2,14 @@ syntax = "proto2";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
optional string help = 50000;
optional bool recurse = 50001 [default = true];
extend google.protobuf.FieldOptions
{
optional string help = 50000;
}
extend google.protobuf.MessageOptions
{
optional bool recurse = 50001 [default = true];
}
enum IndexMode {
@@ -29,21 +34,18 @@ enum FluxSourceSinkType {
FLUXTYPE_DMK = 12;
}
enum ImageReaderWriterType {
IMAGETYPE_NOT_SET = 0;
IMAGETYPE_D64 = 1;
IMAGETYPE_D88 = 2;
IMAGETYPE_DIM = 3;
IMAGETYPE_DISKCOPY = 4;
IMAGETYPE_FDI = 5;
IMAGETYPE_IMD = 6;
IMAGETYPE_IMG = 7;
IMAGETYPE_JV3 = 8;
IMAGETYPE_LDBS = 9;
IMAGETYPE_NFD = 10;
IMAGETYPE_NSI = 11;
IMAGETYPE_RAW = 12;
IMAGETYPE_TD0 = 13;
enum ImageReaderWriterType
{
IMAGETYPE_NOT_SET = 0; IMAGETYPE_D64 = 1; IMAGETYPE_D88 = 2;
IMAGETYPE_DIM = 3;
IMAGETYPE_DISKCOPY = 4;
IMAGETYPE_FDI = 5;
IMAGETYPE_IMD = 6;
IMAGETYPE_IMG = 7;
IMAGETYPE_JV3 = 8;
IMAGETYPE_LDBS = 9;
IMAGETYPE_NFD = 10;
IMAGETYPE_NSI = 11;
IMAGETYPE_RAW = 12;
IMAGETYPE_TD0 = 13;
}

View File

@@ -5,6 +5,7 @@
#include "lib/core/utils.h"
#include <fstream>
#include <google/protobuf/text_format.h>
#include <fmt/ranges.h>
static Config config;
@@ -181,35 +182,8 @@ ConfigProto* Config::combined()
{
_combinedConfig = _baseConfig;
/* First apply any standalone options. */
std::set<std::string> options = _appliedOptions;
for (const auto& option : _baseConfig.option())
{
if (options.find(option.name()) != options.end())
{
_combinedConfig.MergeFrom(option.config());
options.erase(option.name());
}
}
/* Then apply any group options. */
for (auto& group : _baseConfig.option_group())
{
const OptionProto* selectedOption = &*group.option().begin();
for (auto& option : group.option())
{
if (options.find(option.name()) != options.end())
{
selectedOption = &option;
options.erase(option.name());
}
}
_combinedConfig.MergeFrom(selectedOption->config());
}
for (const auto& optionInfo : _appliedOptions)
_combinedConfig.MergeFrom(optionInfo.option->config());
/* Add in the user overrides. */
@@ -241,51 +215,27 @@ std::vector<std::string> Config::validate()
{
std::vector<std::string> results;
std::set<std::string> optionNames = _appliedOptions;
std::set<const OptionProto*> appliedOptions;
for (const auto& option : _baseConfig.option())
{
if (optionNames.find(option.name()) != optionNames.end())
/* Ensure that only one item in each group is set. */
std::map<const OptionGroupProto*, const OptionProto*> optionsByGroup;
for (auto [group, option, hasArgument] : _appliedOptions)
if (group)
{
appliedOptions.insert(&option);
optionNames.erase(option.name());
auto& o = optionsByGroup[group];
if (o)
results.push_back(
fmt::format("multiple mutually exclusive values set for "
"group '{}': valid values are: {}",
group->comment(),
fmt::join(std::views::transform(
group->option(), &OptionProto::name),
", ")));
o = option;
}
}
/* Then apply any group options. */
for (auto& group : _baseConfig.option_group())
{
int count = 0;
for (auto& option : group.option())
{
if (optionNames.find(option.name()) != optionNames.end())
{
optionNames.erase(option.name());
appliedOptions.insert(&option);
count++;
if (count == 2)
results.push_back(
fmt::format("multiple mutually exclusive options set "
"for group '{}'",
group.comment()));
}
}
}
/* Check for unknown options. */
if (!optionNames.empty())
{
for (auto& name : optionNames)
results.push_back(fmt::format("'{}' is not a known option", name));
}
/* Check option requirements. */
for (auto& option : appliedOptions)
for (auto [group, option, hasArgument] : _appliedOptions)
{
try
{
@@ -360,11 +310,12 @@ void Config::readBaseConfig(std::string data)
error("couldn't load external config proto");
}
const OptionProto& Config::findOption(const std::string& optionName)
Config::OptionInfo Config::findOption(
const std::string& name, const std::string value)
{
const OptionProto* found = nullptr;
auto searchOptionList = [&](auto& optionList)
auto searchOptionList = [&](auto& optionList, const std::string& optionName)
{
for (const auto& option : optionList)
{
@@ -377,17 +328,39 @@ const OptionProto& Config::findOption(const std::string& optionName)
return false;
};
if (searchOptionList(base()->option()))
return *found;
/* First look for any group names which match. */
if (!value.empty())
for (const auto& optionGroup : base()->option_group())
if (optionGroup.name() == name)
{
/* The option must therefore be one of these. */
if (searchOptionList(optionGroup.option(), value))
return {&optionGroup, found, true};
throw OptionNotFoundException(fmt::format(
"value {} is not valid for option {}; valid values are: {}",
value,
name,
fmt::join(std::views::transform(
optionGroup.option(), &OptionProto::name),
", ")));
}
/* Now search for individual options. */
if (searchOptionList(base()->option(), name))
return {nullptr, found, false};
for (const auto& optionGroup : base()->option_group())
{
if (searchOptionList(optionGroup.option()))
return *found;
if (optionGroup.name().empty())
if (searchOptionList(optionGroup.option(), name))
return {nullptr, found, false};
}
throw OptionNotFoundException(
fmt::format("option {} not found", optionName));
throw OptionNotFoundException(fmt::format("option {} not found", name));
}
void Config::checkOptionValid(const OptionProto& option)
@@ -445,22 +418,20 @@ bool Config::isOptionValid(const OptionProto& option)
}
}
bool Config::isOptionValid(std::string option)
{
return isOptionValid(findOption(option));
}
void Config::applyOption(const OptionProto& option)
void Config::applyOption(const OptionInfo& optionInfo)
{
auto* option = optionInfo.option;
log(OptionLogMessage{
option.has_message() ? option.message() : option.comment()});
option->has_message() ? option->message() : option->comment()});
_appliedOptions.insert(option.name());
_appliedOptions.insert(optionInfo);
}
void Config::applyOption(std::string option)
bool Config::applyOption(const std::string& name, const std::string value)
{
applyOption(findOption(option));
auto optionInfo = findOption(name, value);
applyOption(optionInfo);
return optionInfo.usesValue;
}
void Config::clearOptions()

View File

@@ -66,6 +66,18 @@ struct FluxConstructor
class Config
{
private:
struct OptionInfo
{
bool operator==(const OptionInfo& other) const = default;
std::strong_ordering operator<=>(
const OptionInfo& other) const = default;
const OptionGroupProto* group;
const OptionProto* option;
bool usesValue;
};
public:
/* Direct access to the various proto layers. */
@@ -124,12 +136,12 @@ public:
/* Option management: look up an option by name, determine whether an option
* is valid, and apply an option. */
const OptionProto& findOption(const std::string& option);
OptionInfo findOption(
const std::string& name, const std::string value = "");
void checkOptionValid(const OptionProto& option);
bool isOptionValid(const OptionProto& option);
bool isOptionValid(std::string option);
void applyOption(const OptionProto& option);
void applyOption(std::string option);
void applyOption(const OptionInfo& optionInfo);
bool applyOption(const std::string& name, const std::string value = "");
void clearOptions();
/* Adjust overall inputs and outputs. */
@@ -165,7 +177,7 @@ private:
ConfigProto _baseConfig;
ConfigProto _overridesConfig;
ConfigProto _combinedConfig;
std::set<std::string> _appliedOptions;
std::set<OptionInfo> _appliedOptions;
bool _configValid;
FluxSourceProto _verificationFluxSourceProto;

View File

@@ -14,20 +14,20 @@ import "lib/config/layout.proto";
enum SupportStatus
{
UNSUPPORTED = 0;
DINOSAUR = 1;
UNICORN = 2;
UNSUPPORTED = 0; DINOSAUR = 1; UNICORN = 2;
}
// NEXT_TAG: 27
message ConfigProto
{
option(recurse) = false;
optional string shortname = 1;
optional string comment = 2;
optional bool is_extension = 3;
repeated string documentation = 4;
optional SupportStatus read_support_status = 5 [ default = UNSUPPORTED ];
optional SupportStatus write_support_status = 6 [ default = UNSUPPORTED ];
optional SupportStatus read_support_status = 5 [default = UNSUPPORTED];
optional SupportStatus write_support_status = 6 [default = UNSUPPORTED];
optional LayoutProto layout = 7;
@@ -51,28 +51,28 @@ message ConfigProto
message OptionPrerequisiteProto
{
optional string key = 1 [ (help) = "path to config value" ];
repeated string value = 2 [ (help) = "list of required values" ];
optional string key = 1 [(help) = "path to config value"];
repeated string value = 2 [(help) = "list of required values"];
}
// NEXT_TAG: 8
message OptionProto
{
optional string name = 1 [ (help) = "option name" ];
optional string comment = 2 [ (help) = "help text for option" ];
optional string message = 3
[ (help) = "message to display when option is in use" ];
optional bool set_by_default = 6
[ (help) = "this option is applied by default", default = false ];
repeated OptionPrerequisiteProto prerequisite = 7
[ (help) = "prerequisites for this option" ];
optional string name = 1 [(help) = "option name"];
optional string comment = 2 [(help) = "help text for option"];
optional string message =
3 [(help) = "message to display when option is in use"];
optional bool set_by_default =
6 [(help) = "this option is applied by default", default = false];
repeated OptionPrerequisiteProto prerequisite =
7 [(help) = "prerequisites for this option"];
optional ConfigProto config = 4
[ (help) = "option data", (recurse) = false ];
optional ConfigProto config = 4 [(help) = "option data"];
}
message OptionGroupProto
{
optional string comment = 1 [ (help) = "help text for option group" ];
repeated OptionProto option = 2;
optional string comment = 1 [(help) = "help text for option group"];
optional string name = 2 [(help) = "option group name"];
repeated OptionProto option = 3;
}

View File

@@ -13,17 +13,23 @@ static std::vector<Flag*> all_flags;
static std::map<const std::string, Flag*> flags_by_name;
static void doHelp();
static void doLoadConfig(const std::string& filename);
static void doShowConfig();
static void doDoc();
static FlagGroup helpGroup;
static ActionFlag helpFlag = ActionFlag({"--help"}, "Shows the help.", doHelp);
static ActionFlag showConfigFlag = ActionFlag({"--config", "-C"},
static FlagGroup configGroup;
static ActionFlag loadConfigFlag({"--config", "-c"},
"Reads an internal or external configuration file.",
doLoadConfig);
static ActionFlag showConfigFlag({"--show-config", "-C"},
"Shows the currently set configuration and halts.",
doShowConfig);
static ActionFlag docFlag = ActionFlag(
static ActionFlag docFlag(
{"--doc"}, "Shows the available configuration options and halts.", doDoc);
FlagGroup::FlagGroup()
@@ -152,7 +158,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
index += usesthat;
}
else
globalConfig().applyOption(path);
usesthat = globalConfig().applyOption(path, value);
}
else
error("unrecognised flag '-{}'; try --help", key);
@@ -182,17 +188,17 @@ void FlagGroup::parseFlags(int argc,
"non-option parameter '{}' seen (try --help)", *filenames.begin());
}
static void doLoadConfig(const std::string& filename)
{
globalConfig().readBaseConfigFile(filename);
}
void FlagGroup::parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, const ConfigProto*>& configFiles)
{
parseFlags(argc,
argv,
[&](const auto& filename)
{
globalConfig().readBaseConfigFile(filename);
return true;
});
globalConfig().readBaseConfigFile("_global_options");
FlagGroup({this, &configGroup}).parseFlags(argc, argv);
}
void FlagGroup::checkInitialised() const
@@ -264,7 +270,8 @@ static void doShowConfig()
static void doDoc()
{
const auto fields = findAllProtoFields(globalConfig().base());
const auto fields =
findAllPossibleProtoFields(globalConfig().base()->GetDescriptor());
for (const auto field : fields)
{
const std::string& path = field.first;

View File

@@ -83,13 +83,23 @@ public:
const std::string helptext,
std::function<void(void)> callback):
Flag(names, helptext),
_callback(callback)
_voidCallback(callback),
_hasArgument(false)
{
}
ActionFlag(const std::vector<std::string>& names,
const std::string helptext,
std::function<void(const std::string&)> callback):
Flag(names, helptext),
_callback(callback),
_hasArgument(true)
{
}
bool hasArgument() const override
{
return false;
return _hasArgument;
}
const std::string defaultValueAsString() const override
@@ -99,11 +109,16 @@ public:
void set(const std::string& value) override
{
_callback();
if (_hasArgument)
_callback(value);
else
_voidCallback();
}
private:
const std::function<void(void)> _callback;
const std::function<void(const std::string&)> _callback;
const std::function<void(void)> _voidCallback;
bool _hasArgument;
};
class SettableFlag : public Flag

View File

@@ -1,6 +1,7 @@
#include "lib/core/globals.h"
#include "lib/config/proto.h"
#include "lib/config/common.pb.h"
#include "google/protobuf/reflection.h"
#include <regex>
static ConfigProto config = []()
@@ -16,7 +17,7 @@ ConfigProto& globalConfigProto()
return config;
}
static double toFloat(const std::string& value)
static float toFloat(const std::string& value)
{
try
{
@@ -66,6 +67,23 @@ static uint64_t toUint64(const std::string& value)
return d;
}
static int splitIndexedField(std::string& item)
{
static const std::regex INDEX_REGEX("(\\w+)\\[([0-9]+)\\]");
int index = -1;
std::smatch dmatch;
if (std::regex_match(item, dmatch, INDEX_REGEX))
{
auto stem = dmatch[1];
index = std::stoi(dmatch[2]);
item = stem;
}
return index;
}
static ProtoField resolveProtoPath(
google::protobuf::Message* message, const std::string& path, bool create)
{
@@ -86,6 +104,10 @@ static ProtoField resolveProtoPath(
std::stringstream ss(leading);
while (std::getline(ss, item, '.'))
{
static const std::regex INDEX_REGEX("(\\w+)\\[([0-9]+)\\]");
int index = splitIndexedField(item);
const auto* field = descriptor->FindFieldByName(item);
if (!field)
throw ProtoPathNotFoundException(
@@ -95,6 +117,14 @@ static ProtoField resolveProtoPath(
"config field '{}' in '{}' is not a message", item, path));
const auto* reflection = message->GetReflection();
if ((field->label() !=
google::protobuf::FieldDescriptor::LABEL_REPEATED) &&
(index != -1))
throw ProtoPathNotFoundException(fmt::format(
"config field '{}[{}]' is indexed, but not repeated",
item,
index));
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
@@ -106,16 +136,15 @@ static ProtoField resolveProtoPath(
break;
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (reflection->FieldSize(*message, field) == 0)
{
if (create)
message = reflection->AddMessage(message, field);
else
fail();
}
else
message =
reflection->MutableRepeatedMessage(message, field, 0);
if (index == -1)
throw ProtoPathNotFoundException(fmt::format(
"config field '{}' is repeated and must be indexed",
item));
while (reflection->FieldSize(*message, field) <= index)
reflection->AddMessage(message, field);
message =
reflection->MutableRepeatedMessage(message, field, index);
break;
default:
@@ -125,11 +154,12 @@ static ProtoField resolveProtoPath(
descriptor = message->GetDescriptor();
}
int index = splitIndexedField(trailing);
const auto* field = descriptor->FindFieldByName(trailing);
if (!field)
fail();
return std::make_pair(message, field);
return ProtoField(path, message, field, index);
}
ProtoField makeProtoPath(
@@ -144,130 +174,324 @@ ProtoField findProtoPath(
return resolveProtoPath(message, path, /* create= */ false);
}
void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
static bool parseBoolean(const std::string& value)
{
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
static const std::map<std::string, bool> boolvalues = {
{"false", false},
{"f", false},
{"no", false},
{"n", false},
{"0", false},
{"true", true },
{"t", true },
{"yes", true },
{"y", true },
{"1", true },
};
const auto* reflection = message->GetReflection();
switch (field->type())
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
error("invalid boolean value");
return it->second;
}
static int32_t parseEnum(
const google::protobuf::FieldDescriptor* field, const std::string& value)
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
error("unrecognised enum value '{}'", value);
return enumvalue->number();
}
template <typename T>
static void updateRepeatedField(
google::protobuf::MutableRepeatedFieldRef<T> mrfr, int index, T value)
{
mrfr.Set(index, value);
}
void ProtoField::set(const std::string& value)
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
{
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
reflection->SetFloat(message, field, toFloat(value));
break;
if (_index == -1)
error("field '{}' is repeated but no index is provided");
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(message, field, toDouble(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(message, field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_BOOL:
switch (_field->type())
{
static const std::map<std::string, bool> boolvalues = {
{"false", false},
{"f", false},
{"no", false},
{"n", false},
{"0", false},
{"true", true },
{"t", true },
{"yes", true },
{"y", true },
{"1", true },
};
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
error("invalid boolean value");
reflection->SetBool(message, field, it->second);
break;
}
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
error("unrecognised enum value '{}'", value);
reflection->SetEnum(message, field, enumvalue);
break;
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (field->containing_oneof() && value.empty())
{
reflection->MutableMessage(message, field);
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<float>(
_message, _field),
_index,
toFloat(value));
break;
}
/* fall through */
default:
error("can't set this config value type");
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<double>(
_message, _field),
_index,
toDouble(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<int32_t>(
_message, _field),
_index,
(int32_t)toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<int64_t>(
_message, _field),
_index,
toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<uint32_t>(
_message, _field),
_index,
(uint32_t)toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<uint64_t>(
_message, _field),
_index,
toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<std::string>(
_message, _field),
_index,
value);
break;
case google::protobuf::FieldDescriptor::TYPE_BOOL:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<bool>(
_message, _field),
_index,
parseBoolean(value));
break;
case google::protobuf::FieldDescriptor::TYPE_ENUM:
updateRepeatedField(
reflection->GetMutableRepeatedFieldRef<int32_t>(
_message, _field),
_index,
parseEnum(_field, value));
break;
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
error("'{}' is a message and can't be directly set",
_field->name());
default:
error("can't set this config value type");
}
}
else
{
if (_index != -1)
error("field '{}' is not repeated but an index is provided");
switch (_field->type())
{
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
reflection->SetFloat(_message, _field, toFloat(value));
break;
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(_message, _field, toDouble(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(_message, _field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(_message, _field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(_message, _field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(_message, _field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(_message, _field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_BOOL:
reflection->SetBool(_message, _field, parseBoolean(value));
break;
case google::protobuf::FieldDescriptor::TYPE_ENUM:
reflection->SetEnumValue(
_message, _field, parseEnum(_field, value));
break;
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
error("'{}[{}]' is a message and can't be directly set",
_field->name(),
_index);
default:
error("can't set this config value type");
}
}
}
std::string getProtoFieldValue(ProtoField& protoField)
std::string ProtoField::get() const
{
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
const auto* reflection = message->GetReflection();
switch (field->type())
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
{
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
return fmt::format("{}", reflection->GetFloat(*message, field));
if (_index == -1)
error("field '{}' is repeated but no index is provided",
_field->name());
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
return fmt::format("{}", reflection->GetDouble(*message, field));
case google::protobuf::FieldDescriptor::TYPE_INT32:
return std::to_string(reflection->GetInt32(*message, field));
case google::protobuf::FieldDescriptor::TYPE_INT64:
return std::to_string(reflection->GetInt64(*message, field));
case google::protobuf::FieldDescriptor::TYPE_UINT32:
return std::to_string(reflection->GetUInt32(*message, field));
case google::protobuf::FieldDescriptor::TYPE_UINT64:
return std::to_string(reflection->GetUInt64(*message, field));
case google::protobuf::FieldDescriptor::TYPE_STRING:
return reflection->GetString(*message, field);
case google::protobuf::FieldDescriptor::TYPE_BOOL:
return std::to_string(reflection->GetBool(*message, field));
case google::protobuf::FieldDescriptor::TYPE_ENUM:
switch (_field->type())
{
const auto* enumvalue = reflection->GetEnum(*message, field);
return enumvalue->name();
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
return fmt::format("{:g}",
reflection->GetRepeatedFloat(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
return fmt::format("{:g}",
reflection->GetRepeatedDouble(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_INT32:
return std::to_string(
reflection->GetRepeatedInt32(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_INT64:
return std::to_string(
reflection->GetRepeatedInt64(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_UINT32:
return std::to_string(
reflection->GetRepeatedUInt32(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_UINT64:
return std::to_string(
reflection->GetRepeatedUInt64(*_message, _field, _index));
case google::protobuf::FieldDescriptor::TYPE_STRING:
return reflection->GetRepeatedString(*_message, _field, _index);
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
error("'{}' is a message and can't be directly fetched",
_field->name());
default:
error("unknown field type when fetching repeated field '{}'",
_field->name());
}
}
else
{
if (_index != -1)
error("field '{}' is not repeated but an index is provided",
_field->name());
switch (_field->type())
{
case google::protobuf::FieldDescriptor::TYPE_FLOAT:
return fmt::format(
"{:g}", reflection->GetFloat(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
error("cannot fetch message value");
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
return fmt::format(
"{:g}", reflection->GetDouble(*_message, _field));
default:
error("unknown field type when fetching");
case google::protobuf::FieldDescriptor::TYPE_INT32:
return std::to_string(reflection->GetInt32(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_INT64:
return std::to_string(reflection->GetInt64(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_UINT32:
return std::to_string(reflection->GetUInt32(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_UINT64:
return std::to_string(reflection->GetUInt64(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_STRING:
return reflection->GetString(*_message, _field);
case google::protobuf::FieldDescriptor::TYPE_BOOL:
return std::to_string(reflection->GetBool(*_message, _field));
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumvalue = reflection->GetEnum(*_message, _field);
return (std::string)enumvalue->name();
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
error("'{}[{}]' is a message and can't be directly set",
_field->name(),
_index);
default:
error("unknown field type when fetching '{}'", _field->name());
}
}
}
google::protobuf::Message* ProtoField::getMessage() const
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
{
if (_index == -1)
error("field '{}' is repeated but no index is provided",
_field->name());
return reflection->MutableRepeatedMessage(_message, _field, _index);
}
else
{
if (_index != -1)
error("field '{}' is not repeated but an index is provided",
_field->name());
return reflection->MutableMessage(_message, _field);
}
}
std::string ProtoField::getBytes() const
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
{
if (_index == -1)
error("field '{}' is repeated but no index is provided",
_field->name());
return reflection->GetRepeatedString(*_message, _field, _index);
}
else
{
if (_index != -1)
error("field '{}' is not repeated but an index is provided",
_field->name());
return reflection->GetString(*_message, _field);
}
}
@@ -275,15 +499,13 @@ void setProtoByString(google::protobuf::Message* message,
const std::string& path,
const std::string& value)
{
ProtoField protoField = makeProtoPath(message, path);
setProtoFieldFromString(protoField, value);
makeProtoPath(message, path).set(value);
}
std::string getProtoByString(
google::protobuf::Message* message, const std::string& path)
{
ProtoField protoField = findProtoPath(message, path);
return getProtoFieldValue(protoField);
return findProtoPath(message, path).get();
}
std::set<unsigned> iterate(unsigned start, unsigned count)
@@ -294,11 +516,17 @@ std::set<unsigned> iterate(unsigned start, unsigned count)
return set;
}
static bool shouldRecurse(const google::protobuf::FieldDescriptor* f)
{
if (f->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
return false;
return f->message_type()->options().GetExtension(::recurse);
}
std::map<std::string, const google::protobuf::FieldDescriptor*>
findAllProtoFields(google::protobuf::Message* message)
findAllPossibleProtoFields(const google::protobuf::Descriptor* descriptor)
{
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
const auto* descriptor = message->GetDescriptor();
std::function<void(const google::protobuf::Descriptor*, const std::string&)>
recurse = [&](auto* d, const auto& s)
@@ -306,10 +534,12 @@ findAllProtoFields(google::protobuf::Message* message)
for (int i = 0; i < d->field_count(); i++)
{
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + f->name();
std::string n = s + (std::string)f->name();
if (f->options().GetExtension(::recurse) &&
(f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE))
if (f->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
n += "[]";
if (shouldRecurse(f))
recurse(f->message_type(), n + ".");
fields[n] = f;
@@ -320,10 +550,47 @@ findAllProtoFields(google::protobuf::Message* message)
return fields;
}
ConfigProto parseConfigBytes(const std::string_view& data)
std::vector<ProtoField> findAllProtoFields(google::protobuf::Message* message)
{
ConfigProto proto;
if (!proto.ParseFromArray(data.begin(), data.size()))
error("invalid internal config data");
return proto;
std::vector<ProtoField> allFields;
std::function<void(google::protobuf::Message*, const std::string&)>
recurse = [&](auto* message, const auto& name)
{
const auto* reflection = message->GetReflection();
std::vector<const google::protobuf::FieldDescriptor*> fields;
reflection->ListFields(*message, &fields);
for (const auto* f : fields)
{
auto basename = name;
if (!basename.empty())
basename += '.';
basename += f->name();
if (f->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
{
for (int i = 0; i < reflection->FieldSize(*message, f); i++)
{
const auto n = fmt::format("{}[{}]", basename, i);
if (shouldRecurse(f))
recurse(
reflection->MutableRepeatedMessage(message, f, i),
n);
else
allFields.push_back(ProtoField(n, message, f, i));
}
}
else
{
if (shouldRecurse(f))
recurse(reflection->MutableMessage(message, f), basename);
else
allFields.push_back(ProtoField(basename, message, f));
}
}
};
recurse(message, "");
return allFields;
}

View File

@@ -14,9 +14,44 @@ public:
}
};
typedef std::pair<google::protobuf::Message*,
const google::protobuf::FieldDescriptor*>
ProtoField;
class ProtoField
{
public:
ProtoField(const std::string& path,
google::protobuf::Message* message,
const google::protobuf::FieldDescriptor* field,
int index = -1):
_path(path),
_message(message),
_field(field),
_index(index)
{
}
void set(const std::string& value);
std::string get() const;
google::protobuf::Message* getMessage() const;
std::string getBytes() const;
bool operator==(const ProtoField& other) const = default;
std::strong_ordering operator<=>(const ProtoField& other) const = default;
const std::string& path() const
{
return _path;
}
const google::protobuf::FieldDescriptor* descriptor() const
{
return _field;
}
private:
std::string _path;
google::protobuf::Message* _message;
const google::protobuf::FieldDescriptor* _field;
int _index;
};
extern ProtoField makeProtoPath(
google::protobuf::Message* message, const std::string& path);
@@ -34,9 +69,19 @@ extern std::string getProtoByString(
extern std::set<unsigned> iterate(unsigned start, unsigned count);
extern std::map<std::string, const google::protobuf::FieldDescriptor*>
findAllProtoFields(google::protobuf::Message* message);
findAllPossibleProtoFields(const google::protobuf::Descriptor* descriptor);
extern ConfigProto parseConfigBytes(const std::string_view& bytes);
extern std::vector<ProtoField> findAllProtoFields(
google::protobuf::Message* message);
template <class T>
static inline const T parseProtoBytes(const std::string_view& bytes)
{
T proto;
if (!proto.ParseFromArray(bytes.begin(), bytes.size()))
error("invalid internal proto data");
return proto;
}
extern const std::map<std::string, const ConfigProto*> formats;

View File

@@ -24,6 +24,9 @@
#define mkdir(A, B) _mkdir(A)
#endif
#define STRINGIFY(a) XSTRINGIFY(a)
#define XSTRINGIFY(a) #a
template <class T>
static inline std::vector<T> vector_of(T item)
{

View File

@@ -29,7 +29,8 @@ static unsigned getTrackStep()
{
case DRIVETYPE_40TRACK:
error(
"you can't read/write an 80 track image from/to a 40 track "
"you can't read/write an 80 track image from/to a 40 "
"track "
"drive");
case DRIVETYPE_80TRACK:
@@ -37,7 +38,8 @@ static unsigned getTrackStep()
case DRIVETYPE_APPLE2:
error(
"you can't read/write an 80 track image from/to an Apple II "
"you can't read/write an 80 track image from/to an "
"Apple II "
"drive");
}
}

View File

@@ -1,5 +1,12 @@
syntax = "proto2";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions
{
optional bool isflux = 60000 [default = false];
}
enum FluxMagic {
MAGIC = 0x466c7578;
}
@@ -12,7 +19,7 @@ enum FluxFileVersion {
message TrackFluxProto {
optional int32 track = 1;
optional int32 head = 2;
repeated bytes flux = 3;
repeated bytes flux = 3 [(isflux) = true];
}
enum DriveType {

View File

@@ -5,13 +5,14 @@ encoders = {}
@Rule
def protoencode_single(self, name, srcs: Targets, proto, symbol):
def protoencode_single(self, name, srcs: Targets, proto, include, symbol):
if proto not in encoders:
r = cxxprogram(
name="protoencode_" + proto,
srcs=["scripts/protoencode.cc"],
cflags=["-DPROTO=" + proto],
cflags=["-DPROTO=" + proto, "-DINCLUDE="+include],
deps=[
"lib/core",
"lib/config+proto_lib",
"lib/fluxsource+proto_lib",
"lib/fluxsink+proto_lib",
@@ -41,12 +42,13 @@ def protoencode_single(self, name, srcs: Targets, proto, symbol):
@Rule
def protoencode(self, name, proto, srcs: TargetsMap, symbol):
def protoencode(self, name, proto, include,srcs: TargetsMap, symbol):
encoded = [
protoencode_single(
name=f"{k}_cc",
srcs=[v],
proto=proto,
include=include,
symbol=f"{symbol}_{k}_pb",
)
for k, v in srcs.items()

View File

@@ -14,10 +14,10 @@ destfile=$dir/dest.img
dd if=/dev/urandom of=$srcfile bs=1048576 count=2 2>&1
echo $fluxengine write $format -i $srcfile -d $fluxfile --drive.rotational_period_ms=200 $flags
$fluxengine write $format -i $srcfile -d $fluxfile --drive.rotational_period_ms=200 $flags
echo $fluxengine read $format -s $fluxfile -o $destfile --drive.rotational_period_ms=200 $flags
$fluxengine read $format -s $fluxfile -o $destfile --drive.rotational_period_ms=200 $flags
echo $fluxengine write -c $format -i $srcfile -d $fluxfile --drive.rotational_period_ms=200 $flags
$fluxengine write -c $format -i $srcfile -d $fluxfile --drive.rotational_period_ms=200 $flags
echo $fluxengine read -c $format -s $fluxfile -o $destfile --drive.rotational_period_ms=200 $flags
$fluxengine read -c $format -s $fluxfile -o $destfile --drive.rotational_period_ms=200 $flags
if [ ! -s $destfile ]; then
echo "Zero length output file!" >&2
exit 1

View File

@@ -28,7 +28,7 @@ static void addExample(std::vector<std::string>& examples,
else
return;
r += fmt::format(" {}", name);
r += fmt::format(" -c {}", name);
if (format)
r += fmt::format(" --{}", format->name());

View File

@@ -19,7 +19,7 @@ static std::string supportStatus(SupportStatus status)
return "";
}
return "";
return "";
}
int main(int argc, const char* argv[])
@@ -43,7 +43,9 @@ int main(int argc, const char* argv[])
{
const auto* descriptor =
FilesystemProto::FilesystemType_descriptor();
auto name = descriptor->FindValueByNumber(fs.type())->name();
auto name =
(std::string)descriptor->FindValueByNumber(fs.type())
->name();
filesystems.insert(name);
}

View File

@@ -3,12 +3,13 @@
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <fstream>
#include "fmt/format.h"
#include "lib/core/globals.h"
#include "tests/testproto.pb.h"
#include "lib/config/config.pb.h"
#include <sstream>
#include <locale>
#define STRINGIFY(s) #s
const std::string protoname = STRINGIFY(PROTO);
static uint32_t readu8(std::string::iterator& it, std::string::iterator end)
{
@@ -125,6 +126,7 @@ int main(int argc, const char* argv[])
output << "#include \"lib/core/globals.h\"\n"
<< "#include \"lib/config/proto.h\"\n"
<< "#include \"" STRINGIFY(INCLUDE) "\"\n"
<< "#include <string_view>\n"
<< "static const uint8_t " << name << "_rawData[] = {";
@@ -143,11 +145,11 @@ int main(int argc, const char* argv[])
output << "\n};\n";
output << "extern const std::string_view " << name << "_data;\n";
output << "const std::string_view " << name
<< "_data = std::string_view((const char*)" << name << "_rawData, " << data.size()
<< ");\n";
output << "extern const ConfigProto " << name << ";\n";
output << "const ConfigProto " << name << " = parseConfigBytes("
<< argv[3] << "_data);\n";
<< "_data = std::string_view((const char*)" << name << "_rawData, "
<< data.size() << ");\n";
output << "extern const " << protoname << " " << name << ";\n";
output << "const " << protoname << " " << name << " = parseProtoBytes<"
<< protoname << ">(" << argv[3] << "_data);\n";
return 0;
}

View File

@@ -5,10 +5,15 @@ cxxprogram(
srcs=[
"./fluxengine.cc",
"./fluxengine.h",
"./fluxfile.cc",
"./fluxfile.h",
"./fe-analysedriveresponse.cc",
"./fe-analyselayout.cc",
"./fe-convert.cc",
"./fe-format.cc",
"./fe-fluxfilels.cc",
"./fe-fluxfilerm.cc",
"./fe-fluxfilecp.cc",
"./fe-getdiskinfo.cc",
"./fe-getfile.cc",
"./fe-getfileinfo.cc",
@@ -39,6 +44,7 @@ cxxprogram(
"+z_lib",
"dep/adflib",
"dep/agg",
"dep/alphanum",
"dep/fatfs",
"dep/hfsutils",
"dep/libusbp",

View File

@@ -22,9 +22,7 @@ static StringFlag destFlux({"--dest", "-d"},
globalConfig().setFluxSink(value);
});
static IntFlag destTrack({"--cylinder", "-c"}, "track to write to", 0);
static IntFlag destHead({"--head", "-h"}, "head to write to", 0);
static StringFlag destTracks({"--tracks", "-t"}, "tracks to write to", "c0h0");
static DoubleFlag minInterval(
{"--min-interval-us"}, "Minimum pulse interval", 2.0);
@@ -251,11 +249,14 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
if (globalConfig()->flux_sink().type() != FLUXTYPE_DRIVE)
error("this only makes sense with a real disk drive");
auto tracks = parseCylinderHeadsString(destTracks);
if (tracks.size() != 1)
error("you must specify exactly one track");
usbSetDrive(globalConfig()->drive().drive(),
globalConfig()->drive().high_density(),
globalConfig()->drive().index_mode());
usbSeek(destTrack);
usbSeek(tracks[0].cylinder);
std::cout << "Measuring rotational speed...\n";
nanoseconds_t period = usbGetRotationalPeriod(0);
@@ -291,12 +292,12 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
outFluxmap.appendPulse();
}
usbWrite(destHead, outFluxmap.rawBytes(), 0);
usbWrite(tracks[0].head, outFluxmap.rawBytes(), 0);
/* Read the test pattern in again. */
Fluxmap inFluxmap;
inFluxmap.appendBytes(usbRead(destHead, true, period, 0));
inFluxmap.appendBytes(usbRead(tracks[0].head, true, period, 0));
/* Compute histogram. */

84
src/fe-fluxfilecp.cc Normal file
View File

@@ -0,0 +1,84 @@
#include "lib/core/globals.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/data/sector.h"
#include "lib/config/proto.h"
#include "lib/data/locations.h"
#include "lib/data/flux.h"
#include "lib/external/fl2.h"
#include "lib/external/fl2.pb.h"
#include "src/fluxengine.h"
#include <fstream>
static FlagGroup flags;
static StringFlag inputFilenameFlag({"-i", "--input"}, "input flux file");
static StringFlag outputFilenameFlag(
{"-o", "--output"}, "output flux file (must exist)");
static StringFlag tracksFlag({"-t", "--tracks"}, "tracks to copy");
static const TrackFluxProto* findTrack(
const FluxFileProto& f, int cylinder, int head)
{
for (const auto& trackFlux : f.track())
if ((trackFlux.track() == cylinder) && (trackFlux.head() == head))
return &trackFlux;
return nullptr;
}
static TrackFluxProto* findOrMakeTrack(FluxFileProto& f, int cylinder, int head)
{
for (auto& trackFlux : *f.mutable_track())
if ((trackFlux.track() == cylinder) && (trackFlux.head() == head))
return &trackFlux;
TrackFluxProto* tf = f.add_track();
tf->set_track(cylinder);
tf->set_head(head);
return tf;
}
int mainFluxfileCp(int argc, const char* argv[])
{
flags.parseFlags(argc, argv);
if (!inputFilenameFlag.isSet())
error("you must specify an input filename with -i");
if (!outputFilenameFlag.isSet())
error("you must specify an output filename with -o");
fmt::print(
"{} -> {}:\n", inputFilenameFlag.get(), outputFilenameFlag.get());
FluxFileProto inf = loadFl2File(inputFilenameFlag.get());
FluxFileProto outf = loadFl2File(outputFilenameFlag.get());
bool changed = false;
for (const auto& location : parseCylinderHeadsString(tracksFlag))
{
const TrackFluxProto* intrack =
findTrack(inf, location.cylinder, location.head);
if (!intrack)
{
fmt::print(" location c{}h{} not found\n",
location.cylinder,
location.head);
continue;
}
TrackFluxProto* outtrack =
findOrMakeTrack(outf, location.cylinder, location.head);
fmt::print(" copying c{}h{}\n", location.cylinder, location.head);
for (const auto& flux : intrack->flux())
outtrack->add_flux(flux);
changed = true;
}
if (changed)
{
fmt::print("writing back output file\n");
saveFl2File(outputFilenameFlag.get(), outf);
}
else
fmt::print("output file not modified\n");
return 0;
}

45
src/fe-fluxfilels.cc Normal file
View File

@@ -0,0 +1,45 @@
#include "lib/core/globals.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/data/sector.h"
#include "lib/config/proto.h"
#include "lib/data/flux.h"
#include "lib/external/fl2.h"
#include "lib/external/fl2.pb.h"
#include "src/fluxengine.h"
#include <fstream>
static FlagGroup flags;
static StringFlag fluxFilename({"-f", "--fluxfile"}, "flux file to show");
int mainFluxfileLs(int argc, const char* argv[])
{
flags.parseFlags(argc, argv);
if (!fluxFilename.isSet())
error("you must specify a filename with -f");
fmt::print("{}:\n", fluxFilename.get());
FluxFileProto f = loadFl2File(fluxFilename.get());
for (auto* s :
{"version", "rotational_period_ms", "drive_type", "format_type"})
fmt::print(" {}: {}\n", s, getProtoByString(&f, s));
for (const auto& trackFlux : f.track())
{
fmt::print(" flux for c{}h{}:", trackFlux.track(), trackFlux.head());
for (const auto& flux : trackFlux.flux())
{
Fluxmap fluxmap(flux);
if (&flux != &trackFlux.flux()[0])
fmt::print(",");
fmt::print(" {:0.3f}ms", fluxmap.duration() / 1000000.0);
}
fmt::print("\n");
}
return 0;
}

62
src/fe-fluxfilerm.cc Normal file
View File

@@ -0,0 +1,62 @@
#include "lib/core/globals.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/data/sector.h"
#include "lib/config/proto.h"
#include "lib/data/locations.h"
#include "lib/data/flux.h"
#include "lib/external/fl2.h"
#include "lib/external/fl2.pb.h"
#include "src/fluxengine.h"
#include <fstream>
static FlagGroup flags;
static StringFlag fluxFilename({"-f", "--fluxfile"}, "flux file to show");
static StringFlag tracksFlag({"-t", "--tracks"}, "tracks to remove");
int mainFluxfileRm(int argc, const char* argv[])
{
flags.parseFlags(argc, argv);
if (!fluxFilename.isSet())
error("you must specify a filename with -f");
fmt::print("{}:\n", fluxFilename.get());
FluxFileProto f = loadFl2File(fluxFilename.get());
bool changed = false;
for (const auto& location : parseCylinderHeadsString(tracksFlag))
{
auto* repeatedFlux = f.mutable_track();
bool found = false;
for (int i = 0; i < repeatedFlux->size(); i++)
{
const auto& trackFlux = repeatedFlux->Get(i);
if ((trackFlux.track() == location.cylinder) &&
(trackFlux.head() == location.head))
{
fmt::print(
" removing c{}h{}\n", location.cylinder, location.head);
repeatedFlux->DeleteSubrange(i, 1);
found = changed = true;
i--;
}
}
if (!found)
fmt::print(" location c{}h{} not found\n",
location.cylinder,
location.head);
}
if (changed)
{
fmt::print("writing back file\n");
saveFl2File(fluxFilename.get(), f);
}
else
fmt::print("file not modified\n");
return 0;
}

View File

@@ -21,9 +21,7 @@ static StringFlag sourceFlux({"--source", "-s"},
globalConfig().setFluxSource(value);
});
static IntFlag trackFlag({"--cylinder", "-c"}, "Track to read.", 0);
static IntFlag headFlag({"--head", "-h"}, "Head to read.", 0);
static StringFlag destTracks({"--tracks", "-t"}, "tracks to write to", "c0h0");
static SettableFlag dumpFluxFlag(
{"--dump-flux", "-F"}, "Dump raw magnetic disk flux.");
@@ -135,7 +133,10 @@ int mainInspect(int argc, const char* argv[])
flags.parseFlagsWithConfigFiles(argc, argv, {});
auto fluxSource = FluxSource::create(globalConfig());
const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next();
auto tracks = parseCylinderHeadsString(destTracks);
if (tracks.size() != 1)
error("you must specify exactly one track");
const auto fluxmap = fluxSource->readFlux(tracks[0])->next();
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
fluxmap->bytes(),

View File

@@ -16,7 +16,7 @@ static StringFlag sourceFlux({"-s", "--source"},
globalConfig().setFluxSource(value);
});
static IntFlag track({"--cylinder", "-c"}, "track to seek to", 0);
static IntFlag track({"--cylinder", "-t"}, "track to seek to", 0);
extern const std::map<std::string, std::string> readables;

View File

@@ -7,6 +7,9 @@ typedef int command_cb(int agrc, const char* argv[]);
extern command_cb mainAnalyseDriveResponse;
extern command_cb mainAnalyseLayout;
extern command_cb mainConvert;
extern command_cb mainFluxfileLs;
extern command_cb mainFluxfileRm;
extern command_cb mainFluxfileCp;
extern command_cb mainFormat;
extern command_cb mainGetDiskInfo;
extern command_cb mainGetFile;
@@ -36,6 +39,7 @@ struct Command
};
static command_cb mainAnalyse;
static command_cb mainFluxfile;
static command_cb mainTest;
// clang-format off
@@ -45,6 +49,7 @@ static std::vector<Command> commands =
{ "analyse", mainAnalyse, "Disk and drive analysis tools." },
{ "read", mainRead, "Reads a disk, producing a sector image.", },
{ "write", mainWrite, "Writes a sector image to a disk.", },
{ "fluxfile", mainFluxfile, "Flux file manipulation operations.", },
{ "format", mainFormat, "Format a disk and make a file system on it.", },
{ "rawread", mainRawRead, "Reads raw flux from a disk. Warning: you can't use this to copy disks.", },
{ "rawwrite", mainRawWrite, "Writes a flux file to a disk. Warning: you can't use this to copy disks.", },
@@ -71,9 +76,16 @@ static std::vector<Command> analysables =
static std::vector<Command> testables =
{
{ "bandwidth", mainTestBandwidth, "Measures your USB bandwidth.", },
{ "devices", mainTestDevices, "Displays all detected devices.", },
{ "voltages", mainTestVoltages, "Measures the FDD bus voltages.", },
{ "bandwidth", mainTestBandwidth, "Measures your USB bandwidth.", },
{ "devices", mainTestDevices, "Displays all detected devices.", },
{ "voltages", mainTestVoltages, "Measures the FDD bus voltages.", },
};
static std::vector<Command> fluxfileables =
{
{ "ls", mainFluxfileLs, "Lists the contents of a flux file.", },
{ "rm", mainFluxfileRm, "Removes flux from a flux file.", },
{ "cp", mainFluxfileCp, "Copies flux from one flux file to another." },
};
// clang-format on
@@ -122,6 +134,11 @@ static int mainTest(int argc, const char* argv[])
return mainExtended(testables, "test", argc, argv);
}
static int mainFluxfile(int argc, const char* argv[])
{
return mainExtended(fluxfileables, "fluxfile", argc, argv);
}
static void globalHelp()
{
std::cout << "fluxengine: syntax: fluxengine <command> [<flags>...]\n"

0
src/fluxfile.cc Normal file
View File

4
src/fluxfile.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
extern void parseFluxfilePath(
const std::string& combined, std::string& filename, std::string& path);

View File

@@ -1,22 +0,0 @@
comment: 'Adjust configuration for a 40-track drive'
is_extension: true
documentation:
<<<
This is an extension profile; adding this to the command line will configure
FluxEngine to read from 40-track, 48tpi 5.25" drives. You have to tell it because there is
no way to detect this automatically.
For example:
```
fluxengine read ibm --180 40track_drive
```
>>>
drive {
tracks: "c0-40h0-1"
drive_type: DRIVETYPE_40TRACK
}

View File

@@ -0,0 +1,77 @@
comment: 'Options which can be applied everywhere.'
is_extension: true
option_group {
comment: "Drive type"
name: "drivetype"
option {
name: "80"
comment: '80 track drive'
set_by_default: true
config {
}
}
option {
name: "40"
comment: '40 track drive'
config {
drive {
tracks: "c0-40h0-1"
drive_type: DRIVETYPE_40TRACK
}
}
}
option {
name: "160"
comment: '160 track Apple II drive'
config {
drive {
tracks: "c0-159h0"
drive_type: DRIVETYPE_APPLE2
}
}
}
}
option_group {
comment: 'Bus interface'
name: "bus"
option {
name: "pc"
comment: 'PC drive interface'
set_by_default: true
}
option {
name: "shugart"
comment: 'Shugart bus interface (only on Greaseweazle)'
config {
usb {
greaseweazle {
bus_type: SHUGART
}
}
}
}
option {
name: "appleii"
comment: 'Apple II bus interface (only on Greaseweazle)'
config {
usb {
greaseweazle {
bus_type: APPLE2
}
}
}
}
}

View File

@@ -1,29 +0,0 @@
comment: 'Adjust configuration for a 40-track Apple II drive'
is_extension: true
documentation:
<<<
This is an extension profile; adding this to the command line will configure
FluxEngine to adjust the pinout and track spacing to work with an Apple II
drive. This only works on Greaseweazle hardware and requires a custom
connector.
For example:
```
fluxengine read apple2 --160 apple2_drive
```
>>>
usb {
greaseweazle {
bus_type: APPLE2
}
}
drive {
tracks: "c0-159h0"
drive_type: DRIVETYPE_APPLE2
}

View File

@@ -3,14 +3,13 @@ from build.c import cxxlibrary
from scripts.build import protoencode
formats = [
"40track_drive",
"_global_options",
"acornadfs",
"acorndfs",
"aeslanier",
"agat",
"amiga",
"ampro",
"apple2_drive",
"apple2",
"atarist",
"bk",
@@ -33,7 +32,6 @@ formats = [
"psos",
"rolandd20",
"rx50",
"shugart_drive",
"smaky6",
"tartu",
"ti99",
@@ -58,6 +56,7 @@ protoencode(
name="formats_cc",
srcs={name: f"./{name}.textpb" for name in formats},
proto="ConfigProto",
include="lib/config/config.pb.h",
symbol="formats",
)

View File

@@ -1,22 +0,0 @@
comment: 'Adjust configuration for a Shugart drive'
is_extension: true
documentation:
<<<
This is an extension profile; adding this to the command line will configure
FluxEngine to adjust the pinout to work with a Shugart drive. This only works
on Greaseweazle hardware.
For example:
```
fluxengine read ibm --720 shugart_drive
```
>>>
usb {
greaseweazle {
bus_type: SHUGART
}
}

View File

@@ -57,13 +57,13 @@ cxxprogram(
if config.osx:
simplerule(
name="fluxengine_pkg",
name="fluxengine_app_zip",
ins=[
".+gui",
"extras+fluxengine_icns",
"extras+fluxengine_template",
],
outs=["=FluxEngine.pkg"],
outs=["=FluxEngine.app.zip"],
commands=[
"rm -rf $[outs[0]]",
"unzip -q $[ins[2]]", # creates FluxEngine.app
@@ -79,7 +79,20 @@ if config.osx:
"cp $$(brew --prefix abseil)/LICENSE FluxEngine.app/Contents/libs/abseil.txt",
"cp $$(brew --prefix libtiff)/LICENSE.md FluxEngine.app/Contents/libs/libtiff.txt",
"cp $$(brew --prefix zstd)/LICENSE FluxEngine.app/Contents/libs/zstd.txt",
"pkgbuild --quiet --install-location /Applications --component FluxEngine.app $[outs[0]]",
"zip -rq $[outs[0]] FluxEngine.app",
],
label="MKAPP",
)
simplerule(
name="fluxengine_pkg",
ins=[
".+fluxengine_app_zip",
],
outs=["=FluxEngine.pkg"],
commands=[
"unzip -q $[ins[0]]",
"pkgbuild --quiet --install-location /Applications --component FluxEngine.app $[outs[0]]",
],
label="MKPKG",
)

View File

@@ -26,6 +26,7 @@ protoencode(
name="drivetypes_cc",
srcs={name: f"./{name}.textpb" for name in drivetypes},
proto="ConfigProto",
include="lib/config/config.pb.h",
symbol="drivetypes",
)

View File

@@ -40,44 +40,39 @@ tests = [
"vfs",
]
protoencode_single(
name="testproto_cc",
srcs=["./testproto.textpb"],
proto="TestProto",
include="tests/testproto.pb.h",
symbol="testproto_pb",
)
export(
name="tests",
deps=[
test(
name="proto_test",
name=f"{n}_test",
command=cxxprogram(
name="proto_test_exe",
name=f"{n}_test_exe",
srcs=[
"./proto.cc",
protoencode_single(
name="testproto_cc",
srcs=["./testproto.textpb"],
proto="TestProto",
symbol="testproto_pb",
),
f"./{n}.cc",
".+testproto_cc",
],
deps=[
"lib/external+fl2_proto_lib",
"+fmt_lib",
"+protobuf_lib",
"+protocol",
"+z_lib",
".+test_proto_lib",
"dep/adflib",
"dep/agg",
"dep/fatfs",
"dep/hfsutils",
"dep/libusbp",
"dep/snowhouse",
"dep/stb",
"lib/config",
"lib/core",
"lib/data",
"lib/fluxsource+proto_lib",
"src/formats",
"dep/alphanum",
],
),
),
)
for n in ["proto"]
]
+ [
test(

View File

@@ -55,14 +55,14 @@ static void test_option_validity()
}
)M");
AssertThat(
globalConfig().isOptionValid(globalConfig().findOption("option1")),
AssertThat(globalConfig().isOptionValid(
*globalConfig().findOption("option1").option),
Equals(true));
AssertThat(
globalConfig().isOptionValid(globalConfig().findOption("option2")),
AssertThat(globalConfig().isOptionValid(
*globalConfig().findOption("option2").option),
Equals(false));
AssertThat(
globalConfig().isOptionValid(globalConfig().findOption("option3")),
AssertThat(globalConfig().isOptionValid(
*globalConfig().findOption("option3").option),
Equals(true));
}

View File

@@ -4,6 +4,7 @@
#include "lib/config/config.pb.h"
#include "lib/config/proto.h"
#include "snowhouse/snowhouse.h"
#include "dep/alphanum/alphanum.h"
#include <google/protobuf/text_format.h>
#include <assert.h>
#include <regex>
@@ -28,8 +29,9 @@ static void test_setting(void)
setProtoByString(&config, "d", "5.5");
setProtoByString(&config, "f", "6.7");
setProtoByString(&config, "m.s", "string");
setProtoByString(&config, "r.s", "val1");
setProtoByString(&config, "r.s", "val2");
setProtoByString(&config, "r[0].s", "val1");
setProtoByString(&config, "r[0].s", "val2");
setProtoByString(&config, "r[1].s", "val3");
setProtoByString(&config, "firstoption.s", "1");
setProtoByString(&config, "secondoption.s", "2");
@@ -48,6 +50,9 @@ static void test_setting(void)
r {
s: "val2"
}
r {
s: "val3"
}
secondoption {
s: "2"
}
@@ -70,8 +75,14 @@ static void test_getting(void)
r {
s: "val2"
}
r {
s: "val3"
}
secondoption {
s: "2"
r: 0
r: 1
r: 2
}
)M";
@@ -86,9 +97,11 @@ static void test_getting(void)
AssertThat(getProtoByString(&tp, "d"), Equals("5.5"));
AssertThat(getProtoByString(&tp, "f"), Equals("6.7"));
AssertThat(getProtoByString(&tp, "m.s"), Equals("string"));
AssertThat(getProtoByString(&tp, "r.s"), Equals("val2"));
AssertThat(getProtoByString(&tp, "r[0].s"), Equals("val2"));
AssertThat(getProtoByString(&tp, "r[1].s"), Equals("val3"));
AssertThrows(
ProtoPathNotFoundException, getProtoByString(&tp, "firstoption.s"));
AssertThat(getProtoByString(&tp, "secondoption.r[2]"), Equals("2"));
AssertThat(getProtoByString(&tp, "secondoption.s"), Equals("2"));
}
@@ -131,8 +144,30 @@ static void test_load(void)
static void test_fields(void)
{
TestProto proto;
auto fields = findAllProtoFields(&proto);
AssertThat(fields.size(), Equals(14));
auto fields = findAllPossibleProtoFields(proto.GetDescriptor());
std::vector<std::string> fieldNames;
for (const auto& e : fields)
fieldNames.push_back(e.first);
AssertThat(fieldNames,
Equals(std::vector<std::string>{"d",
"f",
"firstoption",
"firstoption.r[]",
"firstoption.s",
"i32",
"i64",
"m",
"m.r[]",
"m.s",
"r[]",
"r[].r[]",
"r[].s",
"secondoption",
"secondoption.r[]",
"secondoption.s",
"u32",
"u64"}));
}
static void test_options(void)
@@ -145,6 +180,52 @@ static void test_options(void)
AssertThat(s, Equals("i64"));
}
static void test_findallfields(void)
{
std::string s = R"M(
i64: -1
i32: -2
u64: 3
u32: 4
d: 5.5
f: 6.7
m {
s: "string"
}
r {
s: "val2"
}
r {
s: "val3"
}
secondoption {
s: "2"
}
)M";
TestProto proto;
if (!google::protobuf::TextFormat::MergeFromString(cleanup(s), &proto))
error("couldn't load test proto");
auto fields = findAllProtoFields(&proto);
std::vector<std::string> fieldNames;
for (const auto& e : fields)
fieldNames.push_back(e.path());
std::ranges::sort(fieldNames, doj::alphanum_less<std::string>());
AssertThat(fieldNames,
Equals(std::vector<std::string>{"d",
"f",
"i32",
"i64",
"m.s",
"r[0].s",
"r[1].s",
"secondoption.s",
"u32",
"u64"}));
}
int main(int argc, const char* argv[])
{
try
@@ -155,6 +236,7 @@ int main(int argc, const char* argv[])
test_load();
test_fields();
test_options();
test_findallfields();
}
catch (const ErrorException& e)
{

View File

@@ -2,23 +2,25 @@ syntax = "proto2";
import "lib/config/common.proto";
message TestProto {
message SubMessageProto {
optional string s = 1;
}
message TestProto
{
message SubMessageProto
{
optional string s = 1;
repeated int32 r = 2;
}
optional int64 i64 = 1 [(help)="i64"];
optional int32 i32 = 2;
optional uint64 u64 = 3;
optional uint32 u32 = 4;
optional double d = 5;
optional double f = 11;
optional SubMessageProto m = 6;
repeated SubMessageProto r = 7;
optional int64 i64 = 1 [(help) = "i64"];
optional int32 i32 = 2;
optional uint64 u64 = 3;
optional uint32 u32 = 4;
optional double d = 5;
optional double f = 11;
optional SubMessageProto m = 6;
repeated SubMessageProto r = 7;
oneof alt {
SubMessageProto firstoption = 8;
SubMessageProto secondoption = 9;
}
}