Compare commits

..

3 Commits

Author SHA1 Message Date
dg
0457282d47 Smoke test of the GUI, at last on Linux. 2021-12-05 18:26:56 +00:00
dg
06d8609200 Raw import of libui. 2021-12-05 18:00:47 +00:00
David Given
fa975b5511 Merge 194b9b1193 into 5f5f22c82b 2021-12-05 17:51:27 +00:00
1230 changed files with 95919 additions and 157117 deletions

View File

@@ -1,44 +0,0 @@
---
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: Left
AlignEscapedNewlines: Left
AllowAllArgumentsOnNextLine: 'true'
AllowAllConstructorInitializersOnNextLine: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'true'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakBeforeBraces: Allman
BreakConstructorInitializers: 'AfterColon'
BreakInheritanceList: AfterColon
BreakStringLiterals: 'true'
ColumnLimit: '80'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
FixNamespaceComments: 'false'
IncludeBlocks: Preserve
IndentCaseLabels: 'true'
IndentWidth: '4'
IndentWrappedFunctionNames: 'false'
KeepEmptyLinesAtTheStartOfBlocks: 'true'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'false'
SortUsingDeclarations: 'true'
SpaceAfterTemplateKeyword: 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCtorInitializerColon: 'false'
SpaceBeforeInheritanceColon: 'true'
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: 'false'
...

View File

@@ -2,100 +2,53 @@ name: C/C++ CI
on: [push] on: [push]
concurrency:
group: environment-${{ github.head_ref }}
cancel-in-progress: true
jobs: jobs:
build-linux: build-linux:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v1
with: with:
repository: 'davidgiven/fluxengine' fetch-depth: 1
path: 'fluxengine'
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine-testdata'
path: 'fluxengine-testdata'
- name: apt - name: apt
run: | run: sudo apt update && sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build protobuf-compiler
sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev libprotobuf-dev wx-common
- name: make - name: make
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j`nproc` -C fluxengine run: make
build-macos-current: build-macos:
strategy: runs-on: macos-latest
matrix:
runs-on: [macos-13, macos-latest]
runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
with: with:
repository: 'davidgiven/fluxengine' fetch-depth: 1
path: 'fluxengine'
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine-testdata'
path: 'fluxengine-testdata'
- name: brew - name: brew
run: | run: brew install sqlite pkg-config libusb ninja protobuf truncate
brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make - name: make
run: gmake -C fluxengine -j2 run: make
- 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
build-windows: build-windows:
runs-on: windows-latest runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps: steps:
- name: setup WSL - uses: msys2/setup-msys2@v2
run: |
curl -L https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/releases/download/40.1.0/Fedora-Remix-for-WSL-SL_40.1.0.0_x64_arm64.msixbundle -o fedora.msixbundle
unzip fedora.msixbundle Fedora-Remix-for-WSL-SL_40.1.0.0_x64.msix
unzip Fedora-Remix-for-WSL-SL_40.1.0.0_x64.msix install.tar.gz
wsl --update
wsl --set-default-version 2
wsl --import fedora fedora install.tar.gz
wsl --set-default fedora
wsl sh -c 'dnf -y install https://github.com/rpmsphere/noarch/raw/master/r/rpmsphere-release-40-1.noarch.rpm'
wsl sh -c 'dnf -y install --setop=install_weak_deps=False gcc gcc-c++ protobuf-c-compiler protobuf-devel fmt-devel systemd-devel sqlite-devel wxGTK-devel mingw32-gcc mingw32-gcc-c++ mingw32-zlib-static mingw32-protobuf-static mingw32-sqlite-static mingw32-wxWidgets3-static mingw32-libpng-static mingw32-libjpeg-static mingw32-libtiff-static mingw32-nsis png2ico'
- name: fix line endings
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v4
with: with:
repository: 'davidgiven/fluxengine' update: true
path: 'fluxengine' msystem: MINGW32
install: >-
- uses: actions/checkout@v4 make
ninja
mingw-w64-i686-libusb
mingw-w64-i686-sqlite3
mingw-w64-i686-zlib
mingw-w64-i686-gcc
zip
mingw-w64-i686-protobuf
vim
- uses: actions/checkout@v1
with: with:
repository: 'davidgiven/fluxengine-testdata' fetch-depth: 1
path: 'fluxengine-testdata' - name: build
- name: run
run: | run: |
wsl sh -c 'make -C fluxengine BUILDTYPE=windows -j$(nproc)' make
- name: nsis
run: |
wsl sh -c 'cd fluxengine && strip fluxengine.exe -o fluxengine-stripped.exe'
wsl sh -c 'cd fluxengine && strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe'
wsl sh -c 'cd fluxengine && makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi'
- name: zip
run: |
wsl sh -c 'cd fluxengine && zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe'
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}.windows.zip
path: fluxengine/fluxengine-windows.zip

View File

@@ -1,9 +1,5 @@
name: Autorelease name: Autorelease
concurrency:
group: environment-release-${{ github.head_ref }}
cancel-in-progress: true
on: on:
push: push:
branches: branches:
@@ -12,57 +8,42 @@ on:
jobs: jobs:
dev-release: dev-release:
runs-on: windows-latest runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps: steps:
- name: setup WSL - uses: msys2/setup-msys2@v2
run: |
curl -L https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/releases/download/39.0.1/Fedora-Remix-for-WSL-SL_39.0.1.0_x64_arm64.msixbundle -o fedora.msixbundle
unzip fedora.msixbundle Fedora-Remix-for-WSL-SL_39.0.1.0_x64.msix
unzip Fedora-Remix-for-WSL-SL_39.0.1.0_x64.msix install.tar.gz
wsl --update
wsl --set-default-version 2
wsl --import fedora fedora install.tar.gz
wsl --set-default fedora
wsl sh -c 'dnf -y install https://github.com/rpmsphere/noarch/raw/master/r/rpmsphere-release-38-1.noarch.rpm'
wsl sh -c 'dnf -y install --setop=install_weak_deps=False gcc gcc-c++ protobuf-c-compiler protobuf-devel fmt-devel systemd-devel sqlite-devel wxGTK-devel mingw32-gcc mingw32-gcc-c++ mingw32-zlib-static mingw32-protobuf-static mingw32-sqlite-static mingw32-wxWidgets3-static mingw32-libpng-static mingw32-libjpeg-static mingw32-libtiff-static mingw32-nsis png2ico'
- name: fix line endings
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v4
with: with:
repository: 'davidgiven/fluxengine' update: true
path: 'fluxengine' msystem: MINGW32
install: >-
- name: run make
ninja
mingw-w64-i686-libusb
mingw-w64-i686-sqlite3
mingw-w64-i686-zlib
mingw-w64-i686-gcc
zip
mingw-w64-i686-protobuf
vim
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: build
run: | run: |
wsl sh -c 'cd fluxengine && make BUILDTYPE=windows -j$(nproc)' make
- name: nsis
run: |
wsl sh -c 'cd fluxengine && strip fluxengine.exe -o fluxengine-stripped.exe'
wsl sh -c 'cd fluxengine && strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe'
wsl sh -c 'cd fluxengine && makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi'
- name: zip - name: zip
run: | run: |
wsl sh -c 'cd fluxengine && zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe' zip -9 fluxengine.zip fluxengine.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
- name: date - name: date
run: | run: |
echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV} echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV}
- name: tag - name: tag
uses: EndBug/latest-tag@latest uses: EndBug/latest-tag@latest
with: with:
tag-name: dev tag-name: dev
force-branch: false
git-directory: 'fluxengine'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: delete-old-assets - name: delete-old-assets
uses: mknejp/delete-release-assets@v1 uses: mknejp/delete-release-assets@v1
with: with:
@@ -70,62 +51,13 @@ jobs:
tag: dev tag: dev
assets: | assets: |
fluxengine.zip fluxengine.zip
fluxengine-installer.exe
fail-if-no-assets: false fail-if-no-assets: false
- name: release - name: release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
name: Development build ${{ env.RELEASE_DATE }} name: Development build ${{ env.RELEASE_DATE }}
files: | files: |
fluxengine/fluxengine.zip fluxengine.zip
fluxengine/fluxengine-installer.exe
tag_name: dev tag_name: dev
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-macos:
strategy:
matrix:
runs-on: [macos-13, macos-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: |
gmake -j2
mv FluxEngine.pkg FluxEngine-${{ runner.arch }}.pkg
- name: tag
uses: EndBug/latest-tag@latest
with:
tag-name: dev
force-branch: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: delete-old-assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ github.token }}
tag: dev
assets: |
FluxEngine-${{ runner.arch }}.pkg
fail-if-no-assets: false
- name: release
uses: softprops/action-gh-release@v1
with:
name: Development build ${{ env.RELEASE_DATE }}
files: |
FluxEngine-${{ runner.arch }}.pkg
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

8
.gitignore vendored
View File

@@ -1,10 +1,2 @@
.obj .obj
.project .project
/.ninja*
/brother120tool
/brother120tool-*
/brother240tool
/brother240tool-*
/fluxengine
/fluxengine-*
/upgrade-flux-file

View File

@@ -1,5 +1,4 @@
.obj .obj
.git
streams streams
.*\.flux .*\.flux
.*\.img .*\.img

View File

@@ -1,250 +1,250 @@
:400000000080002011000000A1100000A1100000064A08B5136843F020031360044B1A6803F53F5302331A6001F058F8E8460040FA46004010B5054C237833B9044B13B173 :4000000000800020110000004110000041100000064A08B5136843F020031360044B1A6803F53F5302331A6001F028F8E8460040FA46004010B5054C237833B9044B13B163
:400040000448AFF300800123237010BD6081FF1F00000000F0380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000021 :400040000448AFF300800123237010BD6881FF1F00000000C8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000041
:400080006481FF1FF0380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8081FF1FA4 :400080006C81FF1FC8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1FBC
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8081FF1F3F000080114BDA68196919B905 :4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080114BDA68196919B9FD
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F082B9704700BF34 :4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F046B9704700BF70
:400140008081FF1F10B5C4B2204601F089F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002BEB :400140008881FF1F10B5C4B2204601F059F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002B13
:400180000CBF02230023002814BF184643F0010002F0DAFB62B608BD8081FF1F38B50446C5B2284602F0C4F8062002F099FD44F00200C0B202F0BCF8062002F091FD284602 :400180000CBF02230023002814BF184643F0010002F082FE62B608BD8881FF1F38B50446C5B2284602F0B2F8062002F0CFFA44F00200C0B202F0AAF8062002F0C7FA28460D
:4001C00002F0B6F8BDE83840062002F073BD10B5642402F0A7F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F035F9012805D0204601F04EFAE3 :4001C00002F0A4F8BDE83840062002F0A9BA10B5642402F095F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F005F9012805D0204601F01EFA34
:400200002846FFF79FFF204601F032F9314605460246204601F0EEF9204601F021F90028FAD1284670BD000038B5044D0024285D013402F005FD402CF9D138BDA481FF1F75 :400200002846FFF79FFF204601F002F9314605460246204601F0BEF9204601F0F1F80028FAD1284670BD000038B5044D0024285D013402F03BFA402CF9D138BDAC81FF1FCB
:4002400008B502F0FDF9002002F006FA02F018FA02F022FA80B208BD10B50446012002F07FF8642002F026FDFFF7EAFF2080002002F076F8642002F01DFDFFF7E1FF608047 :4002400008B502F055FC002002F05EFC02F070FC02F07AFC80B208BD10B50446012002F06DF8642002F05CFAFFF7EAFF2080002002F064F8642002F053FAFFF7E1FF60809C
:4002800010BD08B502F008FB002002F011FB02F023FB02F02DFB80B208BD10B50446FFF796FF322002F006FDFFF7EBFF20800120FFF774FF322002F0FDFCFFF7E2FF6080A1 :4002800010BD08B502F060FD002002F069FD02F07BFD02F085FD80B208BD10B50446FFF796FF322002F03CFAFFF7EBFF20800120FFF774FF322002F033FAFFF7E2FF6080D2
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0F4FE02A802F0BEF802F0C8F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF62787E :4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0E0FE02A802F07CF802F086F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF627816
:400300002146BDE81040042001F002B90C3A000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0ECFF79 :400300002146BDE81040042001F0D2B8DC38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0DAFFEE
:40034000002002F083F8002384F8643010BD00BF8081FF1F38B5124D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F8653022790B4B1A71A378002B14BF38 :40034000002002F06DFD002384F8643010BD00BF8881FF1F38B5104D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F865302279094B1A71A378002B14BF45
:400380000220012002F062F8E078B0FA80F0400902F0D4FA2079BDE8384002F0DBBA00BF8081FF1FE581FF1F38B50D4C94F8645065B904F16500FFF7CDFF012001F0ACFF17 :400380000220012002F04CFDE07802F043FD2079BDE8384002F07ABD8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7D1FF012001F09EFF4FF47A7002F0B0F968
:4003C0004FF47A7002F076FC84F86A50E368E366012384F86430BDE8384002F0A9BC00BF8081FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF763FFFFF7E7FE5C :4003C00084F86A50E368E366012384F86430BDE8384002F0E3B900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF767FFFFF7EBFE0120002384F86A007C
:400400000120002384F86A00236702F069FC2A46216F1848FFF755FF144E0027236F9D4216D001F07FFF00B13767236F9D4205DD0120FFF7B3FE336F013305E005DA002094 :40040000236702F0A3F92A46216F1848FFF759FF144E0027236F9D4216D001F071FF00B13767236F9D4205DD0120FFF7B7FE336F013305E005DA0020FFF7B0FE336F013B0B
:40044000FFF7ACFE336F013B336702F071FCE5E7322002F02FFC2A2DCCBF0020012002F07FFABDE8F8400448FFF72BBF8081FF1F193A0000203A00003D3A00002DE9F04F9B :40044000336702F0ABF9E5E7322002F069F92A2DCCBF0020012002F025FDBDE8F8400448FFF72FBF8881FF1FE9380000F03800000D3900002DE9F04F99B062B602F0F8F947
:4004800099B062B602F0BEFC9F49042002F0E2FC9E4801F053FF9E4801F084FF9D4801F0B1FF02F06DF902F03FF8002001F0CEFF01F0D2FF0221002000F088FF012001F05D :400480009E49042002F01CFA9D4801F045FF9D4802F0E8FC9C4801F079FF02F0C9FB02F09BFA002002F0BCFC01F094FF0221002000F05CFF954C012001F0D4F8002384F890
:4004C00001F9954D0321084602F012FC2E462C4602F02EFC95F8643043B1EA6EEB689B1A41F28832934201D9FFF722FF00F0A6FF18B98A48FFF7E5FE04E000F0A5FF0028D3 :4004C0006730FFF76DFFFFF782FE84F87400FFF72FFF012384F86730FFF762FFFFF777FE84F87500FFF724FF894B94F87400894994F875202546002A14BF0A461A4600286F
:40050000F7D134E000F09AFF10B902F011FCF9E78348FFF7D6FE032001F0C0F88148FFF7D0FE002386F86730FFF73EFFFFF74FFE86F87400FFF7FCFE012386F86730FFF701 :4005000008BF19468448FFF7DCFE0321084602F025F9264602F042F994F8643043B1EA6EEB689B1A41F28832934201D9FFF700FF00F054FF18B97948FFF7C3FE04E000F079
:4005400033FFFFF744FE86F87500FFF7F1FE96F874007549754B96F87520002A14BF0A461A46002808BF19467148FFF7AAFE032000F076FF0128ABD16E48FFF7EBFE6E49CB :4005400053FF0028F7D10BE000F048FF10B902F025F9F9E77248FFF7B4FE032001F06EF8032000F04DFF0128D4D16E48FFF7F2FE6D490320FFF738FE94F876106B48FFF7F9
:400580000320FFF731FE94F876106C48FFF799FE94F87630023B142B00F2D783DFE813F01500D5031E00D5032400D5035000D5037600D503DA00D503C201D5030903D50356 :40058000A0FE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D4033303D4034D0303238DF851
:4005C0002D03D5033403D5034E0303238DF820308DF8213011238DF822302BE394F87800FFF700FF564B22E340F2DC57FFF7DCFE00232375E068227D02F0FF012AB9EB680B :4005C00020308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD0B4611E083B100227E
:400600001B1ABB42F7DD0B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F9E2BF :40060000174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF786FD4FF000080DF1200A54
:400640000220FFF77FFD4FF000080DF1200A02F06FFB4FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76BFD3A465146022000F04CFFB9F10109EBD108F10F :4006400002F0ACF84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF772FD3A465146022000F023FFB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF076
:400680000108B8F1400FE2D12E4B38E04FF0010A4FF000080DF1200B02F04AFB4FF0000959460120FFF7A0FD08EB090300270493049B1BF807203B44DBB29A4209D08DE89D :40068000010A4FF000080DF1200B02F087F84FF0000959460120FFF7A7FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B464A461F48FFF701FEE0
:4006C0000C0041463B464A461F48FFF7FAFD4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461748FFF7E7FDBAF1000F00F01C81144B1B883D :4006C0004FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EEFDBAF1000F00F01B81144B1B8807A8ADF81C3095E200BF5501000004
:4007000007A8ADF81C3096E255010000F900000091000000C50000008081FF1F523A0000653A00006F3A00004B3A00004F3A0000823A0000E581FF1FF681FF1F9A3A000052 :40070000F900000091000000C50000008881FF1F1F3900001B390000223900003A3900004D390000ED81FF1FFE81FF1F57390000CC380000CE380000663900008239000026
:40074000F4380000F6380000A93A0000C53A0000F8380000206FFFF745FE94F8780001F0FFFD94F8780001F0E3FD02F015FCB94BDFF8FC821A78002702F0FB021A701A785F :40074000D0380000206FFFF749FE94F8780001F0F5FD94F8780001F0D9FD02F009FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7880
:4007800042F001021A701A7802F0FE021A701A7802F0FE021A7002F003FC0220FFF7D2FC012141F6FF734FF48042084601F0DEFD84F8B60002F02AFA08F807000137402FA7 :4007800002F0FE021A7002F0F7FB0220FFF7DAFC012141F6FF734FF48042084602F046FB84F8B60001F068FF08F807000137402FF8D1DFF8B0A200270AF195091FFA89F816
:4007C000F8D1DFF8B0A200270AF195091FFA89F80137402F14BF3A4600221AF8010F2244062392F82420402102F044FA424646F24E419AF8000002F04FFA08F14008402FB5 :4007C0000137402F14BF3A4600221AF8010F2244062392F82420402101F082FF424646F240419AF8000001F08DFF08F14008402F1FFA88F8E4D196F8793053B196F87C30C8
:400800001FFA88F8E4D196F8793053B196F87C30336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241002F09FF994F82A :40080000336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241001F0DDFE94F8B60001F09BFE012194F8B60001F06EFEAA
:40084000B60002F05DF9012194F8B60002F030F92368002BFCD0002398467360D6F80CA0012702F065FAE368B4F87A20CAEB030393420DD367B1042195F8B60002F08AF9BC :400840002368002BFCD0002398467360D6F80CA0012701F0A3FFE368B4F87A20CAEB030393420DD367B1042195F8B60001F0C8FE94F8B60001F0D4FE0028F9D107463072CF
:4008800094F8B60002F096F90028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF74EFC6968402209EB8111022000F02DFE6A68674B01321340002BCA :40088000237AFBB96A682B689A4202D1002FE0D118E00220FFF756FC6968402209EB8111022000F005FE6A68674B01321340002BBEBF03F1FF3363F03F03013308F1010820
:4008C000BEBF03F1FF3363F03F03013308F101086360C6E70220277AFFF734FC00221146022000F015FE0220FFF72CFCFFB2FFF79BFC002001F01CFD37B15848FFF7E1FC90 :4008C0006360C6E70220277AFFF73CFC00221146022000F0EDFD0220FFF734FCFFB2FFF7A3FC002001F012FD37B15848FFF7E9FC0220FFF70DFD06E0554B08A81B88ADF871
:400900000220FFF705FD06E0554B08A81B88ADF82030FFF7EBFC227D4146237A5148FFF7D0FC15E25048FFF7CCFCD4F87A7017F03F0701D0032009E2286FFFF753FD95F83B :400900002030FFF7F3FC227D4146237A5148FFF7D8FC15E25048FFF7D4FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F003FD95F8780001F0E7FC012054
:40094000780001F00DFD95F8780001F0F1FC012001F098FD02F020FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F0BF :4009400001F002FD02F014FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F003FB01214FF4804341F6FF72084601F0CB
:400980000FFB01214FF4804341F6FF72084601F01DFD85F8B60002F039F908F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8F2 :40098000E9FC85F8B60001F077FE08F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F82420402101F090FEB2
:4009C000010F2244052392F82420402102F052F9414646F2484299F8000002F05DF908F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A903772EA :4009C000414646F2475299F8000001F09BFE08F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF0010A4FF0000A2168C1
:400A00005FEA99190CBF4FF0010A4FF0000A2168114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059368 :400A0000114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F16D040223F2102F079
:400A400002EB8010BAF1000F16D040223F2102F003FB1CE09E6400403F000080CF3A0000FA380000E93A0000FC3A000098640040A481FF1FA381FF1F014601370120FFF7BF :400A4000F7FA1CE0906400403F0000808C390000D2380000A6390000B939000097650040AC81FF1FAB81FF1F014601370120FFF7BBFBC7EB0903D3F1000A4AEB030A216882
:400A8000B3FBC7EB0903D3F1000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F0B4FCE9 :400A8000B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F080FC85F808806B6895F8B6002B4493F82410D9
:400AC00085F808806B6895F8B6002B4493F8241002F054F895F8B60002F012F8012195F8B60001F0E5FF95F87E302B6185F81480237D002BFCD04FF00008012086F814800D :400AC00001F092FD95F8B60001F050FD012195F8B60001F023FD95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F06AFC404601F027FC00E023B1237A54
:400B000001F09EFC404601F0BDFC00E023B1237A5BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F083FC012001F0A8FC002001F083FC042194F8B6004D :400B00005BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F04FFC012001F012FC002001F04FFC042194F8B60001F066FD94F8B60001F072FD8046002873
:400B400002F028F894F8B60002F034F880460028F8D196F8B60001F0C1FF337D327A0293012303920193CDF800A05B463A4649467C48FFF7A6FBC6F81080BAF1000F0BD0C6 :400B4000F8D196F8B60001F0FFFC337D327A0293012303920193CDF800A05B463A4649467C48FFF7AEFBC6F81080BAF1000F0BD0FFF75AFB002001F0C9FB237A63B1764854
:400B8000FFF752FB002001F0D3FB237A63B17648FFF797FB0220D9E0B945F1D073490120FFF722FB0137F7E77148FFF78AFB714B3DE094F8780001F0D3FB206FFFF712FC19 :400B8000FFF79FFB0220D9E0B945F1D073490120FFF72AFB0137F7E77148FFF792FB714B3DE094F8780001F0C9FB206FFFF716FC6D48FFF786FB94F87930236100232375F8
:400BC0006D48FFF77EFB94F87930236100232375237D002BFCD0012001F032FC00233375237D002BFCD0002001F02AFC002363483361FFF766FB624B19E0002084F86A0088 :400BC000237D002BFCD0012001F0FEFB00233375237D002BFCD0002001F0F6FB002363483361FFF76EFB624B19E0002084F86A00FFF7F4FB5F4B12E094F8743023B195F870
:400C0000FFF7F0FB5F4B12E094F8743023B195F875200AB985F8782094F875201AB113B9012385F878305848FFF794FB574B1B88ADF8203008A8FFF759FB89E0FFF778FB0D :400C000075200AB985F8782094F875201AB113B9012385F878305848FFF79CFB574B1B88ADF8203008A8FFF761FB89E0FFF780FB02F07CF8002002F01FF82A2701F04AFFE9
:400C400001F01CFE002001F0BFFD2A2701F0EAFC002001F08DFC3A46002108A802F0FCF917238DF820308DF8217002F061F8002001F052FB002001F0E9FBC82002F01AF8F3 :400C4000002001F0EDFE3A46002108A802F0F0F917238DF820308DF8217001F09FFD002001F048FB002002F0DBF8C82001F058FD0DF12200FFF7F0FA0DF13600FFF70DFB01
:400C80000DF12200FFF7E8FA0DF13600FFF705FB02F04EF8012001F0D9FB322002F00AF80DF12600FFF7D8FA0DF13A00FFF7F5FA012001F031FB4FF4967001F0FBFF02F041 :400C800001F08CFD012002F0CBF8322001F048FD0DF12600FFF7E0FA0DF13A00FFF7FDFA012001F027FB4FF4967001F039FD01F075FD0DF12E00FFF7CFFA0DF14200FFF71B
:400CC00037F80DF12E00FFF7C7FA0DF14200FFF7E4FA002001F020FB4FF4967001F0EAFF02F026F8022001F0B1FB322001F0E2FF0DEB0700FFF7B0FA0DF13E00FFF7CDFAF4 :400CC000ECFA002001F016FB4FF4967001F028FD01F064FD022002F0A3F8322001F020FD0DEB0700FFF7B8FA0DF13E00FFF7D5FA012001F0FFFA4FF4967001F011FD01F040
:400D0000012001F009FB4FF4967001F0D3FF02F00FF80DF13200FFF79FFA0DF14600FFF7BCFA002001F0F8FA4FF4967001F0C2FF01F0FEFF002001F089FB002384F86A302F :400D00004DFD0DF13200FFF7A7FA0DF14600FFF7C4FA002001F0EEFA4FF4967001F000FD01F03CFD002002F07BF8002384F86A3001F07EFF01F050FE74E70120FFF7E8FA91
:400D400001F01EFD01F0F0FB74E70120FFF7E0FA032000F0A3FC0E48FFF7B3FAFFF7B8BB3F000080063B0000363B00003892FF1F403B0000FC380000483B0000563B00000F :400D4000032000F07BFC0E48FFF7BBFAFFF7E2BB3F000080C3390000F33900004092FF1FFD390000D4380000053A0000133A0000D6380000D8380000FE81FF1FDA380000E3
:400D8000FE38000000390000F681FF1F02390000633B00000F4B1A78120616D55878C0B2012814D11B79DBB2042B02D0052B05D07047094A094B5A60282203E0084A074B07 :400D8000203A00002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2FE
:400DC0005A60E0221A8000F043BE002070470120704700BF0060004004390000BC92FF1F2C3900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E807 :400DC000137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0BDF8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A20
:400E000003008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EAA4 :400E0000002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70F9
:400E4000450502F0A1F8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F060 :400E4000654A137843F02003137000E0FEE707FB056300219A881868013502F0E9F8072DF5D15E485E4E002550F8041F05F1105303F1480221F0FF074933C9B20B44520042
:400E80000103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0CDF8072D6A :400E80005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A7B
:400EC000F5D15E485E4E002550F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002390 :400EC000137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212CA9
:400F000013609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A1378DD :400F0000424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B78A7
:400F400043F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4B11 :400F40003D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48221A702E4A402313702E49937013729372082382F81F32BF
:400F8000A2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F095 :400F800020220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F081004800404C0A00480F010049A1460040254200408F
:400FC000030388F800302F4B48221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F077FE284B88F8006044223D70264D1A709B :400FC000224200400440004006400040A2430040A0430040253A0000E8460040FCFFFF478C00004800760040540A0048F846004020760040580A004828760040035001404D
:4010000094E80F0007C52B80BDE8F08100480040680A00480F010049A146004025420040224200400440004006400040A2430040A0430040683B0000E8460040FCFFFF47E7 :401000000C0A0048C0510040180A0048200A00482C0A0048380A004832510040440A0048CF0100491D51004001590040235B0040585B004076580040B0430040F9460040F2
:401040009000004800760040700A0048F846004020760040740A00482876004003500140280A0048C0510040340A00483C0A0048480A0048540A004832510040600A004800 :4010400008B501F0C9FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0B8FF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B5114817
:40108000CF0100491D51004001590040235B0040585B004076580040B0430040F946004008B501F0ADFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC75 :40108000114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F094FFFFF7DAF9D9
:4010C000086005E001F09CFF0C2303604FF0FF33184608BDCC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7E8 :4010C000FEE700BF01000000F43B0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A7814
:40110000114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F078FFFFF7AEF9FEE700BF01000000FC3C0000124A134B10B51A60124A134C1368134843F4007367 :401100000C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1F41100000BC760040C080FF1F08ED00E0F8B501F042
:4011400013600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F087BC00BF0004FA050CED00E0A4 :4011400017FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F903F2
:4011800014ED00E0000000000080FF1FA1100000BC760040C080FF1F08ED00E0F8B501F0FBFE4B4A01271378022643F001031370137C484C43F001031374474B02F5E352FA :4011800023701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0C6
:4011C0001F700B3203F8946C1378054603F07F031370002001F084FD2378404A03F0F90323701378384603F0DF03137023783B43237001F075FD282001F072FD384B304610 :4011C000040323700023137053702F4AFF2199540133092BFBD1284601F0CEFE0721172001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152033
:401200001A7802F07F021A701A7802F0BF021A7023783343237001F063FD2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0B2FE07211720E7 :4012000001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA07210820F8
:4012400001F096FD2949172001F084FD0721182001F08EFD2649182001F07CFD0721152001F086FD2349152001F074FD0721052001F07EFD2049052001F06CFD072106201F :4012400001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D6000401260004076
:4012800001F076FD1D49062001F064FD0721084601F06EFD1A49072001F05CFD0721082001F066FD1749082001F054FD0021162001F05EFD1449162001F04CFD07210C2014 :40128000F851004084600040B592FF1F0B1B000045190000091B00003D1A0000691A0000991A0000D11A0000111B0000851B0000214B224A10B5187000231370204A40209B
:4012C00001F056FDBDE8F84010490C2001F042BDA5430040944300409D60004012600040F851004084600040AD92FF1F6B1B0000A5190000691B00009D1A0000C91A0000BE :4012C0001370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A6020221A601860802018604FF480701860174804702E
:40130000F91A0000311B0000711B0000E51B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A60B1 :401300004FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8202C012001F018FE0D4B04221A7010BDD092FF1FD692FF1F39
:401340004FF080721A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B50 :40134000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F00E100E09E6000409C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031
:4013800003221A70802203F8202C012001F0FCFD0D4B04221A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E6000407A :4013800031462846BDE87040FFF792BF8092FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1F4E
:4013C0009C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A81 :4013C000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F28600040014B1878704700BFD592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F52
:4014000013700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B187898 :40140000024A0C2303FB002040787047DC92FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FBA9
:40144000704700BFCD92FF1F044B1A7802F0FF001AB118780022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C32 :4014400000231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E0002180
:401480005B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032DA1 :40148000084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BDDC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD857
:4014C00018D8DFE805F002070C110021084601F0A1FA0DE00021084601F080FA08E00021084601F05FFA03E00021084601F03EFA054B1855EDB2072D03D801F087FB034B10 :4014C0002F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224BD4
:40150000185538BDD492FF1FA492FF1FAD92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BF8A :401500001A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8D2
:40154000D588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4FEB :40154000040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFE7
:40158000385D01F0ABFA11232946FE2218F8040001F070FB06F5C04278321FFA89F118F8040001F079FB124D18F80410385D01F0E5FA0121385D01F07BFA735D43F002030D :40158000DC92FF1FFC5F0040B592FF1FAC92FF1F706000402DE9F047044615468846002940D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D1DE
:4015C0007355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002945 :4015C00041462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FFE9
:4016000040D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040001F053FA1223FE222946305D01F087 :4016000001211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072BA7
:4016400019FB07F5C0411FFA88F27831305D01F023FBDFF84490315D1AF8040001F08EFA01211AF8040001F023FA17F8093043F0020307F8093017F8093003F0FD0307F881 :401640000AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFDC92FF1FFE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0A
:40168000093002E00D4600E000252846BDE8F087AD92FF1FA492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1F5D :401680000878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D003
:4016C000FE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2EC :4016C00040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1FE1
:40170000E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B18700870CA :401700004993FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B78E1
:4017400030BD00BF4493FF1F4093FF1F00600040BC92FF1FB992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012088 :401740000A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370B3
:4017800013707047CE92FF1FCA92FF1FB992FF1F4093FF1F4193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B155682C :40178000094A137883F080031370084B0B221A7030BD00BF296000404C93FF1F00600040C492FF1F4993FF1FD292FF1FC192FF1F064A06231370064A01201370054B802273
:4017C000215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F006000400F :4017C0001A70054B00221A70704700BFD692FF1FC192FF1FD292FF1F4993FF1F054B9A683AB19A68044910709A680988518000229A607047C492FF1F4C93FF1F08B5124BAC
:40180000BC92FF1F4193FF1FCA92FF1FB992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A68E4 :401800001A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F012FC04E001F0A5FB01E000F046FD10B9034B1C
:401840003AB19A68044910709A680988518000229A607047BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F0CE :4018400003221A7008BD00BF28600040C192FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1F1D
:401880006003202B05D0402B06D043B900F012FC04E001F089FB01E0FFF77CFA10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B012019781388FA :40188000D692FF1FC192FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FCC
:4018C0000B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE8084045 :4018C000C192FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFD692FF1FC192FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B00220120D1
:40190000FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FCA :401900001A70FFF76BFF054B03221A7008BD00BFC092FF1F08600040D692FF1FC192FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF70479E
:40194000B992FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1FC0 :40194000D692FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B066B
:40198000074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF42 :401980001BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD2860004067
:4019C000174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078EB :4019C000C192FF1FD292FF1F4993FF1F29600040054A00231380054A916819B191680B7092685380704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E0D1
:401A0000DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B701D :401A00000B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B012220
:401A400092685380704700BF4493FF1FBC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934229 :401A40001A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBF0F
:401A800038BF0380FFF728FE012008BDBC92FF1FCE92FF1FCA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A73704722 :401A8000DA7E82F08002DA7601225A76704700BF0B600040DC92FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F8272082F0800283F827200122E0
:401AC0000B600040D492FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B21 :401AC00083F82520704700BF0B600040DC92FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BFF5
:401B000093F83230094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F856 :401B00000B600040DC92FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C799F
:401B4000302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF028 :401B4000240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040D5
:401B8000000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425795F :401B8000FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE00222046DB
:401BC0002D0658BF84F801C090700133DBB24908D7E7F0BD9F600040D492FF1F70600040FE5F004000F032BF70B50446184B88B003AA03F11006154618685968083303C530 :401BC0001146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BD4F3A00002DE9F0431C4D01222E460C2093
:401C0000B3422A46F7D11B782B70FCB12223237001AD03232846637001F024F9002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AB1 :401C00001F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880F
:401C4000C1700371417100F10400EAD108B070BD923B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F8059021445B :401C40000132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A70
:401C8000B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1CB :401C80001B7802EBC30253681A7C824286BF03EBC003586900207047D092FF1FB03A00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC368147810
:401CC000054BFF221A70BDE8F08300BFD492FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1FB83B000044 :401CC000007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B01E
:401D00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF829 :401D000009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF20
:401D40000420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000FB6 :401D40004FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFAAB
:401D80000BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F806 :401D8000494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004052
:401DC00008A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288FA :401DC00070600040C292FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDD592FF1F00212DE9F84F0B464E4E0C2707FB01F4013132190929335553
:401E0000C2F307224A71083394E7BDE8F88F00BFCD92FF1FD492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC3008A :401E00004FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0B5
:401E4000406908BDCD92FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B16 :401E4000400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BF50
:401E80001A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C368D0 :401E8000A1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052673
:401EC00003EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78D3 :401EC00009264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B01211860E6
:401F0000BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C20083393423E :401F000000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FCE
:401F400009D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE88B :401F4000D892FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0800F074A1A4414BF8D238923CF
:401F8000F84FFFF767BEBDE8F88F00BFD492FF1FBA92FF1F4293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F08E :401F80009370FFF715BC0020704700BF00600040DC92FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D970294600
:401FC000010191700021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F0D8 :401FC0001201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF9C
:402000007F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D167 :4020000000600040DC92FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD56
:402040008C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE80 :40204000002010BDD592FF1F00600040C292FF1F4A93FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F05D000301030103010301030167
:4020800043681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B5874A874C13780021DBB254 :402080000B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7885
:4020C00021801806517840F188800A2900F20081DFE811F05800FE00FE00FE00FE00FE000B00FE007900FE007D00D3787949012B09D17A4B1A787A4B03EBC2035B685B68FE :4020C00063689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D025
:402100006360122310E0CB78022B12D18878FFF7E5FD002800F0DC80436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B21D16A4B00228878D5B29B :4021000018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F029BF13F0030313D0022B40F0A0802380504B0C21C4
:40214000854203D3634A92783AB910E0187801320028F7D018780344F0E75E4A62499278097C914203D16148FFF73EFD5F4B1A78002A00F0AD801A78228018E0BDE8384099 :402140001B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4AC4
:4021800000F012BF13F0030313D0022B40F0A0802380504B0C211B7903F07F02544B01FB02339A78534BD2B21A7000225A706360BBE702222280504A11784E4AC9B2117026 :402180001344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E082
:4021C00053706260B1E7012323804C4BEFE70123238013794A4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378444ADBB25AE0937803F0FF0153B928 :4021C000481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303EE
:402200003E4B1A7891425FD019703F4B01201870FFF71AFE58E0481EC0B2FFF75FFD0028EED155E0FFF722FF002851D0294A374913791279DBB2D2B20A70354A3049D25C1E :40220000012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9A4
:40224000CB5C9A4240D0304B01221A70FFF758FD3AE003F00303012B2BD009D3022B37D11C4B9B78002B33D1BDE83840FFF7C4BE184B9B78012B2BD11F4A137803F0FD03CA :40224000BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BFEC
:4022800015E003F00303012B13D008D3022B1FD1104B9B78E3B9BDE83840FFF783BE0D4B9B78012B14D1144A137843F0020313700AE0084B1A795AB998781B791549DBB239 :4022800000600040C492FF1FD092FF1FB03A0000143B00009C3A0000873B00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1FC092FF1FD492FF1FD192FF1FF4
:4022C000CA5C22EA0002CA54BDE83840FFF7A0BA002038BD00600040BC92FF1FC892FF1FB83B00001C3C00008F3C00006093FF1FD492FF1F7992FF1FCB92FF1FCD92FF1FBA :4022C0004A93FF1FD792FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB0020704700600040C492FF1F743A0000014B1870704700BF76650040FA
:40230000BA92FF1FB892FF1FCC92FF1FC992FF1F4293FF1FCF92FF1F014B1870704700BF72640040014B1878704700BF68650040014B1870704700BF7A650040064A0123C8 :40230000014B1878704700BF67640040014B1870704700BF77640040064A0123136002F688321268E0211064034A1170A2F540721360704780E100E000E400E0014B187039
:40234000136002F688321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A64004073B515461E460B4C04230022019200920A46014658 :40234000704700BF74640040014B1870704700BF7565004073B515461E460B4C05230022019200920A4601461846237000F064F932462946207800F01FF90221207800F0B7
:402380001846237000F022FC32462946207800F0DDFB0221207800F0C7FB207802B070BDD080FF1F074A0223136002F688321268E0215064044A11706FF440710A44136017 :4023800009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04228E
:4023C000704700BF80E100E001E400E073B515461E460B4C05230022019200920A4601461846237000F0F2FB32462946207800F0ADFB0221207800F097FB207802B070BD84 :4023C0001A60704780E100E0014B1870704700BF74650040704738B505460078012428B100F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDDC
:40240000D180FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E013 :40240000F7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C05
:40244000014B1870704700BF7B650040014B1870704700BF73640040704738B505460078012428B100F038FD285D0134E4B2F8E738BD08B50D2000F02FFDBDE808400A20C6 :4024400000230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C002301250193009322
:4024800000F02ABD014B1870704700BF7865004010B500F081FD204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F0010393712B :402480000A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F026
:4024C00002F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F0AA :4024C000BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB2F7
:402500000803137000F0ECFB064B10222046BDE810401A6000F044BDAB4300400E5900402F5B004080E200E008B500F035FD0F4A137803F0FE031370A2F5AA521D3A13785D :4025000002F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C2540020704780
:4025400003F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F01BBD00BF08590040044A137803F03F0343EA8010C0B2107082 :40254000012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD8074900010B4603441A7942F004021A7150
:40258000704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2E4 :40258000435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E011943815400207047012070471070004041F6FF73994208BFD2
:4025C000883103F6C41393FBF1F305490B60054B1A8070470A590040A43B00004A93FF1F4C93FF1F5093FF1F08B5102000F036FA0721042000F0BCFB0749042000F0AAFB02 :4025C0004FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002088BF0120704700BF197000402C
:40260000064A0C20137843F006031370FFF7BCFF034B00221A8008BDE1260000095900404893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABF7B92FF1FA6 :4026000017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C000B7
:40264000044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E0204600F037FB024B1B78204610BD00BF09590040D9 :4026400001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A00
:40268000034A044B1B881088181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F20223F1 :402680005378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F06BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1FE
:4026C00091FBF3F30028D8BF5B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F057FC214A044613780A2043F001031370137C43F001031374BC :4026C000804303F5F04319705378147001335370BDE8104000F050BC10BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BDB0
:4027000012F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CBE :40270000012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE875
:40274000CE2203F8062CA3F597530222123B1A70094A137843F00803137000F0C1FA074B08222046BDE810401A6000F019BC00BFAB43004006590040275B004080E200E0CF :40274000384000F00DBC00BFEC80FF1F024B1B7A584300F005BC00BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F024E
:4027800008B500F009FC0F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F0BB :402780001A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E008
:4027C000EFBB00BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF095 :4027C000054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC60F4
:40280000000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040AE3B00005693FF1F5C93FF1F5493FF1FFC :402800001C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8492FF1F9D28000010E000E0EC80FF1FC6
:4028400008B5102000F014F90721032000F090FA0749032000F07EFA064A0C20137843F006031370FFF7BCFF034B00221A8008BD39290000015900405893FF1F10B5054C6D :4028400014E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A36179
:4028800023781BB9FFF7DCFF01232370BDE81040FFF728BF7C92FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0AE :40288000BDE81040FFF7E8BF8492FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1FAE
:4028C000002404E0204600F00BFA024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B00400E4A13881BB223B111880A2309B25943E7 :4028C000024B03EB80035868596070478492FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB2436031
:4029000001E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1F5C93FF1F5893FF1F70470000014B1870E9 :40290000127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC46004002010049010100490001004905010049060100490D
:40294000704700BF7B640040014B1870704700BF7F640040014B1870704700BF7C640040014B1870704700BF7E640040F7B516461F460B4C00230325019300930A460146B2 :4029400010B500F015FB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18223F
:402980002846257000F022F93A463146207800F0DDF80221207800F0C7F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F006F917 :402980001A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE8DE
:4029C0003A463146207800F0C1F82946207800F0ABF8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0EAF83A463146207800F06F :4029C00010401A6000F0D8BAAB4300400E5900402F5B004080E200E008B500F0C9FA0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F8E1
:402A0000A5F80221207800F08FF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0CFF832462946207800F08AF80221207800F074F880 :402A00000A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0AFBA00BF08590040044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF9D
:402A4000207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF74 :402A40008223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B33
:402A800006410040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F020028D :402A80001A8070470A590040603A00005293FF1F5493FF1F5893FF1F08B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFFBA
:402AC00001E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004024 :402AC000034B00221A8008BD912B0000095900405093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F0010256
:402B000017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E0119438154A3 :402B00001A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF1D
:402B400000207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF6F :402B40005893FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F0C1
:402B8000034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F04D :402B800000B270475293FF1F5493FF1F5093FF1F7047000010B500F0EBF9214A044613780A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F0DB
:402BC000070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F08B :402BC0000203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A7067
:402C00000F03A370E07010BD012010BD10B500F0C3F90A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0B5F9204610BD00BFE480FF1F33 :402C0000094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0ADB900BFAB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE0313708E
:402C4000030610B5044611D400F0A6F9084AE300117803F1804303F5F04319705378147001335370BDE8104000F09AB910BD00BFE480FF1F30B504060CD411F4704509D1B0 :402C4000A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0361
:402C8000C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D988 :402C800043EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A80AE
:402CC000286800F061F9A4F50044F6E7034B58686043BDE8384000F057B900BFEC80FF1F024B1B7A584300F04FB900BFEC80FF1F0E4B00F003001A78490102F0FC02104300 :402CC00042F210734B4341F2883103F6C41393FBF1F305490B60054B1A807047025900406A3A00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD92
:402D000018701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F08C :402D000007490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BDE92D0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040C8
:402D40000F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B29B :402D4000FFF728BFA192FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204621
:402D8000017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F883 :402D800010BD00BF01590040034A044B1B881088181A00B2704700BF5C93FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107381
:402DC000E82010BD8092FF1F312E000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BFD3 :402DC00000B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475E93FF1F6493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040A1
:402E000010E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8092FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F891 :402E0000034A00F0F800137803431370704700BF06410040014B1870704700BF72640040014B1870704700BF7864004073B515461E460B4C04230022019200920A46014603
:402E4000243003B198470134052CF8D138BD00BF8492FF1F024B03EB80035868596070478092FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A07 :402E400018462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F688321268E0215064044A11706FF440710A4413608D
:402E80000360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC460040B2 :402E8000704700BF80E100E001E400E0014B1870704700BF75640040014B1870704700BF76640040014B1870704700BF79640040FEB5494652465B460EB407462449096800
:402EC000020100490101004900010049050100490601004900000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204936 :402EC0008A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814651460B7823400B705846013000F030F81C
:402F000000F06AF8143883460121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072E00 :402F00003800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF12
:402F4000F2D9002001300138013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F6B :402F40000EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF77
:402F800000C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F021C :402F8000094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3097
:402FC000044B01FB02339B7A00E013790020704700600040D492FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BFA1 :402FC00000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E75C
:40300000014B1868704700BF5C81FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F04EFE084B094C1E46E41AA4100025A54204D056F8253071 :4030000000F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BDCC3B0000CC3B0000CC3B0000D43B000003460244934202D003F8011BFAE770475A
:4030400098470135F8E770BDD43C0000D43C0000D43C0000DC3C000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF432 :4030400030B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F0D4
:403080000273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000022 :4030800073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46D9
:4030C00007B5009313460A46014603480068FFF7CBFF03B05DF804FB5C81FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656977 :4030C000914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FA24
:4031000005EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B9E0 :40310000A38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561E0
:403140002169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA3680020A9 :40314000ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB958
:403180009B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B6150 :40318000402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BD0
:4031C0000023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780FE :4031C000F9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF853301A93012605222178C6
:40320000099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F0184379 :403200004E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039AA2
:403240000490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027468C :40324000111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1DAA
:403280003B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781CA7 :403280001B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B1402309
:4032C0000A2407463A780130303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1CEB :4032C000CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB00932A46144B04A940468F
:403300008DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F12A :40330000AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF9B3B000071
:40334000FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BFA33C0000A93C0000AD3C000000000000DD3000002DE9F04791461F4627 :40334000A13B0000A53B000000000000B53000002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BF15
:403380000A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E05B :40338000D9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF01230D
:4033C0000123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F884 :4033C00092060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042AD7
:40340000431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED1843 :4034000008BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F0438A
:403440004FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8054 :4034400017460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A65
:4034800011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A6091 :4034800000F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D098
:4034C0003EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E045 :4034C000111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F104010E
:403500001368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE068 :40350000196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F032
:4035400078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E08A :40354000200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F020
:4035800049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0737 :4035800002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0FC
:4035C00008D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198064 :4035C000101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D1960156800216268284600F049F808B1401B6060636804E0BC
:4036000000E0196000232361754616E01A68111D1960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B468C :4036000004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D563
:4036400003AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135CC :40364000002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083AB
:40368000E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083923B0000B43C000010B5C9B202449042034605D01C78013064 :403680004F3A0000AC3B000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EBCE
:4036C0008C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014DA2 :4036C000020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BF2
:4037000001F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A342E7 :40370000B8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918DB
:403740000DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D11068526801441960EE :40374000A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF106852680918216062605C602846BDE8384000F0C4
:403780005A600DE002D90C232B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C2534 :4037800098B838BDA892FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D94E
:4037C000002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4695 :4037C0000B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304D4
:4038000015E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A48 :40380000A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1F09
:403840000ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BFAB :40384000A492FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF3146E1
:403880001AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C002305467D :403880003846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7D8FB431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8F0
:4038C00008462360FDF7F4FB431C02D1236803B12B6038BD8493FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102BB :4038C000043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066CB
:403900001302150228000000000104000100000000000000000157494E55534200003030303031000000000000000000E0000000000105000100D6000000070000002A0075 :40390000726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E5F
:4039400044006500760069006300650049006E0074006500720066006100630065004700550049004400730000009E0000007B00330064003200370035006300660065004E :403940006720666F72205553422E2E2E0055534220726561647900636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F7420254E
:403980002D0035003400330035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0000007B00330058 :4039800064007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F77726997
:4039C00064003200370035006300660065002D0035003400330035002D0034006400640035002D0061006300630061002D00390066006200390039003500650032006600B4 :4039C000746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E21007375635E
:403A00003600330038007D00000000007265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E14 :403A0000636573730073746172742065726173696E670073746F702065726173696E670069646C6500005100401000405100403000000001400010001401400008004001A3
:403A40006973686564207365656B00796573006E6F0057616974696E6720666F72205553422E2E2E00555342207265616479005363616E6E696E67206472697665732E2E1C :403A400040000A004C0140000200500140200030313233343536373839414243444546000001000000040000001000010000000400000010280000000001040001000000C2
:403A80002E00647269766520303A20257320647269766520313A20257300636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F40 :403A800000000000000157494E5553420000303030303100000000000000000012034D005300460054003100300030000100000001000000B83A000001000000873B0000A5
:403AC00074202564007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645FEF :403AC000000000000000000001000000D03A000001000000593B000004000000F23A0000000000000000000000000000F03A0000FF00000001024000FF00000082024000C7
:403B0000777269746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E210015 :403B0000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200034
:403B4000737563636573730073746172742065726173696E670073746F702065726173696E670069646C650000510040100040510040300000000140001000140140000858 :403B400054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000AE8
:403B800000400140000A004C01400002005001402000303132333435363738394142434445460000000100000004000000100001000000040000001001000000C03B000072 :403B80000705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C0065666745464700303132333435363738396162636465660000000069
:403BC000010000008F3C0000000000000000000001000000D83B000001000000613C000004000000FA3B0000000000000000000000000000F83B0000FF0000000102400099 :403BC000F8B500BFF8BC08BC9E46704759000000CD100000F8B500BFF8BC08BC9E46704735000000F83B0000C880FF1FA00000002812000000000000000000009093FF1FA8
:403C0000FF00000082024000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00CE :403C0000FF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000028
:403C4000610072006B00200054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000E5 :403C400000000000000000000000000000000000993B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070
:403C8000000705030340000A0705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C00656667454647003031323334353637383961629E :403C8000000000000000000000000000000000000081FF1F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065
:403CC0006364656600000000F8B500BFF8BC08BC9E467047590000002D110000F8B500BFF8BC08BC9E46704735000000003D0000C880FF1F980000002812000000000000F3 :403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4
:403D0000000000008893FF1FFFFF0000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003000000000000000000000000EE :403D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083
:403D40000000000000000000000000000000000000000000A13C00000000000000000000000000000000000000000000000000000000000000000000000000000000000066 :403D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043
:403D80000000000000000000000000000000000000000000FC80FF1F0000000000000000000000000000000000000000000000000000000000000000000000000000000069 :403D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
:403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3 :403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3
:403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082 :403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082
:403E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042 :403E40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042
@@ -4098,48 +4098,48 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041 :40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 :40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A :0200000480007A
:400000000145004008520040015B004001650040020101402C0201402D0301400F0501400B0701406308014056090140570A01404A0B0140500C01404A0D0140480E014042 :400000000145004009520040015B00400165004047000140380101404E0201404703014046040140440501404C060140490701402C08014031090140040B0140030D014075
:40004000390F01400415014004170140671801404B190140361A0140471B01400D40014010410140134201400D43014004440140064501400A460140084701400B48014097 :400040006C14014057150140591601404B17014011190140071B01400B400140104101400C4201400F43014002440140054501400A4601400B4701400C480140104901404C
:4000800010490140144C01400C4D014006500140045101407E02080109441180180219016009610A7C402721290AE204E6080F011001150126012E012F013001310138026A :40008000174C01400C4D014006500140045101407E02080409021082110218021904601561137C402721240A02E40303040E0520064107C00A840B900E840F901001120693
:4000C00039023E013F01580459045B045F01810883048C018D0E910194029701A404AD04AE04AF0AB004B102B301B402B50CB601B802B908BE51BF04D608D804D909DB0401 :4000C00013901684179019011A841B481C081DFC1E221F0221C122182324268427902B9C2CE72E10300733E0351F36F83A023B084204460E480449FF4AFF4BFF4F83580B46
:40010000DC90DD90DF01020208080C02100213081540170818081A801B801C80200821012210231026012A802C02300233043708388039023D405840600268026B0C6D4004 :40010000590B5A045B045C995D095F01868088028A8190E7921094019686980F9A409C0F9E20A2E0A618A804AA81AE80B207B4F8D80BDC09DF010022022003010524084089
:40014000782088028B109840B040C001C214C4A5CA18CC43CE19D608D808DE04E208E68080088201928094809C289E80A201A602B044B380B404B610E604EA01EE44804068 :4001400009080A080B400C420D200E2010021108130215801610172119011A011C241E201F0C210422082308284029042B88312032643301382039883A013B804240528017
:40018000842094809C209E80A602AA40E211E608EA01EE0C00800121020403020403052106FC0704080109210A020B080C040E100F4010FF11011404152F162019201A0495 :400180005A215B4A600461016218634278027C028280C06FC2FFC4FDCA07CC0FCE0FD001D60FD80FDE8103140404050806110710081F0A800C010E1E0FE4101F124015102D
:4001C0001B011C041D211EF91F0E220224042608271028042A402B2F2D0F2E02311F336034FF39083A0C3E104003450C470E481149FF4AFF4BFF50045609581859045A044E :4001C00016E0178218021A111B141C1F1E20211023212408261127142B142D1F2F4032F0330F360F37F03A083B80580B590B5B045C995F018203830884618608873088204C
:400200005B045C995D995F0162C081028407850186388702880189048C028D048E04902091049208940895029610983F99029C089D049E30A105A503A704A802A902AA0417 :4002000089108A508B828DC78E108F2892109304961C97C49A109B04A061A110A204A341A440A630A704AB04AC7CAE02AF04B01FB30FB460B5F4BB08D80BD90BDB04DC99FF
:40024000AC3FAD02B43FB507B63FBF10D608D804D904DB04DC09DD90DF010010010803010490050909240A420C030E020F24100A15081602172119401A021B501E101F107B :40024000DF01012A02200301040805400708081009080B8C0C420E200F04104211081504180819221A0C1B011C221D4C1E211F4820042218242025442731285029402B8839
:400280002120242025052603272029982B402C022F18300831803202332035983701388039013B183C803D113E0447084F0856085A805F4062806302670269806B026C0881 :400280002C022E042F883204331134103508380439813A103D8C3E016230680269806A806D4078027C029308942096E897809C029D249F61A403A50CA620A781AA01AB0889
:4002C0006D116E846F21759076467F01814087018E08908094209581971499049C019DC99E449F03A382A408A682A729C0F7C2FFC4F3CA7FCCFFCEFFD040D618D818DE1080 :4002C000C0EFC2FEC42DCAFFCC67CEDFDE81E208021004400620080C0C010E0210081101140818051A081C04200828202AC02CE02F0130603280341E3501360138203E40ED
:40030000E210EA01EE0C0101041205010624084209800A100B200C040D700F80107011081201130414061508170219071A081B081D801E601F102140221023802404250F16 :400300005608580459045B045C995D905F0181608209842487108A128B108F10903F93039407950197089B1C9D40A036A209A310A710A82DA901AA12AB04AC40AD7CAF0220
:4003400027F028042B0F2C802D01300F320F348035FF3670380A3B0C3E103F105804590A5B045C995F01824185F186418704888089208A018B118CF18E0291F1924E930218 :40034000B11FB238B440B540B607B720BE10D608D80BD90BDB04DC99DD90DF01000201280302040A070409980B800C020E20104212081550170119281A401B401E201FA0A5
:40038000954096419711980199109A149BE19F0EA1F1A308A580A641A711A801AA28B00FB10FB3F0B4F0BA02BB02D80BD90BDB04DC99DF010108020203080510076208017B :4003800022102662274828022C022E052F043302364237283C203D803F0558805D80600267016B026D106F017C0287208B848F4091409205930496409B209C029F48A0124A
:4003C00009200A220D020E1410021240131414081508188819041A221B821C801D121F1021282501277029892B082D022F103008314033213608376238A039013A043E1454 :4003C000A140A208A620A780AD01AE08C07FC23FC4DBCAF8CCF1CEF0D618D818DE80E208E408EA040104020F0440068008700A800C800E200F0110011106130114081602E9
:4004000068026D407A107E80800481408D108F019108921495809702980899029D109F72A218A440A509A701A809B340B408B502B640C0F7C27FC46FCACFCCFFCE6FDE8298 :40040000170218011C0F1EF0200124072608288029052A102B022C082E04310736FF3E403F01580459045B045F018001811C820283E18402852087C0880489828B218C0248
:40044000E401E8F0EC20EE40020103C4070808050A020B040C380D200F82101011C7120813181602170418081A301B041F302520274128042B042C062E012F043020320715 :400440008D088F10900491FF940495089710980599029C049D04A002A1FCA402A520A740A802AC03AE04AF02B007B3FCB503BB20BE01D804D904DB04DC90DF010081012440
:40048000330F341837F43B083E04580459085B045C995F0182108303840285628604870489208A108B508DFC8E108F0192039420964097809A109B1C9C409E209F10A07C22 :4004800004450520084009080A080B810C080D010E020F041008120113861544172219011C0220042114222C23282740280229012B202D1030083180321033023705396892
:4004C000A162A201A308A61CA710A940AA10AB30AC02AE08AF10B01FB11FB360B460B780D808D908DC99DF0100A001800204036004020706081009080A800B010C080E8878 :4004C0003A013D82588068066C016D806F048004810184048F0191109220940A9780980299209B019C409F04A401A598A702A880A980AF02C0FFC2FFC4FFCA2BCCCFCE9F8E
:400500001218134215401608180419201A841B201C081EA02105220923112504268429922A242D802E102F20301031083350340136883722380439823B103D0A3E203F4070 :40050000D608E24CE402E605EA04EC02EE0C06010B0410041304160119021C022101240829042C013001310232043304340236083701380239083E553F45560858045904A9
:40054000611062106D406F047B037F028A08900292509508968297129C019D329F62A018A444A501A690A721C0DFC27FC4CFCA7FCCFECEFFDE18E020E208EA8000020120A4 :400540005B045D905F0183018A029501A401AC02B401B501B602B880B920BE50BF10D804D904DB04DF01030804080D800F50100212101410154019101A021E2A1F022210BD
:40058000024803C004C20502062407440A030B900E9011FC129013011690170318201AC01B901E901F9020FC21C222012328269027902A9C2B9C2F90321F331F34E037E015 :40058000250226882C802F083308372138023F10584059105DA45E01645266106A406C076D406F09800282C085808608890293409980AF40C022C2B0C4A5CAA0CCA2CE2157
:4005C0003A203B80420647E0482049FF4AFF4BFF4F83580859085A045B045C995D095F0180E28208861088E28A049210942096D09A03A0FCA201A440A630A880AA30AE1C91 :4005C000D6FCD8F0EA04EE0C9980E208E448EA049980B180E208018002200608070108280B100C020D210E2C1028110F1434152117081828192F1C101E021F2F202821210D
:40060000B2E0B41FD808DB04DC09DF01009001C6032005260610072009010A140B420D200E100F40121013121548171218101A241B101C201D201E101F0820022204230148 :40060000230224022740282829212B042C202D212E012F0E321F336035803620371F39083E443F1040524720482049FF4AFF4BFF4D204EF05110580459045A045B045C9038
:4006400029862B08300131103204335039043B1148054A0252015AAA6120628263206A406C097B037F0282018610C07FC27FC4FECA0FCC0FCE07D204D60FD80FDE188A02E6 :400640005D095F01610862406340648066406740800F810183028503861087FC88208A018B048C208DFF8E0290039302942395809608970499049A409B209D049E2F9FF9AF
:40068000A602E604EE02A602B680E401EE020010022003020406063807040802090C0A040BF10C080D0C0F20100813081408150C17101808190C1B401C081F0221802204B3 :40068000A023A104A20CA308A423A504A604A740A82FA904AB10AF02B060B3FFB460B61FB822BF04D804D904DB04DC09DF01012202200301040205040608070209880A04D5
:4006C000230C250327FC281A29012A202B022DFF2E01303F31FF3E013F01580459045B045F0181238280830C842286CC870188308A028B2F8CC88D0F9123924894C8952367 :4006C0000B800C080D800E48118012081305144015051610171019881C401D401E041F602118228023112410284129082A102D202E402F6030083240331234013680372A63
:40070000970898D499239B049CC89F10A0C8A120A302A4C8A52FA802AB40AC80AE01B080B261B31FB41FB560B760B808B9A0BE11C046C620C808C9FFCAFFCBFFCD20CEF004 :40070000381039423A083C063D803F1041104980510159025A0269806C016D806F0285018D409004914592C2930494429604979199249A089B3B9D40A009A180A22CA548B8
:40074000D110D804D904DA04DB04DC99DD09DF01E108E240E340E480E640E7400010010803820490050909080A420B040E020F941002110A120415401721184019181C80B4 :40074000A722AC02B704C0FFC2FFC47FCAFFCCFFCEFFD004D208E402E808EE1200FF01880403054606FC07B8080C09880A100C0C0EF10F0112081510160417A0180C198849
:40078000218C2314274029182A022D4A2F0830083361350436413720381039413A043D013F9440404A085010584059205F806C026D4082409080910192029410950896C023 :400780001A401C011D421E021F04218822022480259A260C2720280C29882A202E022F0431C132FF333F35C137C139A23E043F04580459045B045C905F01800282058521EF
:4007C000971C99049A069B219D099F08A008A105A2C1A3E6A442A580A708B340B610C0FFC2FFC4DFCAF7CCFFCEFFD001D204D61CEE120008060209020D01100411041202D8 :4007C000870288028C0291279403950896049B389D049F01A118A340A706AC02AF01B006B107B310B401B560B708BE10BF40C006C5ECC803C9FFCAFFCBFFD004D601D8046C
:4008000018041F04220428012C063006310132013302340835043A023E143F055608580459045B045C995D905F0181028405881090019236930198019C319E0AAC05AD012B :40080000D904DA04DB04DC99DD09DF01E2C00020010203020406056007100A200C010D800E040F1013801542172819131D042003218022D5240425102605278128102901DB
:40084000B00CB102B210B420B501B603BE04BF11D608D804D904DB04DC09DD90DF0101080240051809200A400E820F08100811401280154019231A501C101D101E821F0E2C :400840002A802D202E402F603180321433023680372838043C043D803F11481049104B0264016701684869476A406C02728873068410860194419546978799209B2C9F827A
:4008800020402120220827882F02300232803601382039803F0158205A805F40600862406701680869806F0180408210844887408B018E4090209140941096409708982064 :40088000A228A448A598A601A722AB40B002B481C0FBC2F4C4F1CAFBCC7FCEF2D204E220E604EA4082108A018E048F029222960197109D209F20A214A302B080B104B6017C
:4008C00099089C409D239F88A002A280A304A608A708B020B410C06CC2DAC48BCA10CC89CE8CD61CD81CE001E622EE021B011F083140330836843B4083408540C630CCF07B :4008C000E220EA10EC808F209F20AF10B211B520E412EE021B011F083240330836843B408140C630CCF0CE10E62030803204358037083A023D408480914096809740A6046A
:40090000CE10E220E61030803204364037043A083F80848088029740A340A604AE80AF01CCF0CE60E2105004570880049002960897409A049E089F04A002A120A644A80182 :40090000AE80AF01CCF0CE60E210508057208580968897409D809E02A604AB08D460E210EA20832284808E02968097409E02A480A620A720AA24AE04E230E620EC80EE30D9
:40094000AA04B520D460E680EA80EE208E08900297409A049E08A002A120A640AA04AB0CAE04EA80EEB014407008C404DC0160808740A408B040D802EA011B0482209780E6 :400940001501C4045D828740B101D605EE011B04850287108D8097809D82A480A880C608E4020B880C800F108A109798A480A740AB44AF04C20FE404262080018A01974061
:400980009940B008B140B480C608E809EC040B880F4197899940A220AB05AF04C20F24088004900297409A04A002A120AE40C820E480EE4050015120560470027F018301EC :40098000A280A302A620AA40B680C820E620EE8052805302551070017E0190029202A110A280A302AF40B510D4E0DC80DE20EE40052008040E020F801F1053805610588014
:4009C00090029A04A002A120AF40D4E0DC80DE20E620EE40070208080D010E021F10520854085940622095029940A220AF40B101C001C20DC601D407D802EA04800887021E :4009C00063028E208F809A109D20A740B120C001C20DC601D407D602E002E402EA04762084809A209C80AF10B004B301B601DE04E801EA08EE01010109010B010D011101C4
:400A00008E019B02A201A408AF10B008B608E208EA01EC02010109010D010F0111011D0100FF01AB02021105BF0000A09F001F0000000000000000001000000040000000B7 :400A00001B0100FF01AB020211050000BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B847004700000100008000000282008200DC
:400A400000000000C0000000FF0000B84700470000010000800000008000800000000000000707000700000027001801270018010004000000050000000000000000000052 :400A400000000000000707000700000027001801270018010004000000050000000000000000000000000000000000000000000000000000000000000000000000000000D8
:400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036 :400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6 :400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5 :400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
@@ -4615,12 +4615,12 @@
:0200000490105A :0200000490105A
:04000000BC90ACAF55 :04000000BC90ACAF55
:0200000490303A :0200000490303A
:0200000021B02D :02000000FEBF41
:0200000490402A :0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0 :4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 :400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040 :400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 :4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A :0200000490501A
:0C00000000012E16106900002E3032198D :0C00000000012E16106900002E300F28A1
:00000001FF :00000001FF

View File

@@ -816,7 +816,7 @@
<Group key="v1"> <Group key="v1">
<Data key="cy_boot" value="cy_boot_v6_10" /> <Data key="cy_boot" value="cy_boot_v6_10" />
<Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" /> <Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v6_0" /> <Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" />
</Group> </Group>
</Group> </Group>
<Data key="DataVersionKey" value="2" /> <Data key="DataVersionKey" value="2" />

View File

File diff suppressed because it is too large Load Diff

View File

@@ -23,49 +23,36 @@ module Sampler (
reg [5:0] counter; reg [5:0] counter;
reg index_q;
reg rdata_q;
reg index_edge; reg index_edge;
reg rdata_edge; reg rdata_edge;
reg req_toggle; reg req_toggle;
reg rdata_toggle;
reg old_rdata_toggle;
reg index_toggle;
reg old_index_toggle;
always @(posedge rdata)
begin
rdata_toggle <= ~rdata_toggle;
end
always @(posedge index)
begin
index_toggle <= ~index_toggle;
end
always @(posedge sampleclock) always @(posedge sampleclock)
begin begin
if (reset) if (reset)
begin begin
old_rdata_toggle <= 0;
old_index_toggle <= 0;
index_edge <= 0; index_edge <= 0;
rdata_edge <= 0; rdata_edge <= 0;
index_q <= 0;
rdata_q <= 0;
counter <= 0; counter <= 0;
req_toggle <= 0; req_toggle <= 0;
end end
else else
begin begin
/* If data_toggle or index_toggle have changed state, this means that they've /* Both index and rdata are active high -- positive-going edges
* gone high since the last sampleclock. */ * indicate the start of an index pulse and read pulse, respectively.
*/
index_edge <= index_toggle != old_index_toggle; index_edge <= index && !index_q;
old_index_toggle <= index_toggle; index_q <= index;
rdata_edge <= rdata_toggle != old_rdata_toggle; rdata_edge <= rdata && !rdata_q;
old_rdata_toggle <= rdata_toggle; rdata_q <= rdata;
if (rdata_edge || index_edge || (counter == 6'h3f)) begin if (rdata_edge || index_edge || (counter == 6'h3f)) begin
opcode <= { rdata_edge, index_edge, counter }; opcode <= { rdata_edge, index_edge, counter };

View File

Binary file not shown.

View File

@@ -147,7 +147,7 @@ static void set_drive_flags(struct set_drive_frame* flags)
current_drive_flags = *flags; current_drive_flags = *flags;
DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */ DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */
DENSITY_REG_Write(!flags->high_density); /* double density bit */ DENSITY_REG_Write(flags->high_density); /* density bit */
INDEX_REG_Write(flags->index_mode); INDEX_REG_Write(flags->index_mode);
} }
@@ -221,7 +221,7 @@ static int usb_read(int ep, uint8_t buffer[FRAME_SIZE])
static void cmd_get_version(struct any_frame* f) static void cmd_get_version(struct any_frame* f)
{ {
DECLARE_REPLY_FRAME(struct version_frame, F_FRAME_GET_VERSION_REPLY); DECLARE_REPLY_FRAME(struct version_frame, F_FRAME_GET_VERSION_REPLY);
r.version = FLUXENGINE_PROTOCOL_VERSION; r.version = FLUXENGINE_VERSION;
send_reply((struct any_frame*) &r); send_reply((struct any_frame*) &r);
} }
@@ -905,6 +905,7 @@ int main(void)
USBFS_Start(0, USBFS_DWR_VDDD_OPERATION); USBFS_Start(0, USBFS_DWR_VDDD_OPERATION);
USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM); USBFS_DisableOutEP(FLUXENGINE_DATA_OUT_EP_NUM);
detect_drives();
CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED); CyWdtStart(CYWDT_1024_TICKS, CYWDT_LPMODE_DISABLED);
for (;;) for (;;)
@@ -925,8 +926,6 @@ int main(void)
CyWdtClear(); CyWdtClear();
print("USB ready"); print("USB ready");
USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM); USBFS_EnableOutEP(FLUXENGINE_CMD_OUT_EP_NUM);
print("Scanning drives...");
detect_drives();
} }
if (USBFS_GetEPState(FLUXENGINE_CMD_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL) if (USBFS_GetEPState(FLUXENGINE_CMD_OUT_EP_NUM) == USBFS_OUT_BUFFER_FULL)
@@ -938,75 +937,3 @@ int main(void)
} }
} }
} }
const uint8_t USBFS_MSOS_CONFIGURATION_DESCR[USBFS_MSOS_CONF_DESCR_LENGTH] = {
/* Length of the descriptor 4 bytes */ 0x28u, 0x00u, 0x00u, 0x00u,
/* Version of the descriptor 2 bytes */ 0x00u, 0x01u,
/* wIndex - Fixed:INDEX_CONFIG_DESCRIPTOR */ 0x04u, 0x00u,
/* bCount - Count of device functions. */ 0x01u,
/* Reserved : 7 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
/* bFirstInterfaceNumber */ 0x00u,
/* Reserved */ 0x01u,
/* compatibleId - "WINUSB\0\0" */ 'W', 'I', 'N', 'U', 'S', 'B', 0, 0,
/* subcompatibleID - "00001\0\0" */ '0', '0', '0', '0', '1',
0x00u, 0x00u, 0x00u,
/* Reserved : 6 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u
};
const uint8_t USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[224] = {
/* Length; 4 bytes */ 224, 0, 0, 0,
/* Version; 2 bytes */ 0x00, 0x01, /* 1.0 */
/* wIndex */ 0x05, 0x00,
/* Number of sections */ 0x01, 0x00,
/* Property section size */ 214, 0, 0, 0,
/* Property data type */ 0x07, 0x00, 0x00, 0x00, /* 7 = REG_MULTI_SZ Unicode */
/* Property name length */ 42, 0,
/* Property name */ 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0,
'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0,
'a', 0, 'c', 0, 'e', 0, 'G', 0, 'U', 0, 'I', 0,
'D', 0, 's', 0, 0, 0,
/* Property data length */ 158, 0, 0, 0,
/* GUID #1 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0,
/* GUID #2 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0, '\0', 0
};
uint8 USBFS_HandleVendorRqst(void)
{
if (!(USBFS_bmRequestTypeReg & USBFS_RQST_DIR_D2H))
return false;
switch (USBFS_bRequestReg)
{
case USBFS_GET_EXTENDED_CONFIG_DESCRIPTOR:
switch (USBFS_wIndexLoReg)
{
case 4:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_CONFIGURATION_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_CONFIGURATION_DESCR[0u];
return USBFS_InitControlRead();
case 5:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
return USBFS_InitControlRead();
}
default:
break;
}
return true;
}

View File

@@ -0,0 +1,18 @@
const READ = 1
const WRITE = 2
filename = "Generated_Source\PSoC5\USBFS_descr.c"
set fso = CreateObject("Scripting.FileSystemObject")
set file = fso.OpenTextFile(filename, READ)
text = file.ReadAll
file.Close
set r = New RegExp
r.MultiLine = True
r.Pattern = "/\* +compatibleID.*\n.*"
text = r.replace(text, "'W', 'I', 'N', 'U', 'S', 'B', 0, 0,")
set file = fso.CreateTextFile(filename, True)
file.Write text
file.Close

163
Makefile
View File

@@ -1,129 +1,70 @@
ifeq ($(BUILDTYPE),) PACKAGES = zlib sqlite3 libusb-1.0 protobuf gtk+-3.0
buildtype_Darwin = osx
buildtype_Haiku = haiku
BUILDTYPE := $(buildtype_$(shell uname -s ))
ifeq ($(BUILDTYPE),)
BUILDTYPE := unix
endif
endif
export BUILDTYPE
ifeq ($(BUILDTYPE),windows) export CFLAGS = -ffunction-sections -fdata-sections
MINGW = i686-w64-mingw32- export CXXFLAGS = $(CFLAGS) \
CC = $(MINGW)gcc --std=gnu++2a \
CXX = $(MINGW)g++ -std=c++17 -Wno-deprecated-enum-enum-conversion \
CFLAGS += -g -O3 -Wno-deprecated-enum-float-conversion
CXXFLAGS += \ export LDFLAGS = -pthread
-fext-numeric-literals \
-Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion
LDFLAGS += -static
AR = $(MINGW)ar
PKG_CONFIG = $(MINGW)pkg-config -static
WINDRES = $(MINGW)windres
WX_CONFIG = /usr/i686-w64-mingw32/sys-root/mingw/bin/wx-config-3.0 --static=yes
EXT = .exe
else
CC = gcc
CXX = g++ -std=c++17
CFLAGS = -g -O3
LDFLAGS =
AR = ar
PKG_CONFIG = pkg-config
ifeq ($(BUILDTYPE),osx)
else
LDFLAGS += -pthread -Wl,--no-as-needed
endif
endif export COPTFLAGS = -Os
export LDOPTFLAGS = -Os -ldl
HOSTCC = gcc export CDBGFLAGS = -O0 -g
HOSTCXX = g++ -std=c++17 export LDDBGFLAGS = -O0 -g -ldl
HOSTCFLAGS = -g -O3
HOSTLDFLAGS =
REALOBJ = .obj
OBJ = $(REALOBJ)/$(BUILDTYPE)
DESTDIR ?=
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
# Special Windows settings.
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
EXT ?= .exe export PROTOC = /mingw32/bin/protoc
MINGWBIN = /mingw32/bin export CC = /mingw32/bin/gcc
CCPREFIX = $(MINGWBIN)/ export CXX = /mingw32/bin/g++
PKG_CONFIG = $(MINGWBIN)/pkg-config export AR = /mingw32/bin/ar rc
WX_CONFIG = /usr/bin/sh $(MINGWBIN)/wx-config --static=yes export RANLIB = /mingw32/bin/ranlib
PROTOC = $(MINGWBIN)/protoc export STRIP = /mingw32/bin/strip
WINDRES = windres export CFLAGS += -I/mingw32/include/libusb-1.0 -I/mingw32/include
LDFLAGS += \ export LDFLAGS +=
-static export LIBS += -L/mingw32/lib -static -lz -lsqlite3 -lusb-1.0 -lprotobuf
CXXFLAGS += \ export EXTENSION = .exe
-fext-numeric-literals \ else
-Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion
# Required to get the gcc run - time libraries on the path. packages-exist = $(shell pkg-config --exists $(PACKAGES) && echo yes)
export PATH := $(PATH):$(MINGWBIN) ifneq ($(packages-exist),yes)
$(warning These pkg-config packages are installed: $(shell pkg-config --list-all | sort | awk '{print $$1}'))
$(error You must have these pkg-config packages installed: $(PACKAGES))
endif endif
# Special OSX settings. export PROTOC = protoc
export CC = gcc
export CXX = g++
export AR = ar rc
export RANLIB = ranlib
export STRIP = strip
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
export LDFLAGS +=
export LIBS += $(shell pkg-config --libs $(PACKAGES))
export EXTENSION =
ifeq ($(shell uname),Darwin) ifeq ($(shell uname),Darwin)
LDFLAGS += \ AR = ar rcS
-framework IOKit \ RANLIB += -c -no_warning_for_no_symbols
-framework Foundation
endif endif
.PHONY: all endif
all: +all README.md export XXD = xxd
.PHONY: binaries tests CFLAGS += -Ilib -Idep/fmt -Iarch
binaries: all
tests: all
README.md: $(OBJ)/scripts/+mkdocindex/mkdocindex$(EXT)
@echo $(PROGRESSINFO) MKDOC $@
@csplit -s -f$(OBJ)/README. README.md '/<!-- FORMATSSTART -->/' '%<!-- FORMATSEND -->%'
@(cat $(OBJ)/README.00 && $< && cat $(OBJ)/README.01) > README.md
.PHONY: tests export OBJDIR = .obj
.PHONY: install install-bin all: .obj/build.ninja
install:: all install-bin @ninja -f .obj/build.ninja -k 0
@if command -v cscope > /dev/null; then cscope -bRq; fi
clean:: clean:
$(hide) rm -rf $(REALOBJ) @echo CLEAN
@rm -rf $(OBJDIR)
install-bin: .obj/build.ninja: mkninja.sh Makefile
@echo "INSTALL" @echo MKNINJA $@
$(hide) install -D -v "$(OBJ)/src+fluxengine/src+fluxengine" "$(DESTDIR)$(BINDIR)/fluxengine" @mkdir -p $(OBJDIR)
$(hide) install -D -v "$(OBJ)/src/gui+gui/gui+gui" "$(DESTDIR)$(BINDIR)/fluxengine-gui" @sh $< > $@
$(hide) install -D -v "$(OBJ)/tools+brother120tool/tools+brother120tool" "$(DESTDIR)$(BINDIR)/brother120tool"
$(hide) install -D -v "$(OBJ)/tools+brother240tool/tools+brother240tool" "$(DESTDIR)$(BINDIR)/brother240tool"
$(hide) install -D -v "$(OBJ)/tools+upgrade-flux-file/tools+upgrade-flux-file" "$(DESTDIR)$(BINDIR)/upgrade-flux-file"
include build/ab.mk
DOCKERFILES = \
debian11 \
debian12
define run-docker
docker build -t $1 -f tests/docker/Dockerfile.$(strip $1) .
docker run \
--device=/dev/kvm \
--rm \
--attach STDOUT \
--attach STDERR \
$1 \
make
endef
.PHONY: dockertests
dockertests:
$(hide) echo DOCKERTESTS
$(foreach f,$(DOCKERFILES), $(call run-docker, $f))

185
README.md
View File

@@ -2,14 +2,12 @@ FluxEngine
========== ==========
(If you're reading this on GitHub, the formatting's a bit messed up. [Try the (If you're reading this on GitHub, the formatting's a bit messed up. [Try the
version on cowlark.com instead.](http://cowlark.com/fluxengine/)) version on cowlark.com instead.](http://cowlark.com/fluxengine/)
**Breaking news!** As of 2024-10-01, the FluxEngine client software works **Breaking news!** As of 2021-05-21, the command line environment has changed
(to a point) with [Applesauce](doc/applesauce.md) hardware. _substantially_ (to make it more consistent and flexible, and allow some new
backend features like multi-format IBM scheme disks, which are popular with
<div style="text-align: center"> CP/M). If things don't work the way you expect, please check the documentation.
<a href="doc/screenshot.jpg"><img src="doc/screenshot.jpg" style="width:60%" alt="screenshot of the GUI in action"></a>
</div>
What? What?
----- -----
@@ -32,14 +30,17 @@ Don't believe me? Watch the demo reel!
</div> </div>
**New!** The FluxEngine client software now works with **New!** The FluxEngine client software now works with
[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) and [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you
[Applesauce](https://applesaucefdc.com/) hardware. So, if you can't find a PSoC5 can't find a PSoC5 development kit, or don't want to use the Cypress Windows
development kit, or don't want to use the Cypress Windows tools for programming tools for programming it, you can use one of these instead. Very nearly all
it, you can use one of these instead. Very nearly all FluxEngine features are FluxEngine features are available with the GreaseWeazle and it works out-of-the
available with the Greaseweazle and it works out-of-the box; the Applesauce is a box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md)
bit less supported but still works. See the [dedicated Greaseweazle for more information.
documentation page](doc/greaseweazle.md) or the [Applesauce
page](doc/applesauce.md) for more information. **Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
format should be more reliable and use way, way less bandwidth. Sorry for the
inconvenience.
Where? Where?
------ ------
@@ -64,85 +65,71 @@ following friendly articles:
- [Using a FluxEngine](doc/using.md) ∾ what to do with your new hardware ∾ - [Using a FluxEngine](doc/using.md) ∾ what to do with your new hardware ∾
flux files and image files ∾ knowing what you're doing flux files and image files ∾ knowing what you're doing
- [Using Greaseweazle hardware with the FluxEngine client - [Using GreaseWeazle hardware with the FluxEngine client
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
go for help go for help
- [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track
drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II
- [Direct filesystem access](doc/filesystem.md) ∾ imaging files is a pain
∾ accessing files directly ∾ features and limitation ∾ it works on disk
images too, you say?
- [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact - [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact
science ∾ the sector map ∾ clock detection and the histogram science ∾ the sector map ∾ clock detection and the histogram
- [Disk densities](doc/driveresponse.md) ∾ what's the difference between an HD - [Checking your drive](doc/driveresponse.md) ∾ you can't do that with that ∾
and DD disk? ∾ you can't do that with that ∾ measuring your drive's ability to measuring your drive's ability to work with exotic formats
work with exotic formats ∾ I think my drive is broken
Which? Which?
------ ------
The current support state is as follows. The current support state is as follows.
Dinosaurs (🦖) have yet to be observed in real life --- I've written the encoder Dinosaurs (🦖) have yet to be observed in real life --- I've written the
and/or decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have
real, physical disks in my hand to test the capture process, or hardware to real, physical disks in my hand to test the capture process.
verify that written disks work.
Unicorns (🦄) are completely real --- this means that I've read actual, physical Unicorns (🦄) are completely real --- this means that I've read actual,
disks with these formats and/or written real, physical disks and then used them physical disks with these formats and so know they work (or had reports from
on real hardware, and so know they work (or had reports from people who've had people who've had it work).
it work).
If a filesystem is listed, this means that FluxEngine natively supports that ### Old disk formats
particular filesystem and can read (and sometimes write, support varies) files
directly from disks, flux files or disk images. Some formats have multiple
choices because they can store multiple types of file system.
<!-- FORMATSSTART --> | Format | Read? | Write? | Notes |
<!-- This section is automatically generated. Do not edit. --> |:------------------------------------------|:-----:|:------:|-------|
| [IBM PC compatible](doc/disk-ibm.md) | 🦄 | 🦄 | and compatibles (like the Atari ST) |
| Profile | Format | Read? | Write? | Filesystem? | | [Acorn ADFS](doc/disk-acornadfs.md) | 🦄 | 🦖* | single- and double- sided |
|:--------|:-------|:-----:|:------:|:------------| | [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
| [`acornadfs`](doc/disk-acornadfs.md) | Acorn ADFS: BBC Micro, Archimedes | 🦖 | | | | [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
| [`acorndfs`](doc/disk-acorndfs.md) | Acorn DFS: Acorn Atom, BBC Micro series | 🦄 | | ACORNDFS | | [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | | doesn't do logical sector remapping |
| [`aeslanier`](doc/disk-aeslanier.md) | AES Lanier "No Problem": 616kB 5.25" 77-track SSDD hard sectored | 🦖 | | | | [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
| [`agat`](doc/disk-agat.md) | Agat: 840kB 5.25" 80-track DS | 🦖 | 🦖 | | | [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
| [`amiga`](doc/disk-amiga.md) | Amiga: 880kB 3.5" DSDD | 🦄 | 🦄 | AMIGAFFS | | [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦖 | |
| [`ampro`](doc/disk-ampro.md) | Ampro Little Board: CP/M | 🦖 | | CPMFS | | [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [`apple2`](doc/disk-apple2.md) | Apple II: Prodos, Appledos, and CP/M | 🦄 | 🦄 | APPLEDOS CPMFS PRODOS | | [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines |
| [`atarist`](doc/disk-atarist.md) | Atari ST: Almost PC compatible | 🦄 | 🦄 | | | [Macintosh 800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | and probably the 400kB too |
| [`bk`](doc/disk-bk.md) | BK: 800kB 5.25"/3.5" 80-track 10-sector DSDD | 🦖 | 🦖 | | | [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
| [`brother`](doc/disk-brother.md) | Brother word processors: GCR family | 🦄 | 🦄 | BROTHER120 FATFS |
| [`commodore`](doc/disk-commodore.md) | Commodore: 1541, 1581, 8050 and variations | 🦄 | 🦄 | CBMFS |
| [`eco1`](doc/disk-eco1.md) | VDS Eco1: CP/M; 1210kB 77-track mixed format DSHD | 🦖 | | CPMFS |
| [`epsonpf10`](doc/disk-epsonpf10.md) | Epson PF-10: CP/M; 3.5" 40-track DSDD | 🦖 | | CPMFS |
| [`f85`](doc/disk-f85.md) | Durango F85: 461kB 5.25" 77-track SS | 🦖 | | |
| [`fb100`](doc/disk-fb100.md) | Brother FB-100: 100kB 3.5" 40-track SSSD | 🦖 | | |
| [`hplif`](doc/disk-hplif.md) | Hewlett-Packard LIF: a variety of disk formats used by HP | 🦄 | 🦄 | LIF |
| [`ibm`](doc/disk-ibm.md) | IBM PC: Generic PC 3.5"/5.25" disks | 🦄 | 🦄 | FATFS |
| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS |
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | |
| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS |
| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | |
| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | |
| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | |
| [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE |
| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND |
| [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 | |
| [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 | | SMAKY6 |
| [`tartu`](doc/disk-tartu.md) | Tartu: The Palivere and variations | 🦄 | 🦖 | CPMFS |
| [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 | |
| [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M | | | CPMFS |
| [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 | |
| [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 | | ZDOS |
{: .datatable } {: .datatable }
<!-- FORMATSEND --> `*`: these formats are variations of the generic IBM format, and since the
IBM writer is completely generic, it should be configurable for these
formats... theoretically. I don't have the hardware to try it.
### Even older disk formats
These formats are for particularly old, weird architectures, even by the
standards of floppy disks. They've largely been implemented from single flux
files with no access to physical hardware. Typically the reads were pretty
bad and I've had to make a number of guesses as to how things work. They do,
at least, check the CRC so what data's there is probably good.
| Format | Read? | Write? | Notes |
|:-----------------------------------------|:-----:|:------:|-------|
| [AES Superplus / No Problem](doc/disk-aeslanier.md) | 🦖 | | hard sectors! |
| [Durango F85](doc/disk-durangof85.md) | 🦖 | | 5.25" |
| [DVK MX](doc/disk-mx.md) | 🦖 | | Soviet PDP-11 clone |
| [VDS Eco1](doc/disk-eco1.md) | 🦖 | | 8" mixed format |
| [Micropolis](doc/disk-micropolis.md) | 🦄 | | Micropolis 100tpi drives |
| [Northstar(doc/disk-northstar.md) | 🦖 | 🦖 | 5.25" hard sectors |
| [TI DS990 FD1000](doc/disk-tids990.md) | 🦄 | 🦄 | 8" |
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8" |
| [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8" _and_ hard sectors |
{: .datatable }
### Notes ### Notes
@@ -159,7 +146,7 @@ choices because they can store multiple types of file system.
There hasn't been a lot of demand for this yet; if you have a pressing There hasn't been a lot of demand for this yet; if you have a pressing
need to write weird disks, [please need to write weird disks, [please
ask](https://github.com/davidgiven/fluxengine/issues/new). I haven't ask](https://github.com/davidgiven/fluxengine/issues/new). I haven't
implemented write support for PC disks because they're boring and I'm lazy, implement write support for PC disks because they're boring and I'm lazy,
and also because they vary so much that figuring out how to specify them and also because they vary so much that figuring out how to specify them
is hard. is hard.
@@ -213,12 +200,17 @@ There may or may not be anything interesting there.
License License
------- -------
Everything here _except the contents of the `dep` directory_ is © 2022 The Everything here _except the contents of the `dep` directory_ is © 2019 David
FluxEngine Authors (mostly me, David Given; see the VCS history for the other Given and is licensed under the MIT open source license. Please see
people) and is licensed under the MIT open source license. Please see
[COPYING](COPYING) for the full text. The tl;dr is: you can do what you like [COPYING](COPYING) for the full text. The tl;dr is: you can do what you like
with it provided you don't claim you wrote it. with it provided you don't claim you wrote it.
As an exception, `dep/fmt` contains a copy of [fmt](http://fmtlib.net),
maintained by Victor Zverovich (`vitaut <https://github.com/vitaut>`) and
Jonathan Müller (`foonathan <https://github.com/foonathan>`) with
contributions from many other people. It is licensed under the terms of the
BSD license. Please see the contents of the directory for the full text.
As an exception, `dep/emu` contains parts of the OpenBSD C library As an exception, `dep/emu` contains parts of the OpenBSD C library
code, maintained by Todd Miller and William A. Rowe (and probably others). It is licensed code, maintained by Todd Miller and William A. Rowe (and probably others). It is licensed
under the terms of the 3-clause BSD license. Please see the contents of the under the terms of the 3-clause BSD license. Please see the contents of the
@@ -236,28 +228,9 @@ text.
As an exception, `dep/snowhouse` contains the snowhouse assertion library, As an exception, `dep/snowhouse` contains the snowhouse assertion library,
taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License
1.0 licensed. Please see the contents of the directory for the full text. Note 1.0 licensed. Please see the contents of the directory for the full text.
that this is only used during the build and no code ends up in the output
binaries.
As an exception, `dep/libusbp` contains the libusbp library, taken from As an exception, `dep/libui` contains the libui GUI library, taken from
https://github.com/pololu/libusbp. It is MIT licensed. Please see the contents https://github.com/andlabs/libui. It is MIT licensed. Please see the contents
of the directory for the full text. of the directory for the full text.
As an exception, `dep/fatfs` contains the fatfs library, taken from
http://elm-chan.org/fsw/ff/00index_e.html. It is single-clause BSD licensed.
Please see the contents of the directory for the full text.
As an exception, `dep/adflib` contains the adflib library, written by Laurent
Clevy et al, taken from https://github.com/lclevy/ADFlib. It is GPL 2.0
licensed. Please see the contents of the directory for the full text.
As an exception, `dep/hfsutils` contains a partial copy of the hfsutils
package, written by Robert Leslie et al, taken from
https://www.mars.org/home/rob/proj/hfs. It is GPL 2.0 licensed. Please see the
contents of the directory 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
distributable under the terms of the GPL 2.0__.

View File

@@ -2,10 +2,9 @@
#define AESLANIER_H #define AESLANIER_H
#define AESLANIER_RECORD_SEPARATOR 0x55555122 #define AESLANIER_RECORD_SEPARATOR 0x55555122
#define AESLANIER_SECTOR_LENGTH 256 #define AESLANIER_SECTOR_LENGTH 256
#define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 5) #define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 5)
extern std::unique_ptr<Decoder> createAesLanierDecoder( extern std::unique_ptr<AbstractDecoder> createAesLanierDecoder(const DecoderProto& config);
const DecoderProto& config);
#endif #endif

View File

@@ -1,65 +1,79 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "aeslanier.h" #include "aeslanier.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "sector.h"
#include "lib/data/sector.h" #include "bytes.h"
#include "lib/core/bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR); static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine /* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
* with it. */
class AesLanierDecoder : public Decoder static Bytes reverse_bits(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : input)
bw.write_8(reverse_bits(b));
return output;
}
class AesLanierDecoder : public AbstractDecoder
{ {
public: public:
AesLanierDecoder(const DecoderProto& config): Decoder(config) {} AesLanierDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(SECTOR_PATTERN); _sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
} if (_fmr->eof() || !_sector->clock)
return UNKNOWN_RECORD;
return SECTOR_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */ /* Skip ID mark. */
readRawBits(16); readRawBits(16);
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE * 16); const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
const auto& bytes = const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); const auto& reversed = reverse_bits(bytes);
const auto& reversed = bytes.reverseBits();
_sector->logicalTrack = reversed[1]; _sector->logicalTrack = reversed[1];
_sector->logicalSide = 0; _sector->logicalSide = 0;
_sector->logicalSector = reversed[2]; _sector->logicalSector = reversed[2];
/* Check header 'checksum' (which seems far too simple to mean much). */ /* Check header 'checksum' (which seems far too simple to mean much). */
{ {
uint8_t wanted = reversed[3]; uint8_t wanted = reversed[3];
uint8_t got = reversed[1] + reversed[2]; uint8_t got = reversed[1] + reversed[2];
if (wanted != got) if (wanted != got)
return; return;
} }
/* Check data checksum, which also includes the header and is /* Check data checksum, which also includes the header and is
* significantly better. */ * significantly better. */
_sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); _sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH);
uint16_t wanted = reversed.reader().seek(0x101).read_le16(); uint16_t wanted = reversed.reader().seek(0x101).read_le16();
uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data);
_sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; _sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM;
} }
}; };
std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createAesLanierDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new AesLanierDecoder(config)); return std::unique_ptr<AbstractDecoder>(new AesLanierDecoder(config));
} }

View File

@@ -1,20 +0,0 @@
#include "lib/core/globals.h"
#include "lib/decoders/decoders.h"
#include "agat.h"
#include "lib/core/bytes.h"
#include "fmt/format.h"
uint8_t agatChecksum(const Bytes& bytes)
{
uint16_t checksum = 0;
for (uint8_t b : bytes)
{
if (checksum > 0xff)
checksum = (checksum + 1) & 0xff;
checksum += b;
}
return checksum & 0xff;
}

View File

@@ -1,19 +0,0 @@
#ifndef AGAT_H
#define AGAT_H
#define AGAT_SECTOR_SIZE 256
static constexpr uint64_t SECTOR_ID = 0x8924555549111444;
static constexpr uint64_t DATA_ID = 0x8924555514444911;
class Encoder;
class EncoderProto;
class Decoder;
class DecoderProto;
extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config);
extern uint8_t agatChecksum(const Bytes& bytes);
#endif

View File

@@ -1,19 +0,0 @@
syntax = "proto2";
import "lib/config/common.proto";
message AgatDecoderProto {}
message AgatEncoderProto {
optional double target_clock_period_us = 1
[default=2.00, (help)="Data clock period of target format."];
optional double target_rotational_period_ms = 2
[default=200.0, (help)="Rotational period of target format."];
optional int32 post_index_gap_bytes = 3
[default=40, (help)="Post-index gap before first sector header."];
optional int32 pre_sector_gap_bytes = 4
[default=11, (help)="Gap before each sector header."];
optional int32 pre_data_gap_bytes = 5
[default=2, (help)="Gap before each sector data record."];
}

View File

@@ -1,90 +0,0 @@
#include "lib/core/globals.h"
#include "lib/decoders/decoders.h"
#include "agat.h"
#include "lib/core/crc.h"
#include "lib/data/fluxmap.h"
#include "lib/data/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "lib/data/sector.h"
#include "lib/core/bytes.h"
#include "fmt/format.h"
#include <string.h>
// clang-format off
/*
* data: X X X X X X X X X - - X - X - X - X X - X - X - = 0xff956a
* flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = 0x555549111444
*
* data: X X X X X X X X - X X - X - X - X - - X - X - X = 0xff6a95
* flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = 0x555514444911
*
* Each pattern is prefixed with this one:
*
* data: - - - X - - X - = 0x12
* flux: (10) 10 10 10 01 00 10 01 00 = 0xa924
* magic: (10) 10 00 10 01 00 10 01 00 = 0x8924
* ^
*
* This seems to be generated by emitting A4 in MFM and then a single 0 bit to
* shift it out of phase, so the data bits become clock bits and vice versa.
*
* X - X - - X - - = 0xA4
* 0100010010010010 = MFM encoded
* 1000100100100100 = with trailing zero
* - - - X - - X - = effective bitstream = 0x12
*/
// clang-format on
static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID);
static const FluxPattern DATA_PATTERN(64, DATA_ID);
static const FluxMatchers ALL_PATTERNS = {&SECTOR_PATTERN, &DATA_PATTERN};
class AgatDecoder : public Decoder
{
public:
AgatDecoder(const DecoderProto& config): Decoder(config) {}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ALL_PATTERNS);
}
void decodeSectorRecord() override
{
if (readRaw64() != SECTOR_ID)
return;
auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4);
if (bytes[3] != 0x5a)
return;
_sector->logicalTrack = bytes[1] >> 1;
_sector->logicalSector = bytes[2];
_sector->logicalSide = bytes[1] & 1;
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord() override
{
if (readRaw64() != DATA_ID)
return;
Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE + 2) * 16))
.slice(0, AGAT_SECTOR_SIZE + 2);
if (bytes[AGAT_SECTOR_SIZE + 1] != 0x5a)
return;
_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE);
uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE];
uint8_t gotChecksum = agatChecksum(_sector->data);
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
};
std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new AgatDecoder(config));
}

View File

@@ -1,118 +0,0 @@
#include "lib/core/globals.h"
#include "lib/core/utils.h"
#include "lib/decoders/decoders.h"
#include "lib/encoders/encoders.h"
#include "agat.h"
#include "lib/core/crc.h"
#include "lib/data/image.h"
#include "lib/data/layout.h"
#include "arch/agat/agat.pb.h"
#include "lib/encoders/encoders.pb.h"
class AgatEncoder : public Encoder
{
public:
AgatEncoder(const EncoderProto& config):
Encoder(config),
_config(config.agat())
{
}
private:
void writeRawBits(uint64_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeByte(uint8_t byte)
{
Bytes b;
b.writer().write_8(byte);
writeBytes(b);
}
void writeFillerRawBytes(int count, uint16_t byte)
{
for (int i = 0; i < count; i++)
writeRawBits(byte, 16);
};
void writeFillerBytes(int count, uint8_t byte)
{
Bytes b{byte};
for (int i = 0; i < count; i++)
writeBytes(b);
};
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
auto trackLayout = Layout::getLayoutOfTrack(
trackInfo->logicalTrack, trackInfo->logicalSide);
double clockRateUs = _config.target_clock_period_us() / 2.0;
int bitsPerRevolution =
(_config.target_rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa);
for (const auto& sector : sectors)
{
/* Header */
writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa);
writeRawBits(SECTOR_ID, 64);
writeByte(0x5a);
writeByte((sector->logicalTrack << 1) | sector->logicalSide);
writeByte(sector->logicalSector);
writeByte(0x5a);
/* Data */
writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa);
auto data = sector->data.slice(0, AGAT_SECTOR_SIZE);
writeRawBits(DATA_ID, 64);
writeBytes(data);
writeByte(agatChecksum(data));
writeByte(0x5a);
}
if (_cursor >= _bits.size())
error("track data overrun");
fillBitmapTo(_bits, _cursor, _bits.size(), {true, false});
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBits(_bits,
calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3,
_config.target_rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const AgatEncoderProto& _config;
uint32_t _cursor;
bool _lastBit;
std::vector<bool> _bits;
};
std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config)
{
return std::unique_ptr<Encoder>(new AgatEncoder(config));
}

View File

@@ -1,7 +1,7 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "amiga.h" #include "amiga.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
uint32_t amigaChecksum(const Bytes& bytes) uint32_t amigaChecksum(const Bytes& bytes)
@@ -18,61 +18,61 @@ uint32_t amigaChecksum(const Bytes& bytes)
static uint8_t everyother(uint16_t x) static uint8_t everyother(uint16_t x)
{ {
/* aabb ccdd eeff gghh */ /* aabb ccdd eeff gghh */
x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */ x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */
x >>= 1; /* 00ab 00cd 00ef 00gh */ x >>= 1; /* 00ab 00cd 00ef 00gh */
x |= x << 2; /* abab cdcd efef ghgh */ x |= x << 2; /* abab cdcd efef ghgh */
x &= 0x3c3c; /* 00ab cd00 00ef gh00 */ x &= 0x3c3c; /* 00ab cd00 00ef gh00 */
x >>= 2; /* 0000 abcd 0000 efgh */ x >>= 2; /* 0000 abcd 0000 efgh */
x |= x >> 4; /* 0000 abcd abcd efgh */ x |= x >> 4; /* 0000 abcd abcd efgh */
return x; return x;
} }
Bytes amigaInterleave(const Bytes& input) Bytes amigaInterleave(const Bytes& input)
{ {
Bytes output; Bytes output;
ByteWriter bw(output); ByteWriter bw(output);
/* Write all odd bits. (Numbering starts at 0...) */ /* Write all odd bits. (Numbering starts at 0...) */
{ {
ByteReader br(input); ByteReader br(input);
while (!br.eof()) while (!br.eof())
{ {
uint16_t x = br.read_be16(); uint16_t x = br.read_be16();
x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */ x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */
x |= x >> 1; /* aabb ccdd eeff gghh */ x |= x >> 1; /* aabb ccdd eeff gghh */
x = everyother(x); /* 0000 0000 abcd efgh */ x = everyother(x); /* 0000 0000 abcd efgh */
bw.write_8(x); bw.write_8(x);
} }
} }
/* Write all even bits. */ /* Write all even bits. */
{ {
ByteReader br(input); ByteReader br(input);
while (!br.eof()) while (!br.eof())
{ {
uint16_t x = br.read_be16(); uint16_t x = br.read_be16();
x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */ x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */
x |= x << 1; /* aabb ccdd eeff gghh */ x |= x << 1; /* aabb ccdd eeff gghh */
x = everyother(x); /* 0000 0000 abcd efgh */ x = everyother(x); /* 0000 0000 abcd efgh */
bw.write_8(x); bw.write_8(x);
} }
} }
return output; return output;
} }
Bytes amigaDeinterleave(const uint8_t*& input, size_t len) Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
{ {
assert(!(len & 1)); assert(!(len & 1));
const uint8_t* odds = &input[0]; const uint8_t* odds = &input[0];
const uint8_t* evens = &input[len / 2]; const uint8_t* evens = &input[len/2];
Bytes output; Bytes output;
ByteWriter bw(output); ByteWriter bw(output);
for (size_t i = 0; i < len / 2; i++) for (size_t i=0; i<len/2; i++)
{ {
uint8_t o = *odds++; uint8_t o = *odds++;
uint8_t e = *evens++; uint8_t e = *evens++;
@@ -81,15 +81,11 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
* http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*/ */
uint16_t result = uint16_t result =
(((e * 0x0101010101010101ULL & 0x8040201008040201ULL) * (((e * 0x0101010101010101ULL & 0x8040201008040201ULL)
0x0102040810204081ULL >> * 0x0102040810204081ULL >> 49) & 0x5555) |
49) & (((o * 0x0101010101010101ULL & 0x8040201008040201ULL)
0x5555) | * 0x0102040810204081ULL >> 48) & 0xAAAA);
(((o * 0x0101010101010101ULL & 0x8040201008040201ULL) *
0x0102040810204081ULL >>
48) &
0xAAAA);
bw.write_be16(result); bw.write_be16(result);
} }
@@ -99,6 +95,6 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
Bytes amigaDeinterleave(const Bytes& input) Bytes amigaDeinterleave(const Bytes& input)
{ {
const uint8_t* ptr = input.cbegin(); const uint8_t* ptr = input.cbegin();
return amigaDeinterleave(ptr, input.size()); return amigaDeinterleave(ptr, input.size());
} }

View File

@@ -1,16 +1,16 @@
#ifndef AMIGA_H #ifndef AMIGA_H
#define AMIGA_H #define AMIGA_H
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#define AMIGA_SECTOR_RECORD 0xaaaa44894489LL #define AMIGA_SECTOR_RECORD 0xaaaa44894489LL
#define AMIGA_TRACKS_PER_DISK 80 #define AMIGA_TRACKS_PER_DISK 80
#define AMIGA_SECTORS_PER_TRACK 11 #define AMIGA_SECTORS_PER_TRACK 11
#define AMIGA_RECORD_SIZE 0x21c #define AMIGA_RECORD_SIZE 0x21f
extern std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config); extern std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createAmigaEncoder(const EncoderProto& config); extern std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config);
extern uint32_t amigaChecksum(const Bytes& bytes); extern uint32_t amigaChecksum(const Bytes& bytes);
extern Bytes amigaInterleave(const Bytes& input); extern Bytes amigaInterleave(const Bytes& input);

View File

@@ -1,6 +1,6 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
message AmigaDecoderProto {} message AmigaDecoderProto {}

View File

@@ -1,85 +1,85 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "amiga.h" #include "amiga.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "lib/decoders/decoders.pb.h" #include "lib/decoders/decoders.pb.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
/* /*
* Amiga disks use MFM but it's not quite the same as IBM MFM. They only use * Amiga disks use MFM but it's not quite the same as IBM MFM. They only use
* a single type of record with a different marker byte. * a single type of record with a different marker byte.
* *
* See the big comment in the IBM MFM decoder for the gruesome details of how * See the big comment in the IBM MFM decoder for the gruesome details of how
* MFM works. * MFM works.
*/ */
static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD); static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD);
class AmigaDecoder : public Decoder class AmigaDecoder : public AbstractDecoder
{ {
public: public:
AmigaDecoder(const DecoderProto& config): AmigaDecoder(const DecoderProto& config):
Decoder(config), AbstractDecoder(config),
_config(config.amiga()) _config(config.amiga())
{ {}
}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord() override
{ {
return seekToPattern(SECTOR_PATTERN); _sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
} if (_fmr->eof() || !_sector->clock)
return UNKNOWN_RECORD;
return SECTOR_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord() override
{ {
if (readRaw48() != AMIGA_SECTOR_RECORD) const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16);
return; if (rawbits.size() < (AMIGA_RECORD_SIZE*16))
return;
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE * 16); const uint8_t* ptr = bytes.begin() + 3;
if (rawbits.size() < (AMIGA_RECORD_SIZE * 16))
return;
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE * 2);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
const uint8_t* ptr = bytes.begin(); Bytes header = amigaDeinterleave(ptr, 4);
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
Bytes header = amigaDeinterleave(ptr, 4); _sector->logicalTrack = header[1] >> 1;
Bytes recoveryinfo = amigaDeinterleave(ptr, 16); _sector->logicalSide = header[1] & 1;
_sector->logicalSector = header[2];
_sector->logicalTrack = header[1] >> 1; uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
_sector->logicalSide = header[1] & 1; uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(6, 40));
_sector->logicalSector = header[2]; if (gotheaderchecksum != wantedheaderchecksum)
return;
uint32_t wantedheaderchecksum = uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
amigaDeinterleave(ptr, 4).reader().read_be32(); uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(62, 1024));
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40));
if (gotheaderchecksum != wantedheaderchecksum)
return;
uint32_t wanteddatachecksum = Bytes data;
amigaDeinterleave(ptr, 4).reader().read_be32(); data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024)); _sector->data = data;
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
Bytes data; std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); {
_sector->data = data; static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
_sector->status = (gotdatachecksum == wanteddatachecksum) return sectors;
? Sector::OK }
: Sector::BAD_CHECKSUM;
}
private: private:
const AmigaDecoderProto& _config; const AmigaDecoderProto& _config;
nanoseconds_t _clock;
}; };
std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new AmigaDecoder(config)); return std::unique_ptr<AbstractDecoder>(new AmigaDecoder(config));
} }

View File

@@ -1,10 +1,10 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/core/utils.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "amiga.h" #include "amiga.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "arch/amiga/amiga.pb.h" #include "arch/amiga/amiga.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
@@ -12,138 +12,151 @@ static bool lastBit;
static int charToInt(char c) static int charToInt(char c)
{ {
if (isdigit(c)) if (isdigit(c))
return c - '0'; return c - '0';
return 10 + tolower(c) - 'a'; return 10 + tolower(c) - 'a';
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{ {
for (bool bit : src) for (bool bit : src)
{ {
if (cursor < bits.size()) if (cursor < bits.size())
lastBit = bits[cursor++] = bit; lastBit = bits[cursor++] = bit;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{ {
cursor += width; cursor += width;
lastBit = data & 1; lastBit = data & 1;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = cursor - i - 1; unsigned pos = cursor - i - 1;
if (pos < bits.size()) if (pos < bits.size())
bits[pos] = data & 1; bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{ {
ByteReader br(bytes); ByteReader br(bytes);
BitReader bitr(br); BitReader bitr(br);
while (!bitr.eof()) while (!bitr.eof())
{ {
if (cursor < bits.size()) if (cursor < bits.size())
bits[cursor++] = bitr.get(); bits[cursor++] = bitr.get();
} }
} }
static void write_sector(std::vector<bool>& bits, static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{ {
if ((sector->data.size() != 512) && (sector->data.size() != 528)) if ((sector->data.size() != 512) && (sector->data.size() != 528))
error("unsupported sector size --- you must pick 512 or 528"); Error() << "unsupported sector size --- you must pick 512 or 528";
uint32_t checksum = 0; uint32_t checksum = 0;
auto write_interleaved_bytes = [&](const Bytes& bytes) auto write_interleaved_bytes = [&](const Bytes& bytes)
{ {
Bytes interleaved = amigaInterleave(bytes); Bytes interleaved = amigaInterleave(bytes);
Bytes mfm = encodeMfm(interleaved, lastBit); Bytes mfm = encodeMfm(interleaved, lastBit);
checksum ^= amigaChecksum(mfm); checksum ^= amigaChecksum(mfm);
checksum &= 0x55555555; checksum &= 0x55555555;
write_bits(bits, cursor, mfm); write_bits(bits, cursor, mfm);
}; };
auto write_interleaved_word = [&](uint32_t word) auto write_interleaved_word = [&](uint32_t word)
{ {
Bytes b(4); Bytes b(4);
b.writer().write_be32(word); b.writer().write_be32(word);
write_interleaved_bytes(b); write_interleaved_bytes(b);
}; };
write_bits(bits, cursor, 0xaaaa, 2 * 8); write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6*8);
write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6 * 8);
checksum = 0; checksum = 0;
Bytes header = {0xff, /* Amiga 1.0 format byte */ Bytes header =
(uint8_t)((sector->logicalTrack << 1) | sector->logicalSide), {
(uint8_t)sector->logicalSector, 0xff, /* Amiga 1.0 format byte */
(uint8_t)(AMIGA_SECTORS_PER_TRACK - sector->logicalSector)}; (uint8_t) ((sector->logicalTrack<<1) | sector->logicalSide),
write_interleaved_bytes(header); (uint8_t) sector->logicalSector,
Bytes recoveryInfo(16); (uint8_t) (AMIGA_SECTORS_PER_TRACK - sector->logicalSector)
if (sector->data.size() == 528) };
recoveryInfo = sector->data.slice(512, 16); write_interleaved_bytes(header);
write_interleaved_bytes(recoveryInfo); Bytes recoveryInfo(16);
write_interleaved_word(checksum); if (sector->data.size() == 528)
recoveryInfo = sector->data.slice(512, 16);
write_interleaved_bytes(recoveryInfo);
write_interleaved_word(checksum);
Bytes data = sector->data.slice(0, 512); Bytes data = sector->data.slice(0, 512);
write_interleaved_word( write_interleaved_word(amigaChecksum(encodeMfm(amigaInterleave(data), lastBit)));
amigaChecksum(encodeMfm(amigaInterleave(data), lastBit))); write_interleaved_bytes(data);
write_interleaved_bytes(data);
} }
class AmigaEncoder : public Encoder class AmigaEncoder : public AbstractEncoder
{ {
public: public:
AmigaEncoder(const EncoderProto& config): AmigaEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.amiga()) _config(config.amiga()) {}
{
}
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
/* Number of bits for one nominal revolution of a real 200ms Amiga disk.
*/
int bitsPerRevolution = 200e3 / _config.clock_rate_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits, if ((physicalTrack >= 0) && (physicalTrack < AMIGA_TRACKS_PER_DISK))
cursor, {
_config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
{true, false}); {
lastBit = false; const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
for (const auto& sector : sectors) return sectors;
write_sector(bits, cursor, sector); }
if (cursor >= bits.size()) std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
error("track data overrun"); const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
fillBitmapTo(bits, cursor, bits.size(), {true, false}); {
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
auto fluxmap = std::make_unique<Fluxmap>(); int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
fluxmap->appendBits(bits, std::vector<bool> bits(bitsPerRevolution);
calculatePhysicalClockPeriod(_config.clock_rate_us() * 1e3, 200e6)); unsigned cursor = 0;
return fluxmap;
} fillBitmapTo(bits, cursor, _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), { true, false });
lastBit = false;
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
{
const auto& sectorData = image.get(physicalTrack, physicalSide, sectorId);
if (sectorData)
write_sector(bits, cursor, sectorData);
}
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
return fluxmap;
}
private: private:
const AmigaEncoderProto& _config; const AmigaEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createAmigaEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new AmigaEncoder(config)); return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
} }

View File

@@ -1,19 +1,13 @@
#ifndef APPLE2_H #ifndef APPLE2_H
#define APPLE2_H #define APPLE2_H
#include <memory.h> #define APPLE2_SECTOR_RECORD 0xd5aa96
#include "lib/decoders/decoders.h" #define APPLE2_DATA_RECORD 0xd5aaad
#include "lib/encoders/encoders.h"
#define APPLE2_SECTOR_RECORD 0xd5aa96 #define APPLE2_SECTOR_LENGTH 256
#define APPLE2_DATA_RECORD 0xd5aaad
#define APPLE2_SECTOR_LENGTH 256
#define APPLE2_ENCODED_SECTOR_LENGTH 342 #define APPLE2_ENCODED_SECTOR_LENGTH 342
#define APPLE2_SECTORS 16 extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config);
#endif #endif

View File

@@ -1,22 +1,4 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; message Apple2DecoderProto {}
message Apple2DecoderProto {
optional uint32 side_one_track_offset = 1
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}
message Apple2EncoderProto
{
/* 245kHz. */
optional double clock_period_us = 1
[ default = 4, (help) = "clock rate on the real device" ];
/* Apple II disk drives spin at 300rpm. */
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
optional uint32 side_one_track_offset = 3
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}

View File

@@ -1,39 +1,33 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "apple2.h" #include "apple2.h"
#include "arch/apple2/apple2.pb.h" #include "bytes.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/core/bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr) static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
/* This is extremely inspired by the MESS implementation, written by Nathan /* This is extremely inspired by the MESS implementation, written by Nathan Woods
* Woods and R. Belmont: * and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
*/ */
static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{ {
@@ -53,11 +47,9 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{ {
/* 3 * 2 bit */ /* 3 * 2 bit */
output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02); output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02);
output[i + 86] = output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
if ((i + 172) < APPLE2_SECTOR_LENGTH) if ((i + 172) < APPLE2_SECTOR_LENGTH)
output[i + 172] = output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
} }
} }
@@ -72,105 +64,63 @@ static uint8_t combine(uint16_t word)
return word & (word >> 7); return word & (word >> 7);
} }
class Apple2Decoder : public Decoder class Apple2Decoder : public AbstractDecoder
{ {
public: public:
Apple2Decoder(const DecoderProto& config): Decoder(config) {} Apple2Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
if (readRaw24() != APPLE2_SECTOR_RECORD) /* Skip ID (as we know it's a APPLE2_SECTOR_RECORD). */
return; readRawBits(24);
/* Read header. */ /* Read header. */
auto header = toBytes(readRawBits(8 * 8)).slice(0, 8); auto header = toBytes(readRawBits(8*8)).slice(0, 8);
ByteReader br(header); ByteReader br(header);
uint8_t volume = combine(br.read_be16()); uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16()); _sector->logicalTrack = combine(br.read_be16());
_sector->logicalSide = _sector->physicalSide; _sector->logicalSector = combine(br.read_be16());
_sector->logicalSector = combine(br.read_be16()); uint8_t checksum = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16()); if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
// If the checksum is correct, upgrade the sector from MISSING void decodeDataRecord()
// to DATA_MISSING in anticipation of its data record {
if (checksum == /* Check ID. */
(volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
if (_sector->logicalSide == 1) Bytes bytes = toBytes(readRawBits(3*8)).slice(0, 3);
_sector->logicalTrack -= _config.apple2().side_one_track_offset(); if (bytes.reader().read_be24() != APPLE2_DATA_RECORD)
return;
/* Sanity check. */ /* Read and decode data. */
if (_sector->logicalTrack > 100) unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
{ bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength);
_sector->status = Sector::MISSING;
return;
}
}
void decodeDataRecord() override _sector->status = Sector::BAD_CHECKSUM;
{ _sector->data = decode_crazy_data(&bytes[0], _sector->status);
/* Check ID. */ }
if (readRaw24() != APPLE2_DATA_RECORD)
return;
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
/* Read and decode data. */
auto readApple8 = [&]()
{
auto result = 0;
while ((result & 0x80) == 0)
{
auto b = readRawBits(1);
if (b.empty())
break;
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
uint8_t bytes[recordLength];
for (auto& byte : bytes)
{
byte = readApple8();
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
}; };
std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new Apple2Decoder(config)); return std::unique_ptr<AbstractDecoder>(new Apple2Decoder(config));
} }

View File

@@ -1,192 +0,0 @@
#include "lib/core/globals.h"
#include "lib/core/utils.h"
#include "arch/apple2/apple2.h"
#include "lib/decoders/decoders.h"
#include "lib/encoders/encoders.h"
#include "lib/data/sector.h"
#include "lib/data/image.h"
#include "fmt/format.h"
#include "lib/encoders/encoders.pb.h"
#include <ctype.h>
#include "lib/core/bytes.h"
static int encode_data_gcr(uint8_t data)
{
switch (data)
{
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
class Apple2Encoder : public Encoder
{
public:
Apple2Encoder(const EncoderProto& config):
Encoder(config),
_config(config.apple2())
{
}
private:
const Apple2EncoderProto& _config;
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
int bitsPerRevolution =
(_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sector : sectors)
writeSector(bits, cursor, *sector);
if (cursor >= bits.size())
error("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false});
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
private:
uint8_t volume_id = 254;
/* This is extremely inspired by the MESS implementation, written by Nathan
* Woods and R. Belmont:
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
* as well as Understanding the Apple II (1983) Chapter 9
* https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
*/
void writeSector(
std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
{
if ((sector.status == Sector::OK) or
(sector.status == Sector::BAD_CHECKSUM))
{
auto write_bit = [&](bool val)
{
if (cursor <= bits.size())
{
bits[cursor] = val;
}
cursor++;
};
auto write_bits = [&](uint32_t bits, int width)
{
for (int i = width; i--;)
{
write_bit(bits & (1u << i));
}
};
auto write_gcr44 = [&](uint8_t value)
{
write_bits((value << 7) | value | 0xaaaa, 16);
};
auto write_gcr6 = [&](uint8_t value)
{
write_bits(encode_data_gcr(value), 8);
};
// The special "FF40" sequence is used to synchronize the receiving
// shift register. It's written as "1111 1111 00"; FF indicates the
// 8 consecutive 1-bits, while "40" indicates the total number of
// microseconds.
auto write_ff40 = [&](int n = 1)
{
for (; n--;)
{
write_bits(0xff << 2, 10);
}
};
// There is data to encode to disk.
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
error("unsupported sector size {} --- you must pick 256",
sector.data.size());
// Write address syncing leader : A sequence of "FF40"s; 5 of them
// are said to suffice to synchronize the decoder.
// "FF40" indicates that the actual data written is "1111
// 1111 00" i.e., 8 1s and a total of 40 microseconds
//
// In standard formatting, the first logical sector apparently gets
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
int track = sector.logicalTrack;
if (sector.logicalSide == 1)
track += _config.side_one_track_offset();
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +
// DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(track);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ track ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector
// data + sum + DE AA EB (+ mystery bits cut off of the scan?)
write_ff40(8);
write_bits(APPLE2_DATA_RECORD, 24);
// Convert the sector data to GCR, append the checksum, and write it
// out
constexpr auto TWOBIT_COUNT =
0x56; // Size of the 'twobit' area at the start of the GCR data
uint8_t checksum = 0;
for (int i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++)
{
int value;
if (i >= TWOBIT_COUNT)
{
value = sector.data[i - TWOBIT_COUNT] >> 2;
}
else
{
uint8_t tmp = sector.data[i];
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
tmp = sector.data[i + TWOBIT_COUNT];
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
if (i + 2 * TWOBIT_COUNT < APPLE2_SECTOR_LENGTH)
{
tmp = sector.data[i + 2 * TWOBIT_COUNT];
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
}
}
checksum ^= value;
// assert(checksum & ~0x3f == 0);
write_gcr6(checksum);
checksum = value;
}
if (sector.status == Sector::BAD_CHECKSUM)
checksum ^= 0x3f;
write_gcr6(checksum);
write_bits(0xDEAAEB, 24);
}
}
};
std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config)
{
return std::unique_ptr<Encoder>(new Apple2Encoder(config));
}

View File

@@ -1,97 +0,0 @@
#include "lib/core/globals.h"
#include "lib/encoders/encoders.h"
#include "lib/decoders/decoders.h"
#include "lib/config/config.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/rolandd20/rolandd20.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tartu/tartu.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "arch/arch.h"
std::unique_ptr<Encoder> Arch::createEncoder(Config& config)
{
if (!config.hasEncoder())
error("no encoder configured");
return createEncoder(config->encoder());
}
std::unique_ptr<Encoder> Arch::createEncoder(const EncoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Encoder>(const EncoderProto&)>>
encoders = {
{EncoderProto::kAgat, createAgatEncoder },
{EncoderProto::kAmiga, createAmigaEncoder },
{EncoderProto::kApple2, createApple2Encoder },
{EncoderProto::kBrother, createBrotherEncoder },
{EncoderProto::kC64, createCommodore64Encoder},
{EncoderProto::kIbm, createIbmEncoder },
{EncoderProto::kMacintosh, createMacintoshEncoder },
{EncoderProto::kMicropolis, createMicropolisEncoder },
{EncoderProto::kNorthstar, createNorthstarEncoder },
{EncoderProto::kTartu, createTartuEncoder },
{EncoderProto::kTids990, createTids990Encoder },
{EncoderProto::kVictor9K, createVictor9kEncoder },
};
auto encoder = encoders.find(config.format_case());
if (encoder == encoders.end())
error("no encoder specified");
return (encoder->second)(config);
}
std::unique_ptr<Decoder> Arch::createDecoder(Config& config)
{
if (!config.hasDecoder())
error("no decoder configured");
return createDecoder(config->decoder());
}
std::unique_ptr<Decoder> Arch::createDecoder(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kRolandd20, createRolandD20Decoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTartu, createTartuDecoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
error("no decoder specified");
return (decoder->second)(config);
}

View File

@@ -1,16 +0,0 @@
#pragma once
class Encoder;
class Decoder;
class DecoderProto;
class EncoderProto;
class Config;
namespace Arch
{
std::unique_ptr<Decoder> createDecoder(Config& config);
std::unique_ptr<Decoder> createDecoder(const DecoderProto& config);
std::unique_ptr<Encoder> createEncoder(Config& config);
std::unique_ptr<Encoder> createEncoder(const EncoderProto& config);
}

View File

@@ -3,19 +3,17 @@
/* Brother word processor format (or at least, one of them) */ /* Brother word processor format (or at least, one of them) */
#define BROTHER_SECTOR_RECORD 0xFFFFFD57 #define BROTHER_SECTOR_RECORD 0xFFFFFD57
#define BROTHER_DATA_RECORD 0xFFFFFDDB #define BROTHER_DATA_RECORD 0xFFFFFDDB
#define BROTHER_DATA_RECORD_PAYLOAD 256 #define BROTHER_DATA_RECORD_PAYLOAD 256
#define BROTHER_DATA_RECORD_CHECKSUM 3 #define BROTHER_DATA_RECORD_CHECKSUM 3
#define BROTHER_DATA_RECORD_ENCODED_SIZE 415 #define BROTHER_DATA_RECORD_ENCODED_SIZE 415
#define BROTHER_TRACKS_PER_240KB_DISK 78 #define BROTHER_TRACKS_PER_240KB_DISK 78
#define BROTHER_TRACKS_PER_120KB_DISK 39 #define BROTHER_TRACKS_PER_120KB_DISK 39
#define BROTHER_SECTORS_PER_TRACK 12 #define BROTHER_SECTORS_PER_TRACK 12
extern std::unique_ptr<Decoder> createBrotherDecoder( extern std::unique_ptr<AbstractDecoder> createBrotherDecoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createBrotherEncoder(
const EncoderProto& config);
#endif #endif

View File

@@ -12,7 +12,9 @@ message BrotherEncoderProto {
optional double post_index_gap_ms = 2 [default = 1.0]; optional double post_index_gap_ms = 2 [default = 1.0];
optional double sector_spacing_ms = 3 [default = 16.2]; optional double sector_spacing_ms = 3 [default = 16.2];
optional double post_header_spacing_ms = 4 [default = 0.69]; optional double post_header_spacing_ms = 4 [default = 0.69];
optional string sector_skew = 5 [default = "05a3816b4927"];
optional BrotherFormat format = 6 [default = BROTHER240]; optional BrotherFormat format = 6 [default = BROTHER240];
optional int32 bias = 7 [default = 0];
} }

View File

@@ -1,13 +1,13 @@
GCR_ENTRY(0x55, 0) // 00000 GCR_ENTRY(0x55, 0) // 00000
GCR_ENTRY(0x57, 1) // 00001 GCR_ENTRY(0x57, 1) // 00001
GCR_ENTRY(0x5b, 2) // 00010 GCR_ENTRY(0x5b, 2) // 00010
GCR_ENTRY(0x5d, 3) // 00011 GCR_ENTRY(0x5d, 3) // 00011
GCR_ENTRY(0x5f, 4) // 00100 GCR_ENTRY(0x5f, 4) // 00100
GCR_ENTRY(0x6b, 5) // 00101 GCR_ENTRY(0x6b, 5) // 00101
GCR_ENTRY(0x6d, 6) // 00110 GCR_ENTRY(0x6d, 6) // 00110
GCR_ENTRY(0x6f, 7) // 00111 GCR_ENTRY(0x6f, 7) // 00111
GCR_ENTRY(0x75, 8) // 01000 GCR_ENTRY(0x75, 8) // 01000
GCR_ENTRY(0x77, 9) // 01001 GCR_ENTRY(0x77, 9) // 01001
GCR_ENTRY(0x7b, 10) // 01010 GCR_ENTRY(0x7b, 10) // 01010
GCR_ENTRY(0x7d, 11) // 01011 GCR_ENTRY(0x7d, 11) // 01011
GCR_ENTRY(0x7f, 12) // 01100 GCR_ENTRY(0x7f, 12) // 01100
@@ -30,3 +30,4 @@ GCR_ENTRY(0xef, 28) // 11100
GCR_ENTRY(0xf5, 29) // 11101 GCR_ENTRY(0xf5, 29) // 11101
GCR_ENTRY(0xf7, 30) // 11110 GCR_ENTRY(0xf7, 30) // 11110
GCR_ENTRY(0xfb, 31) // 11111 GCR_ENTRY(0xfb, 31) // 11111

View File

@@ -1,19 +1,18 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "sql.h"
#include "lib/data/fluxmapreader.h" #include "fluxmap.h"
#include "lib/data/fluxpattern.h" #include "decoders/fluxmapreader.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#include "brother.h" #include "brother.h"
#include "lib/data/sector.h" #include "sector.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "lib/core/crc.h" #include "crc.h"
#include <ctype.h> #include <ctype.h>
const FluxPattern SECTOR_RECORD_PATTERN(32, BROTHER_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(32, BROTHER_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(32, BROTHER_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(32, BROTHER_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static std::vector<uint8_t> outputbuffer; static std::vector<uint8_t> outputbuffer;
@@ -34,89 +33,91 @@ static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
static int decode_header_gcr(uint16_t word) static int decode_header_gcr(uint16_t word)
{ {
switch (word) switch (word)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "header_gcr.h"
#include "header_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY }
} return -1;
return -1;
} }
class BrotherDecoder : public Decoder class BrotherDecoder : public AbstractDecoder
{ {
public: public:
BrotherDecoder(const DecoderProto& config): Decoder(config) {} BrotherDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
if (readRaw32() != BROTHER_SECTOR_RECORD) readRawBits(32);
return; const auto& rawbits = readRawBits(32);
const auto& bytes = toBytes(rawbits).slice(0, 4);
const auto& rawbits = readRawBits(32); ByteReader br(bytes);
const auto& bytes = toBytes(rawbits).slice(0, 4); _sector->logicalTrack = decode_header_gcr(br.read_be16());
_sector->logicalSector = decode_header_gcr(br.read_be16());
ByteReader br(bytes); /* Sanity check the values read; there's no header checksum and
_sector->logicalTrack = decode_header_gcr(br.read_be16()); * occasionally we get garbage due to bit errors. */
_sector->logicalSector = decode_header_gcr(br.read_be16()); if (_sector->logicalSector > 11)
return;
if (_sector->logicalTrack > 79)
return;
/* Sanity check the values read; there's no header checksum and _sector->status = Sector::DATA_MISSING;
* occasionally we get garbage due to bit errors. */ }
if (_sector->logicalSector > 11)
return; void decodeDataRecord()
if (_sector->logicalTrack > 79) {
return; readRawBits(32);
_sector->status = Sector::DATA_MISSING; const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8);
} const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE);
void decodeDataRecord() override Bytes bytes;
{ ByteWriter bw(bytes);
if (readRaw32() != BROTHER_DATA_RECORD) BitWriter bitw(bw);
return; for (uint8_t b : rawbytes)
{
uint32_t nibble = decode_data_gcr(b);
bitw.push(nibble, 5);
}
bitw.flush();
const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE * 8); _sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD);
const auto& rawbytes = uint32_t realCrc = crcbrother(_sector->data);
toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE); uint32_t wantCrc = bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24();
_sector->status = (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
Bytes bytes; }
ByteWriter bw(bytes);
BitWriter bitw(bw);
for (uint8_t b : rawbytes)
{
uint32_t nibble = decode_data_gcr(b);
bitw.push(nibble, 5);
}
bitw.flush();
_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD);
uint32_t realCrc = crcbrother(_sector->data);
uint32_t wantCrc =
bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24();
_sector->status =
(realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
}
}; };
std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createBrotherDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new BrotherDecoder(config)); return std::unique_ptr<AbstractDecoder>(new BrotherDecoder(config));
} }

View File

@@ -1,154 +1,212 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/core/utils.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "brother.h" #include "brother.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "arch/brother/brother.pb.h" #include "arch/brother/brother.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
static int encode_header_gcr(uint16_t word) static int encode_header_gcr(uint16_t word)
{ {
switch (word) switch (word)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case data: \ case data: return gcr;
return gcr; #include "header_gcr.h"
#include "header_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY }
} return -1;
return -1;
} }
static int encode_data_gcr(uint8_t data) static int encode_data_gcr(uint8_t data)
{ {
switch (data) switch (data)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case data: \ case data: return gcr;
return gcr; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY }
} return -1;
return -1;
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
{ {
cursor += width; cursor += width;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = cursor - i - 1; unsigned pos = cursor - i - 1;
if (pos < bits.size()) if (pos < bits.size())
bits[pos] = data & 1; bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
static void write_sector_header( static void write_sector_header(std::vector<bool>& bits, unsigned& cursor,
std::vector<bool>& bits, unsigned& cursor, int track, int sector) int track, int sector)
{ {
write_bits(bits, cursor, 0xffffffff, 31); write_bits(bits, cursor, 0xffffffff, 31);
write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32); write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32);
write_bits(bits, cursor, encode_header_gcr(track), 16); write_bits(bits, cursor, encode_header_gcr(track), 16);
write_bits(bits, cursor, encode_header_gcr(sector), 16); write_bits(bits, cursor, encode_header_gcr(sector), 16);
write_bits(bits, cursor, encode_header_gcr(0x2f), 16); write_bits(bits, cursor, encode_header_gcr(0x2f), 16);
} }
static void write_sector_data( static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
{ {
write_bits(bits, cursor, 0xffffffff, 32); write_bits(bits, cursor, 0xffffffff, 32);
write_bits(bits, cursor, BROTHER_DATA_RECORD, 32); write_bits(bits, cursor, BROTHER_DATA_RECORD, 32);
uint16_t fifo = 0; uint16_t fifo = 0;
int width = 0; int width = 0;
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD) if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
error("unsupported sector size"); Error() << "unsupported sector size";
auto write_byte = [&](uint8_t byte) auto write_byte = [&](uint8_t byte)
{ {
fifo |= (byte << (8 - width)); fifo |= (byte << (8 - width));
width += 8; width += 8;
while (width >= 5) while (width >= 5)
{ {
uint8_t quintet = fifo >> 11; uint8_t quintet = fifo >> 11;
fifo <<= 5; fifo <<= 5;
width -= 5; width -= 5;
write_bits(bits, cursor, encode_data_gcr(quintet), 8); write_bits(bits, cursor, encode_data_gcr(quintet), 8);
} }
}; };
for (uint8_t byte : data) for (uint8_t byte : data)
write_byte(byte); write_byte(byte);
uint32_t realCrc = crcbrother(data); uint32_t realCrc = crcbrother(data);
write_byte(realCrc >> 16); write_byte(realCrc>>16);
write_byte(realCrc >> 8); write_byte(realCrc>>8);
write_byte(realCrc); write_byte(realCrc);
write_byte(0x58); /* magic */ write_byte(0x58); /* magic */
write_byte(0xd4); write_byte(0xd4);
while (width != 0) while (width != 0)
write_byte(0); write_byte(0);
} }
class BrotherEncoder : public Encoder static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}
class BrotherEncoder : public AbstractEncoder
{ {
public: public:
BrotherEncoder(const EncoderProto& config): BrotherEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.brother()) _config(config.brother())
{ {}
}
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
int sectorCount = 0; int logicalTrack;
for (const auto& sectorData : sectors) if (physicalSide != 0)
return sectors;
physicalTrack -= _config.bias();
switch (_config.format())
{
case BROTHER120:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return sectors;
logicalTrack = physicalTrack/2;
break;
case BROTHER240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return sectors;
logicalTrack = physicalTrack;
break;
}
for (int sectorId=0; sectorId<BROTHER_SECTORS_PER_TRACK; sectorId++)
{ {
double headerMs = _config.post_index_gap_ms() + const auto& sector = image.get(logicalTrack, 0, sectorId);
sectorCount * _config.sector_spacing_ms(); if (sector)
unsigned headerCursor = headerMs * 1e3 / _config.clock_rate_us(); sectors.push_back(sector);
double dataMs = headerMs + _config.post_header_spacing_ms(); }
unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us();
fillBitmapTo(bits, cursor, headerCursor, {true, false}); return sectors;
write_sector_header(bits, }
cursor,
sectorData->logicalTrack,
sectorData->logicalSector);
fillBitmapTo(bits, cursor, dataCursor, {true, false});
write_sector_data(bits, cursor, sectorData->data);
sectorCount++; std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
} const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int logicalTrack;
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
physicalTrack -= _config.bias();
switch (_config.format())
{
case BROTHER120:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack/2;
break;
if (cursor >= bits.size()) case BROTHER240:
error("track data overrun"); if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
fillBitmapTo(bits, cursor, bits.size(), {true, false}); return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack;
break;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
fluxmap->appendBits(bits, _config.clock_rate_us() * 1e3); const std::string& skew = _config.sector_skew();
return fluxmap; std::vector<bool> bits(bitsPerRevolution);
} unsigned cursor = 0;
for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++)
{
int sectorId = charToInt(skew.at(sectorCount));
double headerMs = _config.post_index_gap_ms() + sectorCount*_config.sector_spacing_ms();
unsigned headerCursor = headerMs*1e3 / _config.clock_rate_us();
double dataMs = headerMs + _config.post_header_spacing_ms();
unsigned dataCursor = dataMs*1e3 / _config.clock_rate_us();
const auto& sectorData = image.get(logicalTrack, 0, sectorId);
fillBitmapTo(bits, cursor, headerCursor, { true, false });
write_sector_header(bits, cursor, logicalTrack, sectorId);
fillBitmapTo(bits, cursor, dataCursor, { true, false });
write_sector_data(bits, cursor, sectorData->data);
}
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });
// The pre-index gap is not normally reported.
// std::cerr << "pre-index gap " << 200.0 - (double)cursor*clockRateUs/1e3 << std::endl;
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
return fluxmap;
}
private: private:
const BrotherEncoderProto& _config; const BrotherEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createBrotherEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new BrotherEncoder(config)); return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
} }

View File

@@ -76,3 +76,4 @@ GCR_ENTRY(0x6BAB, 74)
GCR_ENTRY(0xAD5F, 75) GCR_ENTRY(0xAD5F, 75)
GCR_ENTRY(0xDBED, 76) GCR_ENTRY(0xDBED, 76)
GCR_ENTRY(0x55BB, 77) GCR_ENTRY(0x55BB, 77)

View File

@@ -1,104 +0,0 @@
from build.c import cxxlibrary
from build.protobuf import proto, protocc
proto(
name="proto",
srcs=[
"./aeslanier/aeslanier.proto",
"./agat/agat.proto",
"./amiga/amiga.proto",
"./apple2/apple2.proto",
"./brother/brother.proto",
"./c64/c64.proto",
"./f85/f85.proto",
"./fb100/fb100.proto",
"./ibm/ibm.proto",
"./macintosh/macintosh.proto",
"./micropolis/micropolis.proto",
"./mx/mx.proto",
"./northstar/northstar.proto",
"./rolandd20/rolandd20.proto",
"./smaky6/smaky6.proto",
"./tartu/tartu.proto",
"./tids990/tids990.proto",
"./victor9k/victor9k.proto",
"./zilogmcz/zilogmcz.proto",
],
deps=["lib/config+common_proto"],
)
protocc(
name="proto_lib", srcs=[".+proto"], deps=["lib/config+common_proto_lib"]
)
cxxlibrary(
name="arch",
srcs=[
"./arch.cc",
"./aeslanier/decoder.cc",
"./agat/agat.cc",
"./agat/decoder.cc",
"./agat/encoder.cc",
"./amiga/amiga.cc",
"./amiga/decoder.cc",
"./amiga/encoder.cc",
"./apple2/decoder.cc",
"./apple2/encoder.cc",
"./brother/decoder.cc",
"./brother/encoder.cc",
"./c64/c64.cc",
"./c64/decoder.cc",
"./c64/encoder.cc",
"./f85/decoder.cc",
"./fb100/decoder.cc",
"./ibm/decoder.cc",
"./ibm/encoder.cc",
"./macintosh/decoder.cc",
"./macintosh/encoder.cc",
"./micropolis/decoder.cc",
"./micropolis/encoder.cc",
"./mx/decoder.cc",
"./northstar/decoder.cc",
"./northstar/encoder.cc",
"./rolandd20/decoder.cc",
"./smaky6/decoder.cc",
"./tartu/decoder.cc",
"./tartu/encoder.cc",
"./tids990/decoder.cc",
"./tids990/encoder.cc",
"./victor9k/decoder.cc",
"./victor9k/encoder.cc",
"./zilogmcz/decoder.cc",
],
hdrs={
"arch/ibm/ibm.h": "./ibm/ibm.h",
"arch/apple2/data_gcr.h": "./apple2/data_gcr.h",
"arch/apple2/apple2.h": "./apple2/apple2.h",
"arch/amiga/amiga.h": "./amiga/amiga.h",
"arch/smaky6/smaky6.h": "./smaky6/smaky6.h",
"arch/tids990/tids990.h": "./tids990/tids990.h",
"arch/zilogmcz/zilogmcz.h": "./zilogmcz/zilogmcz.h",
"arch/amiga/amiga.h": "./amiga/amiga.h",
"arch/f85/data_gcr.h": "./f85/data_gcr.h",
"arch/f85/f85.h": "./f85/f85.h",
"arch/mx/mx.h": "./mx/mx.h",
"arch/aeslanier/aeslanier.h": "./aeslanier/aeslanier.h",
"arch/northstar/northstar.h": "./northstar/northstar.h",
"arch/brother/data_gcr.h": "./brother/data_gcr.h",
"arch/brother/brother.h": "./brother/brother.h",
"arch/brother/header_gcr.h": "./brother/header_gcr.h",
"arch/macintosh/data_gcr.h": "./macintosh/data_gcr.h",
"arch/macintosh/macintosh.h": "./macintosh/macintosh.h",
"arch/agat/agat.h": "./agat/agat.h",
"arch/fb100/fb100.h": "./fb100/fb100.h",
"arch/victor9k/data_gcr.h": "./victor9k/data_gcr.h",
"arch/victor9k/victor9k.h": "./victor9k/victor9k.h",
"arch/rolandd20/rolandd20.h": "./rolandd20/rolandd20.h",
"arch/micropolis/micropolis.h": "./micropolis/micropolis.h",
"arch/c64/data_gcr.h": "./c64/data_gcr.h",
"arch/c64/c64.h": "./c64/c64.h",
"arch/tartu/tartu.h": "./tartu/tartu.h",
"arch/arch.h": "./arch.h",
},
deps=["lib/core", "lib/data", "lib/config", "lib/encoders", "lib/decoders"],
)

View File

@@ -1,28 +0,0 @@
#include "lib/core/globals.h"
#include "c64.h"
/*
* Track Sectors/track # Sectors Storage in Bytes Clock rate
* ----- ------------- --------- ---------------- ----------
* 1-17 21 357 7820 3.25
* 18-24 19 133 7170 3.5
* 25-30 18 108 6300 3.75
* 31-40(*) 17 85 6020 4
* ---
* 683 (for a 35 track image)
*
* The clock rate is normalised for a 200ms drive.
*/
nanoseconds_t clockPeriodForC64Track(unsigned track)
{
constexpr double b = 8.0;
if (track < 17)
return 26.0 / b;
if (track < 24)
return 28.0 / b;
if (track < 30)
return 30.0 / b;
return 32.0 / b;
}

View File

@@ -1,14 +1,14 @@
#ifndef C64_H #ifndef C64_H
#define C64_H #define C64_H
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#define C64_SECTOR_RECORD 0xffd49 #define C64_SECTOR_RECORD 0xffd49
#define C64_DATA_RECORD 0xffd57 #define C64_DATA_RECORD 0xffd57
#define C64_SECTOR_LENGTH 256 #define C64_SECTOR_LENGTH 256
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
1. Header sync FF FF FF FF FF (40 'on' bits, not GCR) 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read) 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
@@ -17,21 +17,17 @@
6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read) 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
1. Header sync (SYNC for the next sector) 1. Header sync (SYNC for the next sector)
*/ */
#define C64_HEADER_DATA_SYNC 0xFF #define C64_HEADER_DATA_SYNC 0xFF
#define C64_HEADER_BLOCK_ID 0x08 #define C64_HEADER_BLOCK_ID 0x08
#define C64_DATA_BLOCK_ID 0x07 #define C64_DATA_BLOCK_ID 0x07
#define C64_HEADER_GAP 0x55 #define C64_HEADER_GAP 0x55
#define C64_INTER_SECTOR_GAP 0x55 #define C64_INTER_SECTOR_GAP 0x55
#define C64_PADDING 0x0F #define C64_PADDING 0x0F
#define C64_TRACKS_PER_DISK 40 #define C64_TRACKS_PER_DISK 40
#define C64_BAM_TRACK 17 #define C64_BAM_TRACK 17
extern std::unique_ptr<Decoder> createCommodore64Decoder( extern std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createCommodore64Encoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createCommodore64Encoder(
const EncoderProto& config);
extern nanoseconds_t clockPeriodForC64Track(unsigned track);
#endif #endif

View File

@@ -1,11 +1,13 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
message Commodore64DecoderProto {} message Commodore64DecoderProto {}
message Commodore64EncoderProto { message Commodore64EncoderProto {
optional double post_index_gap_us = 1 [default=0.0, optional double post_index_gap_us = 1 [default=0.0,
(help) = "post-index gap before first sector header."]; (help) = "post-index gap before first sector header."];
optional double clock_compensation_factor = 2 [default=1.0,
(help) = "scale the output clock by this much."];
} }

View File

@@ -1,31 +1,28 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "c64.h" #include "c64.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_RECORD_PATTERN(20, C64_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(20, C64_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(20, C64_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(20, C64_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr) static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
@@ -40,11 +37,11 @@ static Bytes decode(const std::vector<bool>& bits)
while (ii != bits.end()) while (ii != bits.end())
{ {
uint8_t inputfifo = 0; uint8_t inputfifo = 0;
for (size_t i = 0; i < 5; i++) for (size_t i=0; i<5; i++)
{ {
if (ii == bits.end()) if (ii == bits.end())
break; break;
inputfifo = (inputfifo << 1) | *ii++; inputfifo = (inputfifo<<1) | *ii++;
} }
bitw.push(decode_data_gcr(inputfifo), 4); bitw.push(decode_data_gcr(inputfifo), 4);
@@ -54,50 +51,56 @@ static Bytes decode(const std::vector<bool>& bits)
return output; return output;
} }
class Commodore64Decoder : public Decoder class Commodore64Decoder : public AbstractDecoder
{ {
public: public:
Commodore64Decoder(const DecoderProto& config): Decoder(config) {} Commodore64Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
if (readRaw20() != C64_SECTOR_RECORD) readRawBits(20);
return;
const auto& bits = readRawBits(5 * 10); const auto& bits = readRawBits(5*10);
const auto& bytes = decode(bits).slice(0, 5); const auto& bytes = decode(bits).slice(0, 5);
uint8_t checksum = bytes[0]; uint8_t checksum = bytes[0];
_sector->logicalSector = bytes[1]; _sector->logicalSector = bytes[1];
_sector->logicalSide = 0; _sector->logicalSide = 0;
_sector->logicalTrack = bytes[2] - 1; _sector->logicalTrack = bytes[2] - 1;
if (checksum == xorBytes(bytes.slice(1, 4))) if (checksum == xorBytes(bytes.slice(1, 4)))
_sector->status = _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
Sector::DATA_MISSING; /* unintuitive but correct */ }
}
void decodeDataRecord() override void decodeDataRecord()
{ {
if (readRaw20() != C64_DATA_RECORD) readRawBits(20);
return;
const auto& bits = readRawBits(259 * 10); const auto& bits = readRawBits(259*10);
const auto& bytes = decode(bits).slice(0, 259); const auto& bytes = decode(bits).slice(0, 259);
_sector->data = bytes.slice(0, C64_SECTOR_LENGTH); _sector->data = bytes.slice(0, C64_SECTOR_LENGTH);
uint8_t gotChecksum = xorBytes(_sector->data); uint8_t gotChecksum = xorBytes(_sector->data);
uint8_t wantChecksum = bytes[256]; uint8_t wantChecksum = bytes[256];
_sector->status = _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; }
}
}; };
std::unique_ptr<Decoder> createCommodore64Decoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new Commodore64Decoder(config)); return std::unique_ptr<AbstractDecoder>(new Commodore64Decoder(config));
} }

View File

@@ -1,104 +1,161 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/core/utils.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "c64.h" #include "c64.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/sector.h" #include "sector.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "arch/c64/c64.pb.h" #include "arch/c64/c64.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#include "lib/data/layout.h"
#include <ctype.h> #include <ctype.h>
#include "lib/core/bytes.h" #include "bytes.h"
static bool lastBit; static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
/*
* Track # Sectors/Track Speed Zone bits/rotation
* 1 17 21 3 61,538.4
* 18 24 19 2 57,142.8
* 25 30 18 1 53,333.4
* 31 35 17 0 50,000.0
*/
if (track < 17)
return 200000.0/61538.4;
if (track < 24)
return 200000.0/57142.8;
if (track < 30)
return 200000.0/53333.4;
return 200000.0/50000.0;
}
static unsigned sectorsForTrack(unsigned track)
{
/*
* Track Sectors/track # Sectors Storage in Bytes
* ----- ------------- --------- ----------------
* 1-17 21 357 7820
* 18-24 19 133 7170
* 25-30 18 108 6300
* 31-40(*) 17 85 6020
* ---
* 683 (for a 35 track image)
*/
if (track < 17)
return 21;
if (track < 24)
return 19;
if (track < 30)
return 18;
return 17;
}
static int encode_data_gcr(uint8_t data) static int encode_data_gcr(uint8_t data)
{ {
switch (data) switch (data)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case data: \ case data: return gcr;
return gcr; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{ {
for (bool bit : src) // Range-based for loop for (bool bit : src) //Range-based for loop
{ {
if (cursor < bits.size()) if (cursor < bits.size())
bits[cursor++] = bit; bits[cursor++] = bit;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{ {
cursor += width; cursor += width;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = cursor - i - 1; unsigned pos = cursor - i - 1;
if (pos < bits.size()) if (pos < bits.size())
bits[pos] = data & 1; bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
void bindump(std::ostream& stream, std::vector<bool>& buffer)
{
size_t pos = 0;
while ((pos < buffer.size()) and (pos <520))
{
stream << fmt::format("{:5d} : ", pos);
for (int i=0; i<40; i++)
{
if ((pos+i) < buffer.size())
stream << fmt::format("{:01b}", (buffer[pos+i]));
else
stream << "-- ";
if ((((pos + i + 1) % 8) == 0) and i != 0)
stream << " ";
}
stream << std::endl;
pos += 40;
}
}
static std::vector<bool> encode_data(uint8_t input) static std::vector<bool> encode_data(uint8_t input)
{ {
/* /*
* Four 8-bit data bytes are converted to four 10-bit GCR bytes at a time by * Four 8-bit data bytes are converted to four 10-bit GCR bytes at a time by
* the 1541 DOS. RAM is only an 8-bit storage device though. This hardware * the 1541 DOS. RAM is only an 8-bit storage device though. This hardware
* limitation prevents a 10-bit GCR byte from being stored in a single * limitation prevents a 10-bit GCR byte from being stored in a single
* memory location. Four 10-bit GCR bytes total 40 bits - a number evenly * memory location. Four 10-bit GCR bytes total 40 bits - a number evenly
* divisible by our overriding 8-bit constraint. Commodore sub- divides the * divisible by our overriding 8-bit constraint. Commodore sub- divides the
* 40 GCR bits into five 8-bit bytes to solve this dilemma. This explains * 40 GCR bits into five 8-bit bytes to solve this dilemma. This explains
* why four 8-bit data bytes are converted to GCR form at a time. The * why four 8-bit data bytes are converted to GCR form at a time. The
* following step by step example demonstrates how this bit manipulation is * following step by step example demonstrates how this bit manipulation is
* performed by the DOS. * performed by the DOS.
* *
* STEP 1. Four 8-bit Data Bytes * STEP 1. Four 8-bit Data Bytes
* $08 $10 $00 $12 * $08 $10 $00 $12
* *
* STEP 2. Hexadecimal to Binary Conversion * STEP 2. Hexadecimal to Binary Conversion
* 1. Binary Equivalents * 1. Binary Equivalents
* $08 $10 $00 $12 * $08 $10 $00 $12
* 00001000 00010000 00000000 00010010 * 00001000 00010000 00000000 00010010
* *
* STEP 3. Binary to GCR Conversion * STEP 3. Binary to GCR Conversion
* 1. Four 8-bit Data Bytes * 1. Four 8-bit Data Bytes
* 00001000 00010000 00000000 00010010 * 00001000 00010000 00000000 00010010
* 2. High and Low Nybbles * 2. High and Low Nybbles
* 0000 1000 0001 0000 0000 0000 0001 0010 * 0000 1000 0001 0000 0000 0000 0001 0010
* 3. High and Low Nybble GCR Equivalents * 3. High and Low Nybble GCR Equivalents
* 01010 01001 01011 01010 01010 01010 01011 10010 * 01010 01001 01011 01010 01010 01010 01011 10010
* 4. Four 10-bit GCR Bytes * 4. Four 10-bit GCR Bytes
* 0101001001 0101101010 0101001010 0101110010 * 0101001001 0101101010 0101001010 0101110010
* *
* STEP 4. 10-bit GCR to 8-bit GCR Conversion * STEP 4. 10-bit GCR to 8-bit GCR Conversion
* 1. Concatenate Four 10-bit GCR Bytes * 1. Concatenate Four 10-bit GCR Bytes
* 0101001001010110101001010010100101110010 * 0101001001010110101001010010100101110010
* 2. Five 8-bit Subdivisions * 2. Five 8-bit Subdivisions
* 01010010 01010110 10100101 00101001 01110010 * 01010010 01010110 10100101 00101001 01110010
* *
* STEP 5. Binary to Hexadecimal Conversion * STEP 5. Binary to Hexadecimal Conversion
* 1. Hexadecimal Equivalents * 1. Hexadecimal Equivalents
* 01010010 01010110 10100101 00101001 01110010 * 01010010 01010110 10100101 00101001 01110010
* $52 $56 $A5 $29 $72 * $52 $56 $A5 $29 $72
* *
* STEP 6. Four 8-bit Data Bytes are Recorded as Five 8-bit GCR Bytes * STEP 6. Four 8-bit Data Bytes are Recorded as Five 8-bit GCR Bytes
* $08 $10 $00 $12 * $08 $10 $00 $12
* *
* are recorded as * are recorded as
* $52 $56 $A5 $29 $72 * $52 $56 $A5 $29 $72
*/ */
std::vector<bool> output(10, false); std::vector<bool> output(10, false);
uint8_t hi = 0; uint8_t hi = 0;
@@ -106,109 +163,124 @@ static std::vector<bool> encode_data(uint8_t input)
uint8_t lo_GCR = 0; uint8_t lo_GCR = 0;
uint8_t hi_GCR = 0; uint8_t hi_GCR = 0;
// Convert the byte in high and low nibble //Convert the byte in high and low nibble
lo = input >> 4; // get the lo nibble shift the bits 4 to the right lo = input >> 4; //get the lo nibble shift the bits 4 to the right
hi = input & 15; // get the hi nibble bij masking the lo bits (00001111) hi = input & 15; //get the hi nibble bij masking the lo bits (00001111)
lo_GCR = encode_data_gcr(lo); // example value: 0000 GCR = 01010
hi_GCR = encode_data_gcr(hi); // example value: 1000 GCR = 01001 lo_GCR = encode_data_gcr(lo); //example value: 0000 GCR = 01010
// output = [0,1,2,3,4,5,6,7,8,9] hi_GCR = encode_data_gcr(hi); //example value: 1000 GCR = 01001
// value = [0,1,0,1,0,0,1,0,0,1] //output = [0,1,2,3,4,5,6,7,8,9]
// 01010 01001 //value = [0,1,0,1,0,0,1,0,0,1]
// 01010 01001
int b = 4; int b = 4;
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
if (i < 5) // 01234 if (i < 5) //01234
{ // i = 0 op { //i = 0 op
output[4 - i] = (lo_GCR & 1); // 01010 output[4-i] = (lo_GCR & 1); //01010
// 01010 -> & 00001 -> 00000 output[4] = 0 //01010 -> & 00001 -> 00000 output[4] = 0
// 00101 -> & 00001 -> 00001 output[3] = 1 //00101 -> & 00001 -> 00001 output[3] = 1
// 00010 -> & 00001 -> 00000 output[2] = 0 //00010 -> & 00001 -> 00000 output[2] = 0
// 00001 -> & 00001 -> 00001 output[1] = 1 //00001 -> & 00001 -> 00001 output[1] = 1
// 00000 -> & 00001 -> 00000 output[0] = 0 //00000 -> & 00001 -> 00000 output[0] = 0
lo_GCR >>= 1; lo_GCR >>= 1;
} } else
else
{ {
output[i + b] = (hi_GCR & 1); // 01001 output[i+b] = (hi_GCR & 1); //01001
// 01001 -> & 00001 -> 00001 output[9] = 1 //01001 -> & 00001 -> 00001 output[9] = 1
// 00100 -> & 00001 -> 00000 output[8] = 0 //00100 -> & 00001 -> 00000 output[8] = 0
// 00010 -> & 00001 -> 00000 output[7] = 0 //00010 -> & 00001 -> 00000 output[7] = 0
// 00001 -> & 00001 -> 00001 output[6] = 1 //00001 -> & 00001 -> 00001 output[6] = 1
// 00000 -> & 00001 -> 00000 output[5] = 0 //00000 -> & 00001 -> 00000 output[5] = 0
hi_GCR >>= 1; hi_GCR >>= 1;
b = b - 2; b = b-2;
} }
} }
return output; return output;
} }
class Commodore64Encoder : public Encoder class Commodore64Encoder : public AbstractEncoder
{ {
public: public:
Commodore64Encoder(const EncoderProto& config): Commodore64Encoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.c64()) _config(config.c64())
{ {}
}
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
/* The format ID Character # 1 and # 2 are in the .d64 image only
* present in track 18 sector zero which contains the BAM info in byte
* 162 and 163. it is written in every header of every sector and track.
* headers are not stored in a d64 disk image so we have to get it from
* track 18 which contains the BAM.
*/
const auto& sectorData = image.get( if (physicalSide == 0)
C64_BAM_TRACK, 0, 0); // Read de BAM to get the DISK ID bytes {
int logicalTrack = physicalTrack / 2;
unsigned numSectors = sectorsForTrack(logicalTrack);
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
/* The format ID Character # 1 and # 2 are in the .d64 image only present
* in track 18 sector zero which contains the BAM info in byte 162 and 163.
* it is written in every header of every sector and track. headers are not
* stored in a d64 disk image so we have to get it from track 18 which
* contains the BAM.
*/
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
const auto& sectorData = image.get(C64_BAM_TRACK*2, 0, 0); //Read de BAM to get the DISK ID bytes
if (sectorData) if (sectorData)
{ {
ByteReader br(sectorData->data); ByteReader br(sectorData->data);
br.seek(162); // goto position of the first Disk ID Byte br.seek(162); //goto position of the first Disk ID Byte
_formatByte1 = br.read_8(); _formatByte1 = br.read_8();
_formatByte2 = br.read_8(); _formatByte2 = br.read_8();
} }
else else
_formatByte1 = _formatByte2 = 0; _formatByte1 = _formatByte2 = 0;
int logicalTrack = physicalTrack / 2;
double clockRateUs = clockRateUsForTrack(logicalTrack) * _config.clock_compensation_factor();
double clockRateUs = clockPeriodForC64Track(trackInfo->logicalTrack);
int bitsPerRevolution = 200000.0 / clockRateUs; int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution); std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0; unsigned cursor = 0;
fillBitmapTo(bits, fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
cursor,
_config.post_index_gap_us() / clockRateUs,
{true, false});
lastBit = false; lastBit = false;
for (const auto& sector : sectors) for (const auto& sector : sectors)
writeSector(bits, cursor, sector); writeSector(bits, cursor, sector);
if (cursor >= bits.size()) if (cursor >= bits.size())
error("track data overrun by {} bits", cursor - bits.size()); Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false}); fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits( fluxmap->appendBits(bits, clockRateUs*1e3);
bits, calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6));
return fluxmap; return fluxmap;
} }
private: private:
void writeSector(std::vector<bool>& bits, void writeSector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector) const
unsigned& cursor,
std::shared_ptr<const Sector> sector) const
{ {
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
* 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR) * 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
* 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) * 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
* 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read) * 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
@@ -216,26 +288,23 @@ private:
* 5. Data block 55...4A (325 GCR bytes) * 5. Data block 55...4A (325 GCR bytes)
* 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read) * 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
* 1. Header sync (SYNC for the next sector) * 1. Header sync (SYNC for the next sector)
*/ */
if ((sector->status == Sector::OK) or if ((sector->status == Sector::OK) or (sector->status == Sector::BAD_CHECKSUM))
(sector->status == Sector::BAD_CHECKSUM))
{ {
// There is data to encode to disk. // There is data to encode to disk.
if ((sector->data.size() != C64_SECTOR_LENGTH)) if ((sector->data.size() != C64_SECTOR_LENGTH))
error("unsupported sector size {} --- you must pick 256", Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector->data.size());
sector->data.size());
// 1. Write header Sync (not GCR) // 1. Write header Sync (not GCR)
for (int i = 0; i < 6; i++) for (int i=0; i<6; i++)
write_bits( write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
bits, cursor, C64_HEADER_DATA_SYNC, 1 * 8); /* sync */
// 2. Write Header info 10 GCR bytes // 2. Write Header info 10 GCR bytes
/* /*
* The 10 byte header info (#2) is GCR encoded and must be decoded * The 10 byte header info (#2) is GCR encoded and must be decoded to
* to it's normal 8 bytes to be understood. Once decoded, its * it's normal 8 bytes to be understood. Once decoded, its breakdown is
* breakdown is as follows: * as follows:
* *
* Byte $00 - header block ID ($08) * Byte $00 - header block ID ($08)
* 01 - header block checksum 16 (EOR of $02-$05) * 01 - header block checksum 16 (EOR of $02-$05)
* 02 - Sector * 02 - Sector
@@ -244,14 +313,11 @@ private:
* 05 - Format ID byte #1 * 05 - Format ID byte #1
* 06-07 - $0F ("off" bytes) * 06-07 - $0F ("off" bytes)
*/ */
uint8_t encodedTrack = uint8_t encodedTrack = ((sector->logicalTrack) + 1); // C64 track numbering starts with 1. Fluxengine with 0.
((sector->logicalTrack) +
1); // C64 track numbering starts with 1. Fluxengine with 0.
uint8_t encodedSector = sector->logicalSector; uint8_t encodedSector = sector->logicalSector;
// uint8_t formatByte1 = C64_FORMAT_ID_BYTE1; // uint8_t formatByte1 = C64_FORMAT_ID_BYTE1;
// uint8_t formatByte2 = C64_FORMAT_ID_BYTE2; // uint8_t formatByte2 = C64_FORMAT_ID_BYTE2;
uint8_t headerChecksum = uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ _formatByte1 ^ _formatByte2);
(encodedTrack ^ encodedSector ^ _formatByte1 ^ _formatByte2);
write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID)); write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID));
write_bits(bits, cursor, encode_data(headerChecksum)); write_bits(bits, cursor, encode_data(headerChecksum));
write_bits(bits, cursor, encode_data(encodedSector)); write_bits(bits, cursor, encode_data(encodedSector));
@@ -262,26 +328,22 @@ private:
write_bits(bits, cursor, encode_data(C64_PADDING)); write_bits(bits, cursor, encode_data(C64_PADDING));
// 3. Write header GAP not GCR // 3. Write header GAP not GCR
for (int i = 0; i < 9; i++) for (int i=0; i<9; i++)
write_bits( write_bits(bits, cursor, C64_HEADER_GAP, 1*8); /* header gap */
bits, cursor, C64_HEADER_GAP, 1 * 8); /* header gap */
// 4. Write Data sync not GCR // 4. Write Data sync not GCR
for (int i = 0; i < 6; i++) for (int i=0; i<6; i++)
write_bits( write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
bits, cursor, C64_HEADER_DATA_SYNC, 1 * 8); /* sync */
// 5. Write data block 325 GCR bytes // 5. Write data block 325 GCR bytes
/* /*
* The 325 byte data block (#5) is GCR encoded and must be decoded * The 325 byte data block (#5) is GCR encoded and must be decoded to its
* to its normal 260 bytes to be understood. The data block is made * normal 260 bytes to be understood. The data block is made up of the following:
* up of the following: *
*
* Byte $00 - data block ID ($07) * Byte $00 - data block ID ($07)
* 01-100 - 256 bytes data * 01-100 - 256 bytes data
* 101 - data block checksum (EOR of $01-100) * 101 - data block checksum (EOR of $01-100)
* 102-103 - $00 ("off" bytes, to make the sector size a * 102-103 - $00 ("off" bytes, to make the sector size a multiple of 5)
* multiple of 5)
*/ */
write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID)); write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID));
@@ -291,28 +353,29 @@ private:
for (i = 0; i < C64_SECTOR_LENGTH; i++) for (i = 0; i < C64_SECTOR_LENGTH; i++)
{ {
uint8_t val = br.read_8(); uint8_t val = br.read_8();
write_bits(bits, cursor, encode_data(val)); write_bits(bits, cursor, encode_data(val));
} }
write_bits(bits, cursor, encode_data(dataChecksum)); write_bits(bits, cursor, encode_data(dataChecksum));
write_bits(bits, cursor, encode_data(C64_PADDING)); write_bits(bits, cursor, encode_data(C64_PADDING));
write_bits(bits, cursor, encode_data(C64_PADDING)); write_bits(bits, cursor, encode_data(C64_PADDING));
// 6. Write inter-sector gap 9 - 12 bytes nor gcr //6. Write inter-sector gap 9 - 12 bytes nor gcr
for (int i = 0; i < 9; i++) for (int i=0; i<9; i++)
write_bits( write_bits(bits, cursor, C64_INTER_SECTOR_GAP, 1*8); /* sync */
bits, cursor, C64_INTER_SECTOR_GAP, 1 * 8); /* sync */
} }
} }
private: private:
const Commodore64EncoderProto& _config; const Commodore64EncoderProto& _config;
uint8_t _formatByte1; uint8_t _formatByte1;
uint8_t _formatByte2; uint8_t _formatByte2;
}; };
std::unique_ptr<Encoder> createCommodore64Encoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createCommodore64Encoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new Commodore64Encoder(config)); return std::unique_ptr<AbstractEncoder>(new Commodore64Encoder(config));
} }
// vim: sw=4 ts=4 et // vim: sw=4 ts=4 et

View File

@@ -1,31 +1,28 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "f85.h" #include "f85.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_RECORD_PATTERN(24, F85_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(24, F85_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(24, F85_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(24, F85_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr) static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
@@ -40,11 +37,11 @@ static Bytes decode(const std::vector<bool>& bits)
while (ii != bits.end()) while (ii != bits.end())
{ {
uint8_t inputfifo = 0; uint8_t inputfifo = 0;
for (size_t i = 0; i < 5; i++) for (size_t i=0; i<5; i++)
{ {
if (ii == bits.end()) if (ii == bits.end())
break; break;
inputfifo = (inputfifo << 1) | *ii++; inputfifo = (inputfifo<<1) | *ii++;
} }
bitw.push(decode_data_gcr(inputfifo), 4); bitw.push(decode_data_gcr(inputfifo), 4);
@@ -54,58 +51,63 @@ static Bytes decode(const std::vector<bool>& bits)
return output; return output;
} }
class DurangoF85Decoder : public Decoder class DurangoF85Decoder : public AbstractDecoder
{ {
public: public:
DurangoF85Decoder(const DecoderProto& config): Decoder(config) {} DurangoF85Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
/* Skip sync bits and ID byte. */ /* Skip sync bits and ID byte. */
if (readRaw24() != F85_SECTOR_RECORD) readRawBits(24);
return;
/* Read header. */ /* Read header. */
const auto& bytes = decode(readRawBits(6 * 10)); const auto& bytes = decode(readRawBits(6*10));
_sector->logicalSector = bytes[2]; _sector->logicalSector = bytes[2];
_sector->logicalSide = 0; _sector->logicalSide = 0;
_sector->logicalTrack = bytes[0]; _sector->logicalTrack = bytes[0];
uint16_t wantChecksum = bytes.reader().seek(4).read_be16(); uint16_t wantChecksum = bytes.reader().seek(4).read_be16();
uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4)); uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4));
if (wantChecksum == gotChecksum) if (wantChecksum == gotChecksum)
_sector->status = _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
Sector::DATA_MISSING; /* unintuitive but correct */ }
}
void decodeDataRecord() override void decodeDataRecord()
{ {
/* Skip sync bits ID byte. */ /* Skip sync bits ID byte. */
if (readRaw24() != F85_DATA_RECORD) readRawBits(24);
return;
const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH + 3) * 10)) const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10))
.slice(0, F85_SECTOR_LENGTH + 3); .slice(0, F85_SECTOR_LENGTH+3);
ByteReader br(bytes); ByteReader br(bytes);
_sector->data = br.read(F85_SECTOR_LENGTH); _sector->data = br.read(F85_SECTOR_LENGTH);
uint16_t wantChecksum = br.read_be16(); uint16_t wantChecksum = br.read_be16();
uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data); uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data);
_sector->status = _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; }
}
}; };
std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createDurangoF85Decoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new DurangoF85Decoder(config)); return std::unique_ptr<AbstractDecoder>(new DurangoF85Decoder(config));
} }

View File

@@ -2,10 +2,9 @@
#define F85_H #define F85_H
#define F85_SECTOR_RECORD 0xffffce /* 1111 1111 1111 1111 1100 1110 */ #define F85_SECTOR_RECORD 0xffffce /* 1111 1111 1111 1111 1100 1110 */
#define F85_DATA_RECORD 0xffffcb /* 1111 1111 1111 1111 1100 1101 */ #define F85_DATA_RECORD 0xffffcb /* 1111 1111 1111 1111 1100 1101 */
#define F85_SECTOR_LENGTH 512 #define F85_SECTOR_LENGTH 512
extern std::unique_ptr<Decoder> createDurangoF85Decoder( extern std::unique_ptr<AbstractDecoder> createDurangoF85Decoder(const DecoderProto& config);
const DecoderProto& config);
#endif #endif

View File

@@ -1,24 +1,23 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "fb100.h" #include "fb100.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "lib/decoders/rawbits.h" #include "decoders/rawbits.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_ID_PATTERN(16, 0xabaa); const FluxPattern SECTOR_ID_PATTERN(16, 0xabaa);
/* /*
* Reverse engineered from a dump of the floppy drive's ROM. I have no idea how * Reverse engineered from a dump of the floppy drive's ROM. I have no idea how
* it works. * it works.
* *
* LF8BA: * LF8BA:
* clra * clra
* staa X00B0 * staa X00B0
@@ -98,46 +97,52 @@ static uint16_t checksum(const Bytes& bytes)
return (crchi << 8) | crclo; return (crchi << 8) | crclo;
} }
class Fb100Decoder : public Decoder class Fb100Decoder : public AbstractDecoder
{ {
public: public:
Fb100Decoder(const DecoderProto& config): Decoder(config) {} Fb100Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(SECTOR_ID_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
if (matcher == &SECTOR_ID_PATTERN)
return RecordType::SECTOR_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
auto rawbits = readRawBits(FB100_RECORD_SIZE * 16); auto rawbits = readRawBits(FB100_RECORD_SIZE*16);
const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE); const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE);
ByteReader br(bytes); ByteReader br(bytes);
br.seek(1); br.seek(1);
const Bytes id = br.read(FB100_ID_SIZE); const Bytes id = br.read(FB100_ID_SIZE);
uint16_t wantIdCrc = br.read_be16(); uint16_t wantIdCrc = br.read_be16();
uint16_t gotIdCrc = checksum(id); uint16_t gotIdCrc = checksum(id);
const Bytes payload = br.read(FB100_PAYLOAD_SIZE); const Bytes payload = br.read(FB100_PAYLOAD_SIZE);
uint16_t wantPayloadCrc = br.read_be16(); uint16_t wantPayloadCrc = br.read_be16();
uint16_t gotPayloadCrc = checksum(payload); uint16_t gotPayloadCrc = checksum(payload);
if (wantIdCrc != gotIdCrc) if (wantIdCrc != gotIdCrc)
return; return;
uint8_t abssector = id[2]; uint8_t abssector = id[2];
_sector->logicalTrack = abssector >> 1; _sector->logicalTrack = abssector >> 1;
_sector->logicalSide = 0; _sector->logicalSide = 0;
_sector->logicalSector = abssector & 1; _sector->logicalSector = abssector & 1;
_sector->data.writer().append(id.slice(5, 12)).append(payload); _sector->data.writer().append(id.slice(5, 12)).append(payload);
_sector->status = (wantPayloadCrc == gotPayloadCrc) _sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
? Sector::OK }
: Sector::BAD_CHECKSUM;
}
}; };
std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createFb100Decoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new Fb100Decoder(config)); return std::unique_ptr<AbstractDecoder>(new Fb100Decoder(config));
} }

View File

@@ -5,6 +5,7 @@
#define FB100_ID_SIZE 17 #define FB100_ID_SIZE 17
#define FB100_PAYLOAD_SIZE 0x500 #define FB100_PAYLOAD_SIZE 0x500
extern std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config); extern std::unique_ptr<AbstractDecoder> createFb100Decoder(const DecoderProto& config);
#endif #endif

View File

@@ -1,32 +1,30 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "ibm.h" #include "ibm.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "sector.h"
#include "lib/data/sector.h"
#include "arch/ibm/ibm.pb.h" #include "arch/ibm/ibm.pb.h"
#include "lib/config/proto.h" #include "proto.h"
#include "lib/data/layout.h"
#include <string.h> #include <string.h>
static_assert(std::is_trivially_copyable<IbmIdam>::value, static_assert(std::is_trivially_copyable<IbmIdam>::value,
"IbmIdam is not trivially copyable"); "IbmIdam is not trivially copyable");
/* /*
* The markers at the beginning of records are special, and have * The markers at the beginning of records are special, and have
* missing clock pulses, allowing them to be found by the logic. * missing clock pulses, allowing them to be found by the logic.
* *
* IAM record: * IAM record:
* flux: XXXX-XXX-XXXX-X- = 0xf77a * flux: XXXX-XXX-XXXX-X- = 0xf77a
* clock: X X - X - X X X = 0xd7 * clock: X X - X - X X X = 0xd7
* data: X X X X X X - - = 0xfc * data: X X X X X X - - = 0xfc
* *
* (We just ignore this one --- it's useless and optional.) * (We just ignore this one --- it's useless and optional.)
*/ */
/* /*
* IDAM record: * IDAM record:
* flux: XXXX-X-X-XXXXXX- = 0xf57e * flux: XXXX-X-X-XXXXXX- = 0xf57e
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
@@ -34,7 +32,7 @@ static_assert(std::is_trivially_copyable<IbmIdam>::value,
*/ */
const FluxPattern FM_IDAM_PATTERN(16, 0xf57e); const FluxPattern FM_IDAM_PATTERN(16, 0xf57e);
/* /*
* DAM1 record: * DAM1 record:
* flux: XXXX-X-X-XX-X-X- = 0xf56a * flux: XXXX-X-X-XX-X-X- = 0xf56a
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
@@ -42,7 +40,7 @@ const FluxPattern FM_IDAM_PATTERN(16, 0xf57e);
*/ */
const FluxPattern FM_DAM1_PATTERN(16, 0xf56a); const FluxPattern FM_DAM1_PATTERN(16, 0xf56a);
/* /*
* DAM2 record: * DAM2 record:
* flux: XXXX-X-X-XX-XXXX = 0xf56f * flux: XXXX-X-X-XX-XXXX = 0xf56f
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
@@ -50,7 +48,7 @@ const FluxPattern FM_DAM1_PATTERN(16, 0xf56a);
*/ */
const FluxPattern FM_DAM2_PATTERN(16, 0xf56f); const FluxPattern FM_DAM2_PATTERN(16, 0xf56f);
/* /*
* TRS80DAM1 record: * TRS80DAM1 record:
* flux: XXXX-X-X-XX-X-XX = 0xf56b * flux: XXXX-X-X-XX-X-XX = 0xf56b
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
@@ -58,7 +56,7 @@ const FluxPattern FM_DAM2_PATTERN(16, 0xf56f);
*/ */
const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b); const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b);
/* /*
* TRS80DAM2 record: * TRS80DAM2 record:
* flux: XXXX-X-X-XX-XXX- = 0xf56e * flux: XXXX-X-X-XX-XXX- = 0xf56e
* clock: X X - - - X X X = 0xc7 * clock: X X - - - X X X = 0xc7
@@ -74,178 +72,122 @@ const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e);
* ^^^^^ * ^^^^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal * When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data. * encoding (you can't do 10 00). So this can't be spoofed by user data.
* *
* shifted: 10 00 10 01 00 01 00 1 * shifted: 10 00 10 01 00 01 00 1
* *
* It's repeated three times. * It's repeated three times.
*/ */
const FluxPattern MFM_PATTERN(48, 0x448944894489LL); const FluxPattern MFM_PATTERN(48, 0x448944894489LL);
const FluxMatchers ANY_RECORD_PATTERN({ const FluxMatchers ANY_RECORD_PATTERN(
&MFM_PATTERN, {
&FM_IDAM_PATTERN, &MFM_PATTERN,
&FM_DAM1_PATTERN, &FM_IDAM_PATTERN,
&FM_DAM2_PATTERN, &FM_DAM1_PATTERN,
&FM_TRS80DAM1_PATTERN, &FM_DAM2_PATTERN,
&FM_TRS80DAM2_PATTERN, &FM_TRS80DAM1_PATTERN,
}); &FM_TRS80DAM2_PATTERN,
}
);
class IbmDecoder : public Decoder class IbmDecoder : public AbstractDecoder
{ {
public: public:
IbmDecoder(const DecoderProto& config): IbmDecoder(const DecoderProto& config):
Decoder(config), AbstractDecoder(config),
_config(config.ibm()) _config(config.ibm())
{ {}
}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord() override
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
/* If this is the MFM prefix byte, the the decoder is going to expect three
* extra bytes on the front of the header. */
_currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0;
Fluxmap::Position here = tell();
resetFluxDecoder();
if (_currentHeaderLength > 0)
readRawBits(_currentHeaderLength*16);
auto idbits = readRawBits(16);
const Bytes idbytes = decodeFmMfm(idbits);
uint8_t id = idbytes.slice(0, 1)[0];
seek(here);
switch (id)
{
case IBM_IDAM:
return RecordType::SECTOR_RECORD;
case IBM_DAM1:
case IBM_DAM2:
case IBM_TRS80DAM1:
case IBM_TRS80DAM2:
return RecordType::DATA_RECORD;
}
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord() override
{ {
/* This is really annoying because the IBM record scheme has a unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN;
* variable-sized header _and_ the checksum covers this header too. So auto bits = readRawBits(recordSize*16);
* we have to read and decode a byte at a time until we know where the auto bytes = decodeFmMfm(bits).slice(0, recordSize);
* record itself starts, saving the bytes for the checksumming later.
*/
Bytes bytes; ByteReader br(bytes);
ByteWriter bw(bytes); br.seek(_currentHeaderLength);
br.read_8(); /* skip ID byte */
_sector->logicalTrack = br.read_8();
_sector->logicalSide = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
uint16_t wantCrc = br.read_be16();
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5));
if (wantCrc == gotCrc)
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
auto readByte = [&]() if (_config.swap_sides())
{ _sector->logicalSide ^= 1;
auto bits = readRawBits(16); if (_config.ignore_side_byte())
auto bytes = decodeFmMfm(bits).slice(0, 1); _sector->logicalSide = _sector->physicalHead;
uint8_t byte = bytes[0]; if (_config.ignore_track_byte())
bw.write_8(byte); _sector->logicalTrack = _sector->physicalCylinder;
return byte; }
};
uint8_t id = readByte();
if (id == 0xa1)
{
readByte();
readByte();
id = readByte();
}
if (id != IBM_IDAM)
return;
ByteReader br(bytes);
br.seek(bw.pos);
auto bits = readRawBits(IBM_IDAM_LEN * 16);
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(
trackdata, _sector->physicalTrack, _sector->physicalSide);
_sector->logicalTrack = br.read_8();
_sector->logicalSide = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
if (wantCrc == gotCrc)
_sector->status =
Sector::DATA_MISSING; /* correct but unintuitive */
if (trackdata.ignore_side_byte())
_sector->logicalSide =
Layout::remapSidePhysicalToLogical(_sector->physicalSide);
_sector->logicalSide ^= trackdata.invert_side_byte();
if (trackdata.ignore_track_byte())
_sector->logicalTrack = _sector->physicalTrack;
for (int sector : trackdata.ignore_sector())
if (_sector->logicalSector == sector)
{
_sector->status = Sector::MISSING;
break;
}
}
void decodeDataRecord() override void decodeDataRecord() override
{ {
/* This is the same deal as the sector record. */ unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3;
auto bits = readRawBits(recordLength*16);
auto bytes = decodeFmMfm(bits).slice(0, recordLength);
Bytes bytes; ByteReader br(bytes);
ByteWriter bw(bytes); br.seek(_currentHeaderLength);
br.read_8(); /* skip ID byte */
auto readByte = [&]() _sector->data = br.read(_currentSectorSize);
{ uint16_t wantCrc = br.read_be16();
auto bits = readRawBits(16); uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, recordLength-2));
auto bytes = decodeFmMfm(bits).slice(0, 1); _sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
uint8_t byte = bytes[0]; }
bw.write_8(byte);
return byte;
};
uint8_t id = readByte(); std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
if (id == 0xa1) {
{ std::set<unsigned> s;
readByte(); for (int sectorId : _config.sectors().sector())
readByte(); s.insert(sectorId);
id = readByte(); return s;
} }
if ((id != IBM_DAM1) && (id != IBM_DAM2) && (id != IBM_TRS80DAM1) &&
(id != IBM_TRS80DAM2))
return;
ByteReader br(bytes);
br.seek(bw.pos);
auto bits = readRawBits((_currentSectorSize + 2) * 16);
bw += decodeFmMfm(bits).slice(0, _currentSectorSize + 2);
_sector->data = br.read(_currentSectorSize);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
_sector->status =
(wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
auto layout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
if (_currentSectorSize != layout->sectorSize)
std::cerr << fmt::format(
"Warning: configured sector size for t{}.h{}.s{} is {} bytes "
"but that seen on disk is {} bytes\n",
_sector->logicalTrack,
_sector->logicalSide,
_sector->logicalSector,
layout->sectorSize,
_currentSectorSize);
}
private: private:
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, const IbmDecoderProto& _config;
unsigned track,
unsigned head) const
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_track() && (f.track() != track))
continue;
if (f.has_head() && (f.head() != head))
continue;
trackdata.MergeFrom(f);
}
}
private:
const IbmDecoderProto& _config;
unsigned _currentSectorSize; unsigned _currentSectorSize;
unsigned _currentHeaderLength; unsigned _currentHeaderLength;
}; };
std::unique_ptr<Decoder> createIbmDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createIbmDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new IbmDecoder(config)); return std::unique_ptr<AbstractDecoder>(new IbmDecoder(config));
} }

View File

@@ -1,15 +1,13 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/config/config.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "ibm.h" #include "ibm.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "arch/ibm/ibm.pb.h" #include "arch/ibm/ibm.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "lib/config/proto.h"
#include "lib/data/layout.h"
#include <ctype.h> #include <ctype.h>
/* IAM record separator: /* IAM record separator:
@@ -41,9 +39,9 @@
* ^^^^^ * ^^^^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal * When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data. * encoding (you can't do 10 00). So this can't be spoofed by user data.
* *
* shifted: 10 00 10 01 00 01 00 1 * shifted: 10 00 10 01 00 01 00 1
* *
* It's repeated three times. * It's repeated three times.
*/ */
#define MFM_RECORD_SEPARATOR 0x4489 #define MFM_RECORD_SEPARATOR 0x4489
@@ -61,222 +59,228 @@
static uint8_t decodeUint16(uint16_t raw) static uint8_t decodeUint16(uint16_t raw)
{ {
Bytes b; Bytes b;
ByteWriter bw(b); ByteWriter bw(b);
bw.write_be16(raw); bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0]; return decodeFmMfm(b.toBits())[0];
} }
class IbmEncoder : public Encoder class IbmEncoder : public AbstractEncoder
{ {
public: public:
IbmEncoder(const EncoderProto& config): IbmEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.ibm()) _config(config.ibm())
{ {}
}
private: private:
void writeRawBits(uint32_t data, int width) void writeRawBits(uint32_t data, int width)
{ {
_cursor += width; _cursor += width;
_lastBit = data & 1; _lastBit = data & 1;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = _cursor - i - 1; unsigned pos = _cursor - i - 1;
if (pos < _bits.size()) if (pos < _bits.size())
_bits[pos] = data & 1; _bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
void getEncoderTrackData(IbmEncoderProto::TrackdataProto& trackdata, void getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
unsigned track, {
unsigned head) trackdata.Clear();
{ for (const auto& f : _config.trackdata())
trackdata.Clear(); {
for (const auto& f : _config.trackdata()) if (f.has_cylinder() && (f.cylinder() != cylinder))
{ continue;
if (f.has_track() && (f.track() != track)) if (f.has_head() && (f.head() != head))
continue; continue;
if (f.has_head() && (f.head() != head))
continue;
trackdata.MergeFrom(f); trackdata.MergeFrom(f);
} }
} }
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{ IbmEncoderProto::TrackdataProto trackdata;
IbmEncoderProto::TrackdataProto trackdata; getTrackFormat(trackdata, physicalTrack, physicalSide);
getEncoderTrackData(
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
auto trackLayout = Layout::getLayoutOfTrack( int logicalSide = physicalSide ^ trackdata.swap_sides();
trackInfo->logicalTrack, trackInfo->logicalSide); for (int sectorId : trackdata.sectors().sector())
auto writeBytes = [&](const Bytes& bytes)
{ {
if (trackdata.use_fm()) const auto& sector = image.get(physicalTrack, logicalSide, sectorId);
encodeFm(_bits, _cursor, bytes); if (sector)
else sectors.push_back(sector);
encodeMfm(_bits, _cursor, bytes, _lastBit);
};
auto writeFillerRawBytes = [&](int count, uint16_t byte)
{
for (int i = 0; i < count; i++)
writeRawBits(byte, 16);
};
auto writeFillerBytes = [&](int count, uint8_t byte)
{
Bytes b{byte};
for (int i = 0; i < count; i++)
writeBytes(b);
};
double clockRateUs = trackdata.target_clock_period_us();
if (!trackdata.use_fm())
clockRateUs /= 2.0;
int bitsPerRevolution =
(trackdata.target_rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
uint8_t sectorSize = 0;
{
int s = trackLayout->sectorSize >> 7;
while (s > 1)
{
s >>= 1;
sectorSize += 1;
}
} }
uint16_t gapFill = trackdata.gap_fill_byte(); return sectors;
}
writeFillerRawBytes(trackdata.gap0(), gapFill); std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
if (trackdata.emit_iam()) const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{ {
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); IbmEncoderProto::TrackdataProto trackdata;
if (!trackdata.use_fm()) getTrackFormat(trackdata, physicalTrack, physicalSide);
{
for (int i = 0; i < 3; i++)
writeRawBits(MFM_IAM_SEPARATOR, 16);
}
writeRawBits(
trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
writeFillerRawBytes(trackdata.gap1(), gapFill);
}
bool first = true; auto writeBytes = [&](const Bytes& bytes)
for (const auto& sectorData : sectors) {
{ if (trackdata.use_fm())
if (!first) encodeFm(_bits, _cursor, bytes);
writeFillerRawBytes(trackdata.gap3(), gapFill); else
first = false; encodeMfm(_bits, _cursor, bytes, _lastBit);
};
/* Writing the sector and data records are fantastically annoying. auto writeFillerBytes = [&](int count, uint8_t byte)
* The CRC is calculated from the *very start* of the record, and {
* include the malformed marker bytes. Our encoder doesn't know Bytes bytes = { byte };
* about this, of course, with the result that we have to construct for (int i=0; i<count; i++)
* the unencoded header, calculate the checksum, and then use the writeBytes(bytes);
* same logic to emit the bytes which require special encoding };
* before encoding the rest of the header normally. */
{ double clockRateUs = 1e3 / trackdata.clock_rate_khz();
Bytes header; if (!trackdata.use_fm())
ByteWriter bw(header); clockRateUs /= 2.0;
int bitsPerRevolution = (trackdata.track_length_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
if (!trackdata.use_fm()) uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
{
for (int i = 0; i < 3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(idamUnencoded);
bw.write_8(sectorData->logicalTrack);
bw.write_8(
sectorData->logicalSide ^ trackdata.invert_side_byte());
bw.write_8(sectorData->logicalSector);
bw.write_8(sectorSize);
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
int conventionalHeaderStart = 0; uint8_t sectorSize = 0;
if (!trackdata.use_fm()) {
{ int s = trackdata.sector_size() >> 7;
for (int i = 0; i < 3; i++) while (s > 1)
writeRawBits(MFM_RECORD_SEPARATOR, 16); {
conventionalHeaderStart += 3; s >>= 1;
} sectorSize += 1;
writeRawBits(trackdata.idam_byte(), 16); }
conventionalHeaderStart += 1; }
writeBytes(header.slice(conventionalHeaderStart)); uint8_t gapFill = trackdata.use_fm() ? 0x00 : 0x4e;
}
writeFillerRawBytes(trackdata.gap2(), gapFill); writeFillerBytes(trackdata.gap0(), gapFill);
if (trackdata.emit_iam())
{
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
writeRawBits(MFM_IAM_SEPARATOR, 16);
}
writeRawBits(trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
writeFillerBytes(trackdata.gap1(), gapFill);
}
{ int logicalSide = physicalSide ^ trackdata.swap_sides();
Bytes data; bool first = true;
ByteWriter bw(data); for (int sectorId : trackdata.sectors().sector())
{
if (!first)
writeFillerBytes(trackdata.gap3(), gapFill);
first = false;
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); const auto& sectorData = image.get(physicalTrack, logicalSide, sectorId);
if (!trackdata.use_fm()) if (!sectorData)
{ continue;
for (int i = 0; i < 3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(damUnencoded);
Bytes truncatedData = /* Writing the sector and data records are fantastically annoying.
sectorData->data.slice(0, trackLayout->sectorSize); * The CRC is calculated from the *very start* of the record, and
bw += truncatedData; * include the malformed marker bytes. Our encoder doesn't know
uint16_t crc = crc16(CCITT_POLY, data); * about this, of course, with the result that we have to construct
bw.write_be16(crc); * the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
int conventionalHeaderStart = 0; {
if (!trackdata.use_fm()) Bytes header;
{ ByteWriter bw(header);
for (int i = 0; i < 3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
}
writeRawBits(trackdata.dam_byte(), 16);
conventionalHeaderStart += 1;
writeBytes(data.slice(conventionalHeaderStart)); writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
} if (!trackdata.use_fm())
} {
for (int i=0; i<3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(idamUnencoded);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorData->logicalSide);
bw.write_8(sectorData->logicalSector);
bw.write_8(sectorSize);
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
if (_cursor >= _bits.size()) int conventionalHeaderStart = 0;
error("track data overrun"); if (!trackdata.use_fm())
while (_cursor < _bits.size()) {
writeFillerRawBytes(1, gapFill); for (int i=0; i<3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); }
fluxmap->appendBits(_bits, writeRawBits(trackdata.idam_byte(), 16);
calculatePhysicalClockPeriod(clockRateUs * 1e3, conventionalHeaderStart += 1;
trackdata.target_rotational_period_ms() * 1e6));
return fluxmap; writeBytes(header.slice(conventionalHeaderStart));
} }
writeFillerBytes(trackdata.gap2(), gapFill);
{
Bytes data;
ByteWriter bw(data);
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(damUnencoded);
Bytes truncatedData = sectorData->data.slice(0, trackdata.sector_size());
bw += truncatedData;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
int conventionalHeaderStart = 0;
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
}
writeRawBits(trackdata.dam_byte(), 16);
conventionalHeaderStart += 1;
writeBytes(data.slice(conventionalHeaderStart));
}
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeFillerBytes(1, gapFill);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);
return fluxmap;
}
private: private:
const IbmEncoderProto& _config; const IbmEncoderProto& _config;
std::vector<bool> _bits; std::vector<bool> _bits;
unsigned _cursor; unsigned _cursor;
bool _lastBit; bool _lastBit;
}; };
std::unique_ptr<Encoder> createIbmEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new IbmEncoder(config)); return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
} }

View File

@@ -3,35 +3,35 @@
/* IBM format (i.e. ordinary PC floppies). */ /* IBM format (i.e. ordinary PC floppies). */
#define IBM_MFM_SYNC 0xA1 /* sync byte for MFM */ #define IBM_MFM_SYNC 0xA1 /* sync byte for MFM */
#define IBM_IAM 0xFC /* start-of-track record */ #define IBM_IAM 0xFC /* start-of-track record */
#define IBM_IAM_LEN 1 /* plus prologue */ #define IBM_IAM_LEN 1 /* plus prologue */
#define IBM_IDAM 0xFE /* sector header */ #define IBM_IDAM 0xFE /* sector header */
#define IBM_IDAM_LEN 7 /* plus prologue */ #define IBM_IDAM_LEN 7 /* plus prologue */
#define IBM_DAM1 0xF8 /* sector data (type 1) */ #define IBM_DAM1 0xF8 /* sector data (type 1) */
#define IBM_DAM2 0xFB /* sector data (type 2) */ #define IBM_DAM2 0xFB /* sector data (type 2) */
#define IBM_TRS80DAM1 0xF9 /* sector data (TRS-80 directory) */ #define IBM_TRS80DAM1 0xF9 /* sector data (TRS-80 directory) */
#define IBM_TRS80DAM2 0xFA /* sector data (TRS-80 directory) */ #define IBM_TRS80DAM2 0xFA /* sector data (TRS-80 directory) */
#define IBM_DAM_LEN 1 /* plus prologue and user data */ #define IBM_DAM_LEN 1 /* plus prologue and user data */
/* Length of a DAM record is determined by the previous sector header. */ /* Length of a DAM record is determined by the previous sector header. */
struct IbmIdam struct IbmIdam
{ {
uint8_t id; uint8_t id;
uint8_t track; uint8_t cylinder;
uint8_t side; uint8_t side;
uint8_t sector; uint8_t sector;
uint8_t sectorSize; uint8_t sectorSize;
uint8_t crc[2]; uint8_t crc[2];
}; };
class Encoder; class AbstractEncoder;
class Decoder; class AbstractDecoder;
class DecoderProto; class DecoderProto;
class EncoderProto; class EncoderProto;
extern std::unique_ptr<Decoder> createIbmDecoder(const DecoderProto& config); extern std::unique_ptr<AbstractDecoder> createIbmDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createIbmEncoder(const EncoderProto& config); extern std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config);
#endif #endif

View File

@@ -1,31 +1,33 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
// Next: 7
message IbmDecoderProto { message IbmDecoderProto {
// Next: 11 message SectorsProto {
message TrackdataProto { repeated int32 sector = 1 [(help) = "require these sectors to exist for a good read"];
optional int32 track = 7 [(help) = "if set, the format applies only to this track"];
optional int32 head = 8 [(help) = "if set, the format applies only to this head"];
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool invert_side_byte = 4 [default = false, (help) = "invert the side byte in the sector header"];
repeated int32 ignore_sector = 10 [(help) = "sectors with these IDs will not be read"];
} }
repeated TrackdataProto trackdata = 1; optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool swap_sides = 4 [default = false, (help) = "put logical side 1 on physical side 0"];
optional SectorsProto sectors = 5 [(help) = "require these sectors to exist for a good read"];
} }
message IbmEncoderProto { message IbmEncoderProto {
// Next: 20 // Next: 18
message TrackdataProto { message TrackdataProto {
optional int32 track = 15 [(help) = "if set, the format applies only to this track"]; message SectorsProto {
repeated int32 sector = 1 [(help) = "write these sectors (in order) on each track"];
}
optional int32 cylinder = 15 [(help) = "if set, the format applies only to this track"];
optional int32 head = 16 [(help) = "if set, the format applies only to this head"]; optional int32 head = 16 [(help) = "if set, the format applies only to this head"];
optional double track_length_ms = 1 [(help) = "length of track"];
optional int32 sector_size = 2 [default=512, (help) = "number of bytes per sector"];
optional bool emit_iam = 3 [default=true, (help) = "whether to emit an IAM record"]; optional bool emit_iam = 3 [default=true, (help) = "whether to emit an IAM record"];
optional double target_clock_period_us = 5 [default=4, (help) = "data clock rate on target disk"]; optional double clock_rate_khz = 5 [(help) = "data clock rate"];
optional bool use_fm = 6 [default=false, (help) = "whether to use FM encoding rather than MFM"]; optional bool use_fm = 6 [default=false, (help) = "whether to use FM encoding rather than MFM"];
optional int32 idam_byte = 7 [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"]; optional int32 idam_byte = 7 [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"];
optional int32 dam_byte = 8 [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"]; optional int32 dam_byte = 8 [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"];
@@ -33,9 +35,8 @@ message IbmEncoderProto {
optional int32 gap1 = 10 [default=50, (help) = "size of gap 2 (the post-ID gap)"]; optional int32 gap1 = 10 [default=50, (help) = "size of gap 2 (the post-ID gap)"];
optional int32 gap2 = 11 [default=22, (help) = "size of gap 3 (the pre-data gap)"]; optional int32 gap2 = 11 [default=22, (help) = "size of gap 3 (the pre-data gap)"];
optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"]; optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"];
optional bool invert_side_byte = 19 [default=false, (help) = "invert the side byte before writing"]; optional bool swap_sides = 14 [default=false, (help) = "swap side bytes when writing"];
optional int32 gap_fill_byte = 18 [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"]; optional SectorsProto sectors = 17 [(help) = "write these sectors (in order) on each track"];
optional double target_rotational_period_ms = 1 [default=200, (help) = "rotational period of target disk"];
} }
repeated TrackdataProto trackdata = 1; repeated TrackdataProto trackdata = 1;

View File

@@ -1,37 +1,33 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "macintosh.h" #include "macintosh.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_RECORD_PATTERN(24, MAC_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(24, MAC_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(24, MAC_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(24, MAC_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr) static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
/* This is extremely inspired by the MESS implementation, written by Nathan /* This is extremely inspired by the MESS implementation, written by Nathan Woods
* Woods and R. Belmont: * and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
* https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
*/ */
static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status) static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status)
{ {
@@ -45,7 +41,7 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status)
uint8_t b2[LOOKUP_LEN + 1]; uint8_t b2[LOOKUP_LEN + 1];
uint8_t b3[LOOKUP_LEN + 1]; uint8_t b3[LOOKUP_LEN + 1];
for (int i = 0; i <= LOOKUP_LEN; i++) for (int i=0; i<=LOOKUP_LEN; i++)
{ {
uint8_t w4 = br.read_8(); uint8_t w4 = br.read_8();
uint8_t w1 = br.read_8(); uint8_t w1 = br.read_8();
@@ -126,71 +122,97 @@ uint8_t decode_side(uint8_t side)
return !!(side & 0x20); return !!(side & 0x20);
} }
class MacintoshDecoder : public Decoder class MacintoshDecoder : public AbstractDecoder
{ {
public: public:
MacintoshDecoder(const DecoderProto& config): Decoder(config) {} MacintoshDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return DATA_RECORD;
return UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
if (readRaw24() != MAC_SECTOR_RECORD) /* Skip ID (as we know it's a MAC_SECTOR_RECORD). */
return; readRawBits(24);
/* Read header. */ /* Read header. */
auto header = toBytes(readRawBits(7 * 8)).slice(0, 7); auto header = toBytes(readRawBits(7*8)).slice(0, 7);
uint8_t encodedTrack = decode_data_gcr(header[0]);
if (encodedTrack != (_sector->physicalCylinder & 0x3f))
return;
uint8_t encodedSector = decode_data_gcr(header[1]);
uint8_t encodedSide = decode_data_gcr(header[2]);
uint8_t formatByte = decode_data_gcr(header[3]);
uint8_t wantedsum = decode_data_gcr(header[4]);
uint8_t encodedTrack = decode_data_gcr(header[0]); if (encodedSector > 11)
if (encodedTrack != (_sector->physicalTrack & 0x3f)) return;
return;
uint8_t encodedSector = decode_data_gcr(header[1]); _sector->logicalTrack = _sector->physicalCylinder;
uint8_t encodedSide = decode_data_gcr(header[2]); _sector->logicalSide = decode_side(encodedSide);
uint8_t formatByte = decode_data_gcr(header[3]); _sector->logicalSector = encodedSector;
uint8_t wantedsum = decode_data_gcr(header[4]); uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
if (wantedsum == gotsum)
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
if (encodedSector > 11) void decodeDataRecord()
return; {
auto id = toBytes(readRawBits(24)).reader().read_be24();
if (id != MAC_DATA_RECORD)
return;
_sector->logicalTrack = _sector->physicalTrack; /* Read data. */
_sector->logicalSide = decode_side(encodedSide);
_sector->logicalSector = encodedSector;
uint8_t gotsum =
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
if (wantedsum == gotsum)
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord() override readRawBits(8); /* skip spare byte */
{ auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH*8))
if (readRaw24() != MAC_DATA_RECORD) .slice(0, MAC_ENCODED_SECTOR_LENGTH);
return;
/* Read data. */ for (unsigned i=0; i<inputbuffer.size(); i++)
inputbuffer[i] = decode_data_gcr(inputbuffer[i]);
_sector->status = Sector::BAD_CHECKSUM;
Bytes userData = decode_crazy_data(inputbuffer, _sector->status);
_sector->data.clear();
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
}
readRawBits(8); /* skip spare byte */ std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const
auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH * 8)) {
.slice(0, MAC_ENCODED_SECTOR_LENGTH); int count;
if (cylinder < 16)
count = 12;
else if (cylinder < 32)
count = 11;
else if (cylinder < 48)
count = 10;
else if (cylinder < 64)
count = 9;
else
count = 8;
for (unsigned i = 0; i < inputbuffer.size(); i++) std::set<unsigned> sectors;
inputbuffer[i] = decode_data_gcr(inputbuffer[i]); while (count--)
sectors.insert(count);
_sector->status = Sector::BAD_CHECKSUM; return sectors;
Bytes userData = decode_crazy_data(inputbuffer, _sector->status); }
_sector->data.clear();
_sector->data.writer()
.append(userData.slice(12, 512))
.append(userData.slice(0, 12));
}
}; };
std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createMacintoshDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new MacintoshDecoder(config)); return std::unique_ptr<AbstractDecoder>(new MacintoshDecoder(config));
} }

View File

@@ -1,13 +1,12 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/core/utils.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "macintosh.h" #include "macintosh.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#include "lib/data/layout.h"
#include "arch/macintosh/macintosh.pb.h" #include "arch/macintosh/macintosh.pb.h"
#include <ctype.h> #include <ctype.h>
@@ -15,46 +14,44 @@ static bool lastBit;
static double clockRateUsForTrack(unsigned track) static double clockRateUsForTrack(unsigned track)
{ {
if (track < 16) if (track < 16)
return 2.63; return 2.623;
if (track < 32) if (track < 32)
return 2.89; return 2.861;
if (track < 48) if (track < 48)
return 3.20; return 3.148;
if (track < 64) if (track < 64)
return 3.57; return 3.497;
return 3.98; return 3.934;
} }
static unsigned sectorsForTrack(unsigned track) static unsigned sectorsForTrack(unsigned track)
{ {
if (track < 16) if (track < 16)
return 12; return 12;
if (track < 32) if (track < 32)
return 11; return 11;
if (track < 48) if (track < 48)
return 10; return 10;
if (track < 64) if (track < 64)
return 9; return 9;
return 8; return 8;
} }
static int encode_data_gcr(uint8_t gcr) static int encode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case data: \ case data: return gcr;
return gcr; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
/* This is extremely inspired by the MESS implementation, written by Nathan /* This is extremely inspired by the MESS implementation, written by Nathan Woods
* Woods and R. Belmont: * and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
* https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
*/ */
static Bytes encode_crazy_data(const Bytes& input) static Bytes encode_crazy_data(const Bytes& input)
{ {
@@ -62,7 +59,7 @@ static Bytes encode_crazy_data(const Bytes& input)
ByteWriter bw(output); ByteWriter bw(output);
ByteReader br(input); ByteReader br(input);
uint8_t w1, w2, w3, w4; uint8_t w1, w2, w3, w4;
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3; static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
@@ -70,94 +67,92 @@ static Bytes encode_crazy_data(const Bytes& input)
uint8_t b2[LOOKUP_LEN + 1]; uint8_t b2[LOOKUP_LEN + 1];
uint8_t b3[LOOKUP_LEN + 1]; uint8_t b3[LOOKUP_LEN + 1];
uint32_t c1 = 0; uint32_t c1 = 0;
uint32_t c2 = 0; uint32_t c2 = 0;
uint32_t c3 = 0; uint32_t c3 = 0;
for (int j = 0;; j++) for (int j=0;; j++)
{ {
c1 = (c1 & 0xff) << 1; c1 = (c1 & 0xff) << 1;
if (c1 & 0x0100) if (c1 & 0x0100)
c1++; c1++;
uint8_t val = br.read_8(); uint8_t val = br.read_8();
c3 += val; c3 += val;
if (c1 & 0x0100) if (c1 & 0x0100)
{ {
c3++; c3++;
c1 &= 0xff; c1 &= 0xff;
} }
b1[j] = (val ^ c1) & 0xff; b1[j] = (val ^ c1) & 0xff;
val = br.read_8(); val = br.read_8();
c2 += val; c2 += val;
if (c3 > 0xff) if (c3 > 0xff)
{ {
c2++; c2++;
c3 &= 0xff; c3 &= 0xff;
} }
b2[j] = (val ^ c3) & 0xff; b2[j] = (val ^ c3) & 0xff;
if (br.pos == 524) if (br.pos == 524)
break; break;
val = br.read_8(); val = br.read_8();
c1 += val; c1 += val;
if (c2 > 0xff) if (c2 > 0xff)
{ {
c1++; c1++;
c2 &= 0xff; c2 &= 0xff;
} }
b3[j] = (val ^ c2) & 0xff; b3[j] = (val ^ c2) & 0xff;
} }
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2); uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
b3[LOOKUP_LEN] = 0; b3[LOOKUP_LEN] = 0;
for (int i = 0; i <= LOOKUP_LEN; i++) for (int i = 0; i <= LOOKUP_LEN; i++)
{ {
w1 = b1[i] & 0x3f; w1 = b1[i] & 0x3f;
w2 = b2[i] & 0x3f; w2 = b2[i] & 0x3f;
w3 = b3[i] & 0x3f; w3 = b3[i] & 0x3f;
w4 = ((b1[i] & 0xc0) >> 2); w4 = ((b1[i] & 0xc0) >> 2);
w4 |= ((b2[i] & 0xc0) >> 4); w4 |= ((b2[i] & 0xc0) >> 4);
w4 |= ((b3[i] & 0xc0) >> 6); w4 |= ((b3[i] & 0xc0) >> 6);
bw.write_8(w4); bw.write_8(w4);
bw.write_8(w1); bw.write_8(w1);
bw.write_8(w2); bw.write_8(w2);
if (i != LOOKUP_LEN) if (i != LOOKUP_LEN)
bw.write_8(w3); bw.write_8(w3);
} }
bw.write_8(c4 & 0x3f); bw.write_8(c4 & 0x3f);
bw.write_8(c3 & 0x3f); bw.write_8(c3 & 0x3f);
bw.write_8(c2 & 0x3f); bw.write_8(c2 & 0x3f);
bw.write_8(c1 & 0x3f); bw.write_8(c1 & 0x3f);
return output; return output;
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{ {
for (bool bit : src) for (bool bit : src)
{ {
if (cursor < bits.size()) if (cursor < bits.size())
bits[cursor++] = bit; bits[cursor++] = bit;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{ {
cursor += width; cursor += width;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = cursor - i - 1; unsigned pos = cursor - i - 1;
if (pos < bits.size()) if (pos < bits.size())
bits[pos] = data & 1; bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
static uint8_t encode_side(uint8_t track, uint8_t side) static uint8_t encode_side(uint8_t track, uint8_t side)
@@ -166,93 +161,103 @@ static uint8_t encode_side(uint8_t track, uint8_t side)
* bit 5) and also whether we're above track 0x3f (in bit 0). * bit 5) and also whether we're above track 0x3f (in bit 0).
*/ */
return (side ? 0x20 : 0x00) | ((track > 0x3f) ? 0x01 : 0x00); return (side ? 0x20 : 0x00) | ((track>0x3f) ? 0x01 : 0x00);
} }
static void write_sector(std::vector<bool>& bits, static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{ {
if ((sector->data.size() != 512) && (sector->data.size() != 524)) if ((sector->data.size() != 512) && (sector->data.size() != 524))
error("unsupported sector size --- you must pick 512 or 524"); Error() << "unsupported sector size --- you must pick 512 or 524";
write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */ write_bits(bits, cursor, 0xff, 1*8); /* pad byte */
for (int i = 0; i < 7; i++) for (int i=0; i<7; i++)
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */ write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3 * 8); write_bits(bits, cursor, MAC_SECTOR_RECORD, 3*8);
uint8_t encodedTrack = sector->logicalTrack & 0x3f; uint8_t encodedTrack = sector->logicalTrack & 0x3f;
uint8_t encodedSector = sector->logicalSector; uint8_t encodedSector = sector->logicalSector;
uint8_t encodedSide = uint8_t encodedSide = encode_side(sector->logicalTrack, sector->logicalSide);
encode_side(sector->logicalTrack, sector->logicalSide); uint8_t formatByte = MAC_FORMAT_BYTE;
uint8_t formatByte = MAC_FORMAT_BYTE; uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
uint8_t headerChecksum =
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1 * 8); write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1 * 8); write_bits(bits, cursor, encode_data_gcr(encodedSector), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1 * 8); write_bits(bits, cursor, encode_data_gcr(encodedSide), 1*8);
write_bits(bits, cursor, encode_data_gcr(formatByte), 1 * 8); write_bits(bits, cursor, encode_data_gcr(formatByte), 1*8);
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1 * 8); write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1*8);
write_bits(bits, cursor, 0xdeaaff, 3 * 8); write_bits(bits, cursor, 0xdeaaff, 3*8);
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */ write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_DATA_RECORD, 3 * 8); write_bits(bits, cursor, MAC_DATA_RECORD, 3*8);
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1 * 8); write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1*8);
Bytes wireData; Bytes wireData;
wireData.writer() wireData.writer().append(sector->data.slice(512, 12)).append(sector->data.slice(0, 512));
.append(sector->data.slice(512, 12)) for (uint8_t b : encode_crazy_data(wireData))
.append(sector->data.slice(0, 512)); write_bits(bits, cursor, encode_data_gcr(b), 1*8);
for (uint8_t b : encode_crazy_data(wireData))
write_bits(bits, cursor, encode_data_gcr(b), 1 * 8);
write_bits(bits, cursor, 0xdeaaff, 3 * 8); write_bits(bits, cursor, 0xdeaaff, 3*8);
} }
class MacintoshEncoder : public Encoder class MacintoshEncoder : public AbstractEncoder
{ {
public: public:
MacintoshEncoder(const EncoderProto& config): MacintoshEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.macintosh()) _config(config.macintosh())
{ {}
}
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
double clockRateUs = clockRateUsForTrack(trackInfo->logicalTrack);
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits, if ((physicalTrack >= 0) && (physicalTrack < MAC_TRACKS_PER_DISK))
cursor, {
_config.post_index_gap_us() / clockRateUs, unsigned numSectors = sectorsForTrack(physicalTrack);
{true, false}); for (int sectorId=0; sectorId<numSectors; sectorId++)
lastBit = false; {
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
for (const auto& sector : sectors) return sectors;
write_sector(bits, cursor, sector); }
if (cursor >= bits.size()) std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
error("track data overrun by {} bits", cursor - bits.size()); const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
fillBitmapTo(bits, cursor, bits.size(), {true, false}); {
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); double clockRateUs = clockRateUsForTrack(physicalTrack) * _config.clock_compensation_factor();
fluxmap->appendBits( int bitsPerRevolution = 200000.0 / clockRateUs;
bits, calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6)); std::vector<bool> bits(bitsPerRevolution);
return fluxmap; unsigned cursor = 0;
}
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
lastBit = false;
for (const auto& sector : sectors)
write_sector(bits, cursor, sector);
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}
private: private:
const MacintoshEncoderProto& _config; const MacintoshEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createMacintoshEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new MacintoshEncoder(config)); return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
} }

View File

@@ -1,23 +1,22 @@
#ifndef MACINTOSH_H #ifndef MACINTOSH_H
#define MACINTOSH_H #define MACINTOSH_H
#define MAC_SECTOR_RECORD 0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */ #define MAC_SECTOR_RECORD 0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */
#define MAC_DATA_RECORD 0xd5aaad /* 1101 0101 1010 1010 1010 1101 */ #define MAC_DATA_RECORD 0xd5aaad /* 1101 0101 1010 1010 1010 1101 */
#define MAC_SECTOR_LENGTH 524 /* yes, really */ #define MAC_SECTOR_LENGTH 524 /* yes, really */
#define MAC_ENCODED_SECTOR_LENGTH 703 #define MAC_ENCODED_SECTOR_LENGTH 703
#define MAC_FORMAT_BYTE 0x22 #define MAC_FORMAT_BYTE 0x22
#define MAC_TRACKS_PER_DISK 80 #define MAC_TRACKS_PER_DISK 80
class Encoder; class AbstractEncoder;
class Decoder; class AbstractDecoder;
class DecoderProto; class DecoderProto;
class EncoderProto; class EncoderProto;
extern std::unique_ptr<Decoder> createMacintoshDecoder( extern std::unique_ptr<AbstractDecoder> createMacintoshDecoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createMacintoshEncoder(
const EncoderProto& config);
#endif #endif

View File

@@ -1,11 +1,14 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
message MacintoshDecoderProto {} message MacintoshDecoderProto {}
message MacintoshEncoderProto { message MacintoshEncoderProto {
optional double post_index_gap_us = 1 [default = 0.0, optional double post_index_gap_us = 1 [default = 0.0,
(help) = "post-index gap before first sector header (microseconds)."]; (help) = "post-index gap before first sector header (microseconds)."];
optional double clock_compensation_factor = 2 [default = 1.0,
(help) = "scale the output clock by this much."];
} }

View File

@@ -1,295 +1,75 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "sector.h"
#include "lib/data/sector.h"
#include "micropolis.h" #include "micropolis.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. /* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */
* static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555);
* 00 00 00 F F
* 0000 0000 0000 0000 0000 0000 0101 0101 0101 0101
* A A A A A A 5 5 5 5
*/
static const FluxPattern SECTOR_SYNC_PATTERN(64, 0xAAAAAAAAAAAA5555LL);
/* Pattern to skip past current SYNC. */ /* Adds all bytes, with carry. */
static const FluxPattern SECTOR_ADVANCE_PATTERN(64, 0xAAAAAAAAAAAAAAAALL); uint8_t micropolisChecksum(const Bytes& bytes) {
ByteReader br(bytes);
/* Standard Micropolis checksum. Adds all bytes, with carry. */ uint16_t sum = 0;
uint8_t micropolisChecksum(const Bytes& bytes) while (!br.eof()) {
{ if (sum > 0xFF) {
ByteReader br(bytes); sum -= 0x100 - 1;
uint16_t sum = 0; }
while (!br.eof()) sum += br.read_8();
{ }
if (sum > 0xFF) /* The last carry is ignored */
{ return sum & 0xFF;
sum -= 0x100 - 1;
}
sum += br.read_8();
}
/* The last carry is ignored */
return sum & 0xFF;
} }
/* Vector MZOS does not use the standard Micropolis checksum. class MicropolisDecoder : public AbstractDecoder
* The checksum is initially 0.
* For each data byte in the 256-byte payload, rotate left,
* carrying bit 7 to bit 0. XOR with the current checksum.
*
* Unlike the Micropolis checksum, this does not cover the 12-byte
* header (track, sector, 10 OS-specific bytes.)
*/
uint8_t mzosChecksum(const Bytes& bytes)
{
ByteReader br(bytes);
uint8_t checksum = 0;
uint8_t databyte;
while (!br.eof())
{
databyte = br.read_8();
checksum ^= ((databyte << 1) | (databyte >> 7));
}
return checksum;
}
static uint8_t b(uint32_t field, uint8_t pos)
{
return (field >> pos) & 1;
}
static uint8_t eccNextBit(uint32_t ecc, uint8_t data_bit)
{
// This is 0x81932080 which is 0x0104C981 with reversed bits
return b(ecc, 7) ^ b(ecc, 13) ^ b(ecc, 16) ^ b(ecc, 17) ^ b(ecc, 20) ^
b(ecc, 23) ^ b(ecc, 24) ^ b(ecc, 31) ^ data_bit;
}
uint32_t vectorGraphicEcc(const Bytes& bytes)
{
uint32_t e = 0;
Bytes payloadBytes = bytes.slice(0, bytes.size() - 4);
ByteReader payload(payloadBytes);
while (!payload.eof())
{
uint8_t byte = payload.read_8();
for (int i = 0; i < 8; i++)
{
e = (e << 1) | eccNextBit(e, byte >> 7);
byte <<= 1;
}
}
Bytes trailerBytes = bytes.slice(bytes.size() - 4);
ByteReader trailer(trailerBytes);
uint32_t res = e;
while (!trailer.eof())
{
uint8_t byte = trailer.read_8();
for (int i = 0; i < 8; i++)
{
res = (res << 1) | eccNextBit(e, byte >> 7);
e <<= 1;
byte <<= 1;
}
}
return res;
}
/* Fixes bytes when possible, returning true if changed. */
static bool vectorGraphicEccFix(Bytes& bytes, uint32_t syndrome)
{
uint32_t ecc = syndrome;
int pos = (MICROPOLIS_ENCODED_SECTOR_SIZE - 5) * 8 + 7;
bool aligned = false;
while ((ecc & 0xff000000) == 0)
{
pos += 8;
ecc <<= 8;
}
for (; pos >= 0; pos--)
{
bool bit = ecc & 1;
ecc >>= 1;
if (bit)
ecc ^= 0x808264c0;
if ((ecc & 0xff07ffff) == 0)
aligned = true;
if (aligned && pos % 8 == 0)
break;
}
if (pos < 0)
return false;
bytes[pos / 8] ^= ecc >> 16;
return true;
}
class MicropolisDecoder : public Decoder
{ {
public: public:
MicropolisDecoder(const DecoderProto& config): MicropolisDecoder(const DecoderProto& config):
Decoder(config), AbstractDecoder(config)
_config(config.micropolis()) {}
{
_checksumType = _config.checksum_type();
}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
nanoseconds_t now = tell().ns(); _fmr->seekToIndexMark();
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
if (matcher == &SECTOR_SYNC_PATTERN) {
readRawBits(16);
return SECTOR_RECORD;
}
return UNKNOWN_RECORD;
}
/* For all but the first sector, seek to the next sector pulse. void decodeSectorRecord()
* The first sector does not contain the sector pulse in the fluxmap. {
*/ auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
if (now != 0) auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
{ ByteReader br(bytes);
seekToIndexMark();
now = tell().ns();
}
/* Discard a possible partial sector at the end of the track. br.read_8(); /* sync */
* This partial sector could be mistaken for a conflicted sector, if _sector->logicalTrack = br.read_8();
* whatever data read happens to match the checksum of 0, which is _sector->logicalSide = _sector->physicalHead;
* rare, but has been observed on some disks. There's 570uS of slack in _sector->logicalSector = br.read_8();
* each sector, after accounting for preamble, data, and postamble. if (_sector->logicalSector > 15)
*/ return;
if (now > (getFluxmapDuration() - 12.0e6)) if (_sector->logicalTrack > 77)
{ return;
seekToIndexMark();
return 0;
}
nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN); br.read(10); /* OS data or padding */
_sector->data = br.read(256);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
br.read(5); /* 4 byte ECC and ECC-present flag */
auto syncDelta = tell().ns() - now; _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
/* Due to the weak nature of the Micropolis SYNC patern, }
* it's possible to detect a false SYNC during the gap
* between the sector pulse and the write gate. If the SYNC
* is detected less than 100uS after the sector pulse, search
* for another valid SYNC.
*
* Reference: Vector Micropolis Disk Controller Board Technical
* Information Manual, pp. 1-16.
*/
if ((syncDelta > 0) && (syncDelta < 100e3))
{
seekToPattern(SECTOR_ADVANCE_PATTERN);
clock = seekToPattern(SECTOR_SYNC_PATTERN);
}
_sector->headerStartTime = tell().ns();
/* seekToPattern() can skip past the index hole, if this happens
* too close to the end of the Fluxmap, discard the sector. The
* preamble was expected to be 640uS long.
*/
if (_sector->headerStartTime > (getFluxmapDuration() - 11.3e6))
{
return 0;
}
return clock;
}
void decodeSectorRecord() override
{
readRawBits(48);
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE * 16);
auto bytes =
decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
bool eccPresent = bytes[274] == 0xaa;
uint32_t ecc = 0;
if (_config.ecc_type() == MicropolisDecoderProto::VECTOR && eccPresent)
{
ecc = vectorGraphicEcc(bytes.slice(0, 274));
if (ecc != 0)
{
vectorGraphicEccFix(bytes, ecc);
ecc = vectorGraphicEcc(bytes.slice(0, 274));
}
}
ByteReader br(bytes);
int syncByte = br.read_8(); /* sync */
if (syncByte != 0xFF)
return;
_sector->logicalTrack = br.read_8();
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = br.read_8();
if (_sector->logicalSector > 15)
return;
if (_sector->logicalTrack > 76)
return;
if (_sector->logicalTrack != _sector->physicalTrack)
return;
br.read(10); /* OS data or padding */
auto data = br.read(MICROPOLIS_PAYLOAD_SIZE);
uint8_t wantChecksum = br.read_8();
/* If not specified, automatically determine the checksum type.
* Once the checksum type is determined, it will be used for the
* entire disk.
*/
if (_checksumType == MicropolisDecoderProto::AUTO)
{
/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS
* checksums */
if (wantChecksum == micropolisChecksum(bytes.slice(1, 2 + 266)))
{
_checksumType = MicropolisDecoderProto::MICROPOLIS;
}
else if (wantChecksum ==
mzosChecksum(bytes.slice(
MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE)))
{
_checksumType = MicropolisDecoderProto::MZOS;
std::cout << "Note: MZOS checksum detected." << std::endl;
}
}
uint8_t gotChecksum;
if (_checksumType == MicropolisDecoderProto::MZOS)
{
gotChecksum = mzosChecksum(
bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE));
}
else
{
gotChecksum = micropolisChecksum(bytes.slice(1, 2 + 266));
}
br.read(5); /* 4 byte ECC and ECC-present flag */
if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE)
_sector->data = data;
else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
_sector->data = bytes;
else
error("Sector output size may only be 256 or 275");
if (wantChecksum == gotChecksum && (!eccPresent || ecc == 0))
_sector->status = Sector::OK;
else
_sector->status = Sector::BAD_CHECKSUM;
}
private:
const MicropolisDecoderProto& _config;
MicropolisDecoderProto_ChecksumType
_checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
}; };
std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new MicropolisDecoder(config)); return std::unique_ptr<AbstractDecoder>(new MicropolisDecoder(config));
} }

View File

@@ -1,136 +1,113 @@
#include "lib/core/globals.h" #include "globals.h"
#include "micropolis.h" #include "micropolis.h"
#include "lib/data/sector.h" #include "sector.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#include "lib/data/image.h" #include "image.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
static void write_sector(std::vector<bool>& bits, static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
unsigned& cursor,
const std::shared_ptr<const Sector>& sector,
MicropolisEncoderProto::EccType eccType)
{ {
if ((sector->data.size() != 256) && if ((sector->data.size() != 256) && (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
(sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE)) Error() << "unsupported sector size --- you must pick 256 or 275";
error("unsupported sector size --- you must pick 256 or 275");
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35; int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
auto fullSector = std::make_shared<std::vector<uint8_t>>(); auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize); fullSector->reserve(fullSectorSize);
/* sector preamble */ /* sector preamble */
for (int i = 0; i < 40; i++) for (int i=0; i<40; i++)
fullSector->push_back(0); fullSector->push_back(0);
Bytes sectorData; Bytes sectorData;
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE) if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
{ sectorData = sector->data;
if (sector->data[0] != 0xFF) else
error( {
"275 byte sector doesn't start with sync byte 0xFF. " ByteWriter writer(sectorData);
"Corrupted sector"); writer.write_8(0xff); /* Sync */
uint8_t wantChecksum = sector->data[1 + 2 + 266]; writer.write_8(sector->logicalTrack);
uint8_t gotChecksum = writer.write_8(sector->logicalSector);
micropolisChecksum(sector->data.slice(1, 2 + 266)); for (int i=0; i<10; i++)
if (wantChecksum != gotChecksum) writer.write_8(0); /* Padding */
std::cerr << "Warning: checksum incorrect. Sector: " writer += sector->data;
<< sector->logicalSector << std::endl; writer.write_8(micropolisChecksum(sectorData.slice(1)));
sectorData = sector->data; for (int i=0; i<5; i++)
} writer.write_8(0); /* 4 byte ECC and ECC not present flag */
else }
{ for (uint8_t b : sectorData)
ByteWriter writer(sectorData); fullSector->push_back(b);
writer.write_8(0xff); /* Sync */ /* sector postamble */
writer.write_8(sector->logicalTrack); for (int i=0; i<40; i++)
writer.write_8(sector->logicalSector); fullSector->push_back(0);
for (int i = 0; i < 10; i++) /* filler */
writer.write_8(0); /* Padding */ for (int i=0; i<35; i++)
writer += sector->data; fullSector->push_back(0);
writer.write_8(micropolisChecksum(sectorData.slice(1)));
uint8_t eccPresent = 0; if (fullSector->size() != fullSectorSize)
uint32_t ecc = 0; Error() << "sector mismatched length";
if (eccType == MicropolisEncoderProto::VECTOR) bool lastBit = false;
{ encodeMfm(bits, cursor, fullSector, lastBit);
eccPresent = 0xaa; /* filler */
ecc = vectorGraphicEcc(sectorData + Bytes(4)); for (int i=0; i<5; i++)
} {
writer.write_be32(ecc); bits[cursor++] = 1;
writer.write_8(eccPresent); bits[cursor++] = 0;
} }
for (uint8_t b : sectorData)
fullSector->push_back(b);
/* sector postamble */
for (int i = 0; i < 40; i++)
fullSector->push_back(0);
/* filler */
for (int i = 0; i < 35; i++)
fullSector->push_back(0);
if (fullSector->size() != fullSectorSize)
error("sector mismatched length");
bool lastBit = false;
encodeMfm(bits, cursor, fullSector, lastBit);
/* filler */
for (int i = 0; i < 5; i++)
{
bits[cursor++] = 1;
bits[cursor++] = 0;
}
} }
class MicropolisEncoder : public Encoder class MicropolisEncoder : public AbstractEncoder
{ {
public: public:
MicropolisEncoder(const EncoderProto& config): MicropolisEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.micropolis()) _config(config.micropolis())
{ {}
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
int bitsPerRevolution =
(_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
std::vector<bool> bits(bitsPerRevolution); if ((physicalTrack >= 0) && (physicalTrack < 77))
std::vector<unsigned> indexes; {
unsigned prev_cursor = 0; for (int sectorId = 0; sectorId < 16; sectorId++)
unsigned cursor = 0; {
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
for (const auto& sectorData : sectors) return sectors;
{ }
indexes.push_back(cursor);
prev_cursor = cursor;
write_sector(bits, cursor, sectorData, _config.ecc_type());
}
indexes.push_back(prev_cursor + (cursor - prev_cursor) / 2);
indexes.push_back(cursor);
if (cursor != bits.size()) std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
error("track data mismatched length"); const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int bitsPerRevolution = 100000;
double clockRateUs = 2.00;
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); if ((physicalTrack < 0) || (physicalTrack >= 77) || sectors.empty())
nanoseconds_t clockPeriod = return std::unique_ptr<Fluxmap>();
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6); std::vector<bool> bits(bitsPerRevolution);
auto pos = bits.begin(); unsigned cursor = 0;
for (int i = 1; i < indexes.size(); i++)
{ for (const auto& sectorData : sectors)
auto end = bits.begin() + indexes[i]; write_sector(bits, cursor, sectorData);
fluxmap->appendBits(std::vector<bool>(pos, end), clockPeriod);
fluxmap->appendIndex(); if (cursor != bits.size())
pos = end; Error() << "track data mismatched length";
}
return fluxmap; std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
} fluxmap->appendBits(bits, clockRateUs * 1e3);
return fluxmap;
}
private: private:
const MicropolisEncoderProto& _config; const MicropolisEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createMicropolisEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createMicropolisEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new MicropolisEncoder(config)); return std::unique_ptr<AbstractEncoder>(new MicropolisEncoder(config));
} }

View File

@@ -1,22 +1,16 @@
#ifndef MICROPOLIS_H #ifndef MICROPOLIS_H
#define MICROPOLIS_H #define MICROPOLIS_H
#define MICROPOLIS_PAYLOAD_SIZE (256) #define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6)
#define MICROPOLIS_HEADER_SIZE (1 + 2 + 10)
#define MICROPOLIS_ENCODED_SECTOR_SIZE \
(MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6)
class Decoder; class AbstractDecoder;
class Encoder; class AbstractEncoder;
class EncoderProto; class EncoderProto;
class DecoderProto; class DecoderProto;
extern std::unique_ptr<Decoder> createMicropolisDecoder( extern std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createMicropolisEncoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createMicropolisEncoder(
const EncoderProto& config);
extern uint8_t micropolisChecksum(const Bytes& bytes); extern uint8_t micropolisChecksum(const Bytes& bytes);
extern uint32_t vectorGraphicEcc(const Bytes& bytes);
#endif #endif

View File

@@ -1,37 +1,5 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; message MicropolisDecoderProto {}
message MicropolisEncoderProto {}
message MicropolisDecoderProto {
enum ChecksumType {
AUTO = 0;
MICROPOLIS = 1;
MZOS = 2;
}
enum EccType {
NONE = 0;
VECTOR = 1;
}
optional int32 sector_output_size = 1 [default = 256,
(help) = "How much of the raw sector should be saved. Must be 256 or 275"];
optional ChecksumType checksum_type = 2 [default = AUTO,
(help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"];
optional EccType ecc_type = 3 [default = NONE,
(help) = "ECC type to use: NONE, VECTOR"];
}
message MicropolisEncoderProto {
enum EccType {
NONE = 0;
VECTOR = 1;
}
optional double clock_period_us = 1
[ default = 2.0, (help) = "clock rate on the real device" ];
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
optional EccType ecc_type = 3 [default = NONE,
(help) = "ECC type to use for IMG data: NONE, VECTOR"];
}

View File

@@ -1,11 +1,10 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "arch/mx/mx.h" #include "mx/mx.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "sector.h"
#include "lib/data/sector.h"
#include <string.h> #include <string.h>
const int SECTOR_SIZE = 256; const int SECTOR_SIZE = 256;
@@ -17,68 +16,78 @@ const int SECTOR_SIZE = 256;
*/ */
/* FM beginning of track marker: /* FM beginning of track marker:
* 0 0 f 3 decoded nibbles
* 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1
* 1010 1010 1010 1010 1111 1111 1010 1111 * 1010 1010 1010 1010 1111 1111 1010 1111
* a a a a f f a f encoded nibbles * a a a a f f a f
*/ */
const FluxPattern ID_PATTERN(32, 0xaaaaffaf); const FluxPattern ID_PATTERN(32, 0xaaaaffaf);
class MxDecoder : public Decoder class MxDecoder : public AbstractDecoder
{ {
public: public:
MxDecoder(const DecoderProto& config): Decoder(config) {} MxDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
void beginTrack() override void beginTrack()
{ {
_clock = _sector->clock = seekToPattern(ID_PATTERN); _currentSector = -1;
_currentSector = 0; _clock = 0;
} }
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
if (_currentSector == 11) if (_currentSector == -1)
{ {
/* That was the last sector on the disk. */ /* First sector in the track: look for the sync marker. */
return 0; const FluxMatcher* matcher = nullptr;
} _sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
else readRawBits(32); /* skip the ID mark */
return _clock; _logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16();
} }
else if (_currentSector == 10)
{
/* That was the last sector on the disk. */
return UNKNOWN_RECORD;
}
else
{
/* Otherwise we assume the clock from the first sector is still valid.
* The decoder framwork will automatically stop when we hit the end of
* the track. */
_sector->clock = _clock;
}
void decodeSectorRecord() override _currentSector++;
{ return SECTOR_RECORD;
/* Skip the ID pattern and track word, which is only present on the }
* first sector. We don't trust the track word because some driver
* don't write it correctly. */
if (_currentSector == 0) void decodeSectorRecord()
readRawBits(64); {
auto bits = readRawBits((SECTOR_SIZE+2)*16);
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2).swab();
auto bits = readRawBits((SECTOR_SIZE + 2) * 16); uint16_t gotChecksum = 0;
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE + 2); ByteReader br(bytes);
for (int i=0; i<(SECTOR_SIZE/2); i++)
gotChecksum += br.read_le16();
uint16_t wantChecksum = br.read_le16();
uint16_t gotChecksum = 0; _sector->logicalTrack = _logicalTrack;
ByteReader br(bytes); _sector->logicalSide = _sector->physicalHead;
for (int i = 0; i < (SECTOR_SIZE / 2); i++) _sector->logicalSector = _currentSector;
gotChecksum += br.read_be16(); _sector->data = bytes.slice(0, SECTOR_SIZE);
uint16_t wantChecksum = br.read_be16(); _sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = _currentSector;
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
_sector->status =
(gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
_currentSector++;
}
private: private:
nanoseconds_t _clock; nanoseconds_t _clock;
int _currentSector; int _currentSector;
int _logicalTrack;
}; };
std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createMxDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new MxDecoder(config)); return std::unique_ptr<AbstractDecoder>(new MxDecoder(config));
} }

View File

@@ -1,8 +1,8 @@
#ifndef MX_H #ifndef MX_H
#define MX_H #define MX_H
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
extern std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config); extern std::unique_ptr<AbstractDecoder> createMxDecoder(const DecoderProto& config);
#endif #endif

View File

@@ -11,19 +11,16 @@
* sure that the hardSectorId is correct. * sure that the hardSectorId is correct.
*/ */
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "sector.h"
#include "lib/data/sector.h"
#include "northstar.h" #include "northstar.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "lib/decoders/decoders.pb.h" #include "lib/decoders/decoders.pb.h"
#include "fmt/format.h" #include "fmt/format.h"
#define MFM_ID 0xaaaaaaaaaaaa5545LL
#define FM_ID 0xaaaaaaaaaaaaffefLL
/* /*
* MFM sectors have 32 bytes of 00's followed by two sync characters, * MFM sectors have 32 bytes of 00's followed by two sync characters,
* specified in the North Star MDS manual as 0xFBFB. * specified in the North Star MDS manual as 0xFBFB.
@@ -36,152 +33,164 @@
* 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101 * 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101
* A A A A A A 5 5 4 5 * A A A A A A 5 5 4 5
*/ */
static const FluxPattern MFM_PATTERN(64, MFM_ID); static const FluxPattern MFM_PATTERN(64, 0xAAAAAAAAAAAA5545LL);
/* FM sectors have 16 bytes of 00's followed by 0xFB. /* FM sectors have 16 bytes of 00's followed by 0xFB.
* 00 FB * 00 FB
* 0000 0000 1111 1111 1110 1111 * 0000 0000 1111 1111 1110 1111
* A A F F E F * A A F F E F
*/ */
static const FluxPattern FM_PATTERN(64, FM_ID); static const FluxPattern FM_PATTERN(64, 0xAAAAAAAAAAAAFFEFLL);
const FluxMatchers ANY_SECTOR_PATTERN({ const FluxMatchers ANY_SECTOR_PATTERN(
&MFM_PATTERN, {
&FM_PATTERN, &MFM_PATTERN,
}); &FM_PATTERN,
}
);
/* Checksum is initially 0. /* Checksum is initially 0.
* For each data byte, XOR with the current checksum. * For each data byte, XOR with the current checksum.
* Rotate checksum left, carrying bit 7 to bit 0. * Rotate checksum left, carrying bit 7 to bit 0.
*/ */
uint8_t northstarChecksum(const Bytes& bytes) uint8_t northstarChecksum(const Bytes& bytes) {
{ ByteReader br(bytes);
ByteReader br(bytes); uint8_t checksum = 0;
uint8_t checksum = 0;
while (!br.eof()) while (!br.eof()) {
{ checksum ^= br.read_8();
checksum ^= br.read_8(); checksum = ((checksum << 1) | ((checksum >> 7)));
checksum = ((checksum << 1) | ((checksum >> 7))); }
}
return checksum; return checksum;
} }
class NorthstarDecoder : public Decoder class NorthstarDecoder : public AbstractDecoder
{ {
public: public:
NorthstarDecoder(const DecoderProto& config): NorthstarDecoder(const DecoderProto& config):
Decoder(config), AbstractDecoder(config),
_config(config.northstar()) _config(config.northstar())
{ {}
}
/* Search for FM or MFM sector record */ /* Search for FM or MFM sector record */
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord() override
{ {
nanoseconds_t now = tell().ns(); nanoseconds_t now = _fmr->tell().ns();
/* For all but the first sector, seek to the next sector pulse. /* For all but the first sector, seek to the next sector pulse.
* The first sector does not contain the sector pulse in the fluxmap. * The first sector does not contain the sector pulse in the fluxmap.
*/ */
if (now != 0) if (now != 0) {
{ _fmr->seekToIndexMark();
seekToIndexMark(); now = _fmr->tell().ns();
now = tell().ns(); }
}
/* Discard a possible partial sector at the end of the track. /* Discard a possible partial sector at the end of the track.
* This partial sector could be mistaken for a conflicted sector, if * This partial sector could be mistaken for a conflicted sector, if
* whatever data read happens to match the checksum of 0, which is * whatever data read happens to match the checksum of 0, which is
* rare, but has been observed on some disks. * rare, but has been observed on some disks.
*/ */
if (now > (getFluxmapDuration() - 21e6)) if (now > (_fmr->getDuration() - 21e6)) {
{ _fmr->seekToIndexMark();
seekToIndexMark(); return(UNKNOWN_RECORD);
return 0; }
}
int msSinceIndex = std::round(now / 1e6); int msSinceIndex = std::round(now / 1e6);
/* Note that the seekToPattern ignores the sector pulses, so if const FluxMatcher* matcher = nullptr;
* a sector is not found for some reason, the seek will advance
* past one or more sector pulses. For this reason, calculate
* _hardSectorId after the sector header is found.
*/
nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
/* Discard a possible partial sector. */ /* Note that the seekToPattern ignores the sector pulses, so if
if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) * a sector is not found for some reason, the seek will advance
{ * past one or more sector pulses. For this reason, calculate
return 0; * _hardSectorId after the sector header is found.
} */
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6); int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
int sectorFoundTime; int sectorFoundTime;
/* Round time to the nearest 20ms */ /* Round time to the nearest 20ms */
if ((sectorFoundTimeRaw % 20) < 10) if ((sectorFoundTimeRaw % 20) < 10) {
{ sectorFoundTime = (sectorFoundTimeRaw / 20) * 20;
sectorFoundTime = (sectorFoundTimeRaw / 20) * 20; }
} else {
else sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
{ }
sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
}
/* Calculate the sector ID based on time since the index */ /* Calculate the sector ID based on time since the index */
_hardSectorId = (sectorFoundTime / 20) % 10; _hardSectorId = (sectorFoundTime / 20) % 10;
return clock; // std::cout << fmt::format(
} // "Sector ID {}: hole at {}ms, sector start at {}ms",
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
void decodeSectorRecord() override if (matcher == &MFM_PATTERN) {
{ _sectorType = SECTOR_TYPE_MFM;
uint64_t id = toBytes(readRawBits(64)).reader().read_be64(); readRawBits(48);
unsigned recordSize, payloadSize, headerSize; return SECTOR_RECORD;
}
if (id == MFM_ID) if (matcher == &FM_PATTERN) {
{ _sectorType = SECTOR_TYPE_FM;
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD; readRawBits(48);
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD; return SECTOR_RECORD;
headerSize = NORTHSTAR_HEADER_SIZE_DD; }
}
else
{
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
headerSize = NORTHSTAR_HEADER_SIZE_SD;
}
auto rawbits = readRawBits(recordSize * 16); return UNKNOWN_RECORD;
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize); }
ByteReader br(bytes);
_sector->logicalSide = _sector->physicalSide; void decodeSectorRecord() override
_sector->logicalSector = _hardSectorId; {
_sector->logicalTrack = _sector->physicalTrack; unsigned recordSize, payloadSize, headerSize;
if (headerSize == NORTHSTAR_HEADER_SIZE_DD) if (_sectorType == SECTOR_TYPE_MFM) {
{ recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
br.read_8(); /* MFM second Sync char, usually 0xFB */ payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
} headerSize = NORTHSTAR_HEADER_SIZE_DD;
}
else {
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
headerSize = NORTHSTAR_HEADER_SIZE_SD;
}
_sector->data = br.read(payloadSize); auto rawbits = readRawBits(recordSize * 16);
uint8_t wantChecksum = br.read_8(); auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
uint8_t gotChecksum = ByteReader br(bytes);
northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); uint8_t sync_char;
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; _sector->logicalSide = _sector->physicalHead;
} _sector->logicalSector = _hardSectorId;
_sector->logicalTrack = _sector->physicalCylinder;
sync_char = br.read_8(); /* Sync char: 0xFB */
if (_sectorType == SECTOR_TYPE_MFM) {
sync_char = br.read_8();/* MFM second Sync char, usually 0xFB */
}
_sector->data = br.read(payloadSize);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize, payloadSize));
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return sectors;
}
private: private:
const NorthstarDecoderProto& _config; const NorthstarDecoderProto& _config;
uint8_t _hardSectorId; uint8_t _sectorType = SECTOR_TYPE_MFM;
uint8_t _hardSectorId;
}; };
std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createNorthstarDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new NorthstarDecoder(config)); return std::unique_ptr<AbstractDecoder>(new NorthstarDecoder(config));
} }

View File

@@ -1,10 +1,10 @@
#include "lib/core/globals.h" #include "globals.h"
#include "northstar.h" #include "northstar.h"
#include "lib/data/sector.h" #include "sector.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#include "lib/data/image.h" #include "image.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#define GAP_FILL_SIZE_SD 30 #define GAP_FILL_SIZE_SD 30
@@ -12,157 +12,155 @@
#define GAP_FILL_SIZE_DD 62 #define GAP_FILL_SIZE_DD 62
#define PRE_HEADER_GAP_FILL_SIZE_DD 16 #define PRE_HEADER_GAP_FILL_SIZE_DD 16
#define GAP1_FILL_BYTE (0x4F) #define GAP1_FILL_BYTE (0x4F)
#define GAP2_FILL_BYTE (0x4F) #define GAP2_FILL_BYTE (0x4F)
#define TOTAL_SECTOR_BYTES () #define TOTAL_SECTOR_BYTES ()
static void write_sector(std::vector<bool>& bits, static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{ {
int preambleSize = 0; int preambleSize = 0;
int encodedSectorSize = 0; int encodedSectorSize = 0;
int gapFillSize = 0; int gapFillSize = 0;
int preHeaderGapFillSize = 0; int preHeaderGapFillSize = 0;
bool doubleDensity; bool doubleDensity;
switch (sector->data.size()) switch (sector->data.size()) {
{ case NORTHSTAR_PAYLOAD_SIZE_SD:
case NORTHSTAR_PAYLOAD_SIZE_SD: preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD; encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + gapFillSize = GAP_FILL_SIZE_SD;
NORTHSTAR_ENCODED_SECTOR_SIZE_SD + preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
GAP_FILL_SIZE_SD; doubleDensity = false;
gapFillSize = GAP_FILL_SIZE_SD; break;
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD; case NORTHSTAR_PAYLOAD_SIZE_DD:
doubleDensity = false; preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
break; encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD;
case NORTHSTAR_PAYLOAD_SIZE_DD: gapFillSize = GAP_FILL_SIZE_DD;
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD; preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + doubleDensity = true;
NORTHSTAR_ENCODED_SECTOR_SIZE_DD + break;
GAP_FILL_SIZE_DD; default:
gapFillSize = GAP_FILL_SIZE_DD; Error() << "unsupported sector size --- you must pick 256 or 512";
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD; break;
doubleDensity = true; }
break;
default:
error("unsupported sector size --- you must pick 256 or 512");
break;
}
int fullSectorSize = preambleSize + encodedSectorSize; int fullSectorSize = preambleSize + encodedSectorSize;
auto fullSector = std::make_shared<std::vector<uint8_t>>(); auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize); fullSector->reserve(fullSectorSize);
/* sector gap after index pulse */ /* sector gap after index pulse */
for (int i = 0; i < preHeaderGapFillSize; i++) for (int i = 0; i < preHeaderGapFillSize; i++)
fullSector->push_back(GAP1_FILL_BYTE); fullSector->push_back(GAP1_FILL_BYTE);
/* sector preamble */ /* sector preamble */
for (int i = 0; i < preambleSize; i++) for (int i = 0; i < preambleSize; i++)
fullSector->push_back(0); fullSector->push_back(0);
Bytes sectorData; Bytes sectorData;
if (sector->data.size() == encodedSectorSize) if (sector->data.size() == encodedSectorSize)
sectorData = sector->data; sectorData = sector->data;
else else {
{ ByteWriter writer(sectorData);
ByteWriter writer(sectorData); writer.write_8(0xFB); /* sync character */
writer.write_8(0xFB); /* sync character */ if (doubleDensity == true) {
if (doubleDensity == true) writer.write_8(0xFB); /* Double-density has two sync characters */
{ }
writer.write_8(0xFB); /* Double-density has two sync characters */ writer += sector->data;
} if (doubleDensity == true) {
writer += sector->data; writer.write_8(northstarChecksum(sectorData.slice(2)));
if (doubleDensity == true) } else {
{ writer.write_8(northstarChecksum(sectorData.slice(1)));
writer.write_8(northstarChecksum(sectorData.slice(2))); }
} }
else for (uint8_t b : sectorData)
{ fullSector->push_back(b);
writer.write_8(northstarChecksum(sectorData.slice(1)));
}
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
if (sector->logicalSector != 9) if (sector->logicalSector != 9) {
{ /* sector postamble */
/* sector postamble */ for (int i = 0; i < gapFillSize; i++)
for (int i = 0; i < gapFillSize; i++) fullSector->push_back(GAP2_FILL_BYTE);
fullSector->push_back(GAP2_FILL_BYTE);
if (fullSector->size() != fullSectorSize) if (fullSector->size() != fullSectorSize)
error("sector mismatched length ({}); expected {}, got {}", Error() << "sector mismatched length (" << sector->data.size() << ") expected: " << fullSector->size() << " got " << fullSectorSize;
sector->data.size(), } else {
fullSector->size(), /* sector postamble */
fullSectorSize); for (int i = 0; i < gapFillSize; i++)
} fullSector->push_back(GAP2_FILL_BYTE);
else }
{
/* sector postamble */
for (int i = 0; i < gapFillSize; i++)
fullSector->push_back(GAP2_FILL_BYTE);
}
bool lastBit = false; bool lastBit = false;
if (doubleDensity == true) if (doubleDensity == true) {
{ encodeMfm(bits, cursor, fullSector, lastBit);
encodeMfm(bits, cursor, fullSector, lastBit); }
} else {
else encodeFm(bits, cursor, fullSector);
{ }
encodeFm(bits, cursor, fullSector);
}
} }
class NorthstarEncoder : public Encoder class NorthstarEncoder : public AbstractEncoder
{ {
public: public:
NorthstarEncoder(const EncoderProto& config): NorthstarEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.northstar()) _config(config.northstar())
{ {}
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
int bitsPerRevolution = 100000;
double clockRateUs = _config.clock_period_us();
const auto& sector = *sectors.begin(); if ((physicalTrack >= 0) && (physicalTrack < 35))
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
bitsPerRevolution /= 2; // FM for (int sectorId = 0; sectorId < 10; sectorId++)
else {
clockRateUs /= 2.00; const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
std::vector<bool> bits(bitsPerRevolution); return sectors;
unsigned cursor = 0; }
for (const auto& sectorData : sectors) std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
write_sector(bits, cursor, sectorData); const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int bitsPerRevolution = 100000;
double clockRateUs = 4.00;
if (cursor > bits.size()) if ((physicalTrack < 0) || (physicalTrack >= 35) || sectors.empty())
error("track data overrun"); return std::unique_ptr<Fluxmap>();
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); const auto& sector = *sectors.begin();
fluxmap->appendBits(bits, if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
calculatePhysicalClockPeriod( bitsPerRevolution /= 2; // FM
clockRateUs * 1e3, _config.rotational_period_ms() * 1e6)); } else {
return fluxmap; clockRateUs /= 2.00;
} }
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
if (cursor > bits.size())
Error() << "track data overrun";
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs * 1e3);
return fluxmap;
}
private: private:
const NorthstarEncoderProto& _config; const NorthstarEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createNorthstarEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new NorthstarEncoder(config)); return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
} }

View File

@@ -1,8 +1,7 @@
#ifndef NORTHSTAR_H #ifndef NORTHSTAR_H
#define NORTHSTAR_H #define NORTHSTAR_H
/* Northstar floppies are 10-hard sectored disks with a sector format as /* Northstar floppies are 10-hard sectored disks with a sector format as follows:
* follows:
* *
* |----------------------------------| * |----------------------------------|
* | SYNC Byte | Payload | Checksum | * | SYNC Byte | Payload | Checksum |
@@ -13,30 +12,27 @@
* *
*/ */
#define NORTHSTAR_PREAMBLE_SIZE_SD (16) #define NORTHSTAR_PREAMBLE_SIZE_SD (16)
#define NORTHSTAR_PREAMBLE_SIZE_DD (32) #define NORTHSTAR_PREAMBLE_SIZE_DD (32)
#define NORTHSTAR_HEADER_SIZE_SD (1) #define NORTHSTAR_HEADER_SIZE_SD (1)
#define NORTHSTAR_HEADER_SIZE_DD (2) #define NORTHSTAR_HEADER_SIZE_DD (2)
#define NORTHSTAR_PAYLOAD_SIZE_SD (256) #define NORTHSTAR_PAYLOAD_SIZE_SD (256)
#define NORTHSTAR_PAYLOAD_SIZE_DD (512) #define NORTHSTAR_PAYLOAD_SIZE_DD (512)
#define NORTHSTAR_CHECKSUM_SIZE (1) #define NORTHSTAR_CHECKSUM_SIZE (1)
#define NORTHSTAR_ENCODED_SECTOR_SIZE_SD \ #define NORTHSTAR_ENCODED_SECTOR_SIZE_SD (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE)
(NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + \ #define NORTHSTAR_ENCODED_SECTOR_SIZE_DD (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE)
NORTHSTAR_CHECKSUM_SIZE)
#define NORTHSTAR_ENCODED_SECTOR_SIZE_DD \
(NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + \
NORTHSTAR_CHECKSUM_SIZE)
class Decoder; #define SECTOR_TYPE_MFM (0)
class Encoder; #define SECTOR_TYPE_FM (1)
class AbstractDecoder;
class AbstractEncoder;
class EncoderProto; class EncoderProto;
class DecoderProto; class DecoderProto;
extern uint8_t northstarChecksum(const Bytes& bytes); extern uint8_t northstarChecksum(const Bytes& bytes);
extern std::unique_ptr<Decoder> createNorthstarDecoder( extern std::unique_ptr<AbstractDecoder> createNorthstarDecoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createNorthstarEncoder(
const EncoderProto& config);
#endif /* NORTHSTAR */ #endif /* NORTHSTAR */

View File

@@ -1,13 +1,5 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto";
message NorthstarDecoderProto {} message NorthstarDecoderProto {}
message NorthstarEncoderProto {}
message NorthstarEncoderProto {
optional double clock_period_us = 1
[ default = 4.0, (help) = "clock rate on the real device (for FM)" ];
optional double rotational_period_ms = 2
[ default = 166.0, (help) = "rotational period on the real device" ];
}

View File

@@ -1,52 +0,0 @@
#include "lib/core/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/core/crc.h"
#include "lib/data/fluxmap.h"
#include "lib/data/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "lib/data/sector.h"
#include "lib/core/bytes.h"
#include "rolandd20.h"
#include <string.h>
/* Sector header record:
*
* BF FF FF FF FF FF FE AB
*
* This encodes to:
*
* e d 5 5 5 5 5 5
* 1110 1101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 4 4 4 4 5
* 0101 0101 0101 0100 0100 0100 0100 0101
*/
static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL);
class RolandD20Decoder : public Decoder
{
public:
RolandD20Decoder(const DecoderProto& config): Decoder(config) {}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord() override
{
auto rawbits = readRawBits(256);
const auto& bytes = decodeFmMfm(rawbits);
fmt::print("{} ", _sector->clock);
hexdump(std::cout, bytes);
}
};
std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
}

View File

@@ -1,4 +0,0 @@
#pragma once
extern std::unique_ptr<Decoder> createRolandD20Decoder(
const DecoderProto& config);

View File

@@ -1,5 +0,0 @@
syntax = "proto2";
message RolandD20DecoderProto {}

View File

@@ -1,155 +0,0 @@
#include "lib/core/globals.h"
#include "lib/data/fluxmap.h"
#include "lib/data/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "smaky6.h"
#include "lib/core/bytes.h"
#include "lib/core/crc.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
#include <string.h>
#include <algorithm>
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
class Smaky6Decoder : public Decoder
{
public:
Smaky6Decoder(const DecoderProto& config):
Decoder(config),
_config(config.smaky6())
{
}
private:
/* Returns the sector ID of the _current_ sector. */
int advanceToNextSector()
{
auto previous = tell();
seekToIndexMark();
auto now = tell();
if ((now.ns() - previous.ns()) < 9e6)
{
seekToIndexMark();
auto next = tell();
if ((next.ns() - now.ns()) < 9e6)
{
/* We just found sector 0. */
_sectorId = 0;
}
else
{
/* Spurious... */
seek(now);
}
}
return _sectorId++;
}
public:
void beginTrack() override
{
/* Find the start-of-track index marks, which will be an interval
* of about 6ms. */
seekToIndexMark();
_sectorId = 99;
for (;;)
{
auto pos = tell();
advanceToNextSector();
if (_sectorId < 99)
{
seek(pos);
break;
}
if (eof())
return;
}
/* Now we know where to start counting, start finding sectors. */
_sectorStarts.clear();
for (;;)
{
auto now = tell();
if (eof())
break;
int id = advanceToNextSector();
if (id < 16)
_sectorStarts.push_back(std::make_pair(id, now));
}
_sectorIndex = 0;
}
nanoseconds_t advanceToNextRecord() override
{
if (_sectorIndex == _sectorStarts.size())
{
seekToIndexMark();
return 0;
}
const auto& p = _sectorStarts[_sectorIndex++];
_sectorId = p.first;
seek(p.second);
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
return clock;
}
void decodeSectorRecord() override
{
readRawBits(33);
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
return;
const auto& rawbytes =
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
/* The Smaky bytes are stored backwards! Backwards! */
const auto& bytes =
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
ByteReader br(bytes);
uint8_t track = br.read_8();
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
uint8_t wantedChecksum = br.read_8();
uint8_t gotChecksum = sumBytes(data) & 0xff;
if (track != _sector->physicalTrack)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = _sectorId;
_sector->data = data;
_sector->status =
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const Smaky6DecoderProto& _config;
nanoseconds_t _startOfTrack;
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
int _sectorId;
int _sectorIndex;
};
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
}

View File

@@ -1,9 +0,0 @@
#ifndef SMAKY6_H
#define SMAKY6_H
#define SMAKY6_SECTOR_SIZE 256
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
#endif

View File

@@ -1,4 +0,0 @@
syntax = "proto2";
message Smaky6DecoderProto {}

View File

@@ -1,81 +0,0 @@
#include "lib/core/globals.h"
#include "lib/config/config.h"
#include "lib/decoders/decoders.h"
#include "arch/tartu/tartu.h"
#include "lib/core/crc.h"
#include "lib/data/fluxmap.h"
#include "lib/data/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "lib/data/sector.h"
#include <string.h>
constexpr uint64_t HEADER_BITS = 0xaaaaaaaa44895554LL;
constexpr uint64_t DATA_BITS = 0xaaaaaaaa44895545LL;
static const FluxPattern HEADER_PATTERN(64, HEADER_BITS);
static const FluxPattern DATA_PATTERN(64, DATA_BITS);
const FluxMatchers ANY_RECORD_PATTERN{&HEADER_PATTERN, &DATA_PATTERN};
class TartuDecoder : public Decoder
{
public:
TartuDecoder(const DecoderProto& config):
Decoder(config),
_config(config.tartu())
{
}
void beginTrack() override {}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord() override
{
if (readRaw64() != HEADER_BITS)
return;
auto bits = readRawBits(16 * 4);
auto bytes = decodeFmMfm(bits).slice(0, 4);
ByteReader br(bytes);
uint8_t track = br.read_8();
_sector->logicalTrack = track >> 1;
_sector->logicalSide = track & 1;
br.skip(1); /* seems always to be 1 */
_sector->logicalSector = br.read_8();
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = ~sumBytes(bytes.slice(0, 3));
if (wantChecksum == gotChecksum)
_sector->status = Sector::DATA_MISSING;
_sector->status = Sector::DATA_MISSING;
}
void decodeDataRecord() override
{
if (readRaw64() != DATA_BITS)
return;
const auto& bits = readRawBits(129 * 16);
const auto& bytes = decodeFmMfm(bits).slice(0, 129);
_sector->data = bytes.slice(0, 128);
uint8_t wantChecksum = bytes.reader().seek(128).read_8();
uint8_t gotChecksum = ~sumBytes(_sector->data);
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const TartuDecoderProto& _config;
};
std::unique_ptr<Decoder> createTartuDecoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new TartuDecoder(config));
}

View File

@@ -1,115 +0,0 @@
#include "lib/core/globals.h"
#include "lib/config/config.h"
#include "lib/decoders/decoders.h"
#include "lib/encoders/encoders.h"
#include "arch/tartu/tartu.h"
#include "lib/core/crc.h"
#include "lib/data/fluxmap.h"
#include "lib/data/sector.h"
#include <string.h>
class TartuEncoder : public Encoder
{
public:
TartuEncoder(const EncoderProto& config):
Encoder(config),
_config(config.tartu())
{
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
_clockRateUs = _config.clock_period_us();
int bitsPerRevolution =
(_config.target_rotational_period_ms() * 1000.0) / _clockRateUs;
const auto& sector = *sectors.begin();
_bits.resize(bitsPerRevolution);
_cursor = 0;
writeFillerRawBitsUs(_config.gap1_us());
bool first = true;
for (const auto& sectorData : sectors)
{
if (!first)
writeFillerRawBitsUs(_config.gap4_us());
first = false;
writeSector(sectorData);
}
if (_cursor > _bits.size())
error("track data overrun");
writeFillerRawBitsUs(_config.target_rotational_period_ms() * 1000.0);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits,
calculatePhysicalClockPeriod(_clockRateUs * 1e3,
_config.target_rotational_period_ms() * 1e6));
return fluxmap;
}
private:
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeRawBits(uint64_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeFillerRawBitsUs(double us)
{
unsigned count = (us / _clockRateUs) / 2;
for (int i = 0; i < count; i++)
writeRawBits(0b10, 2);
};
void writeSector(const std::shared_ptr<const Sector>& sectorData)
{
writeRawBits(_config.header_marker(), 64);
{
Bytes bytes;
ByteWriter bw(bytes);
bw.write_8(
(sectorData->logicalTrack << 1) | sectorData->logicalSide);
bw.write_8(1);
bw.write_8(sectorData->logicalSector);
bw.write_8(~sumBytes(bytes.slice(0, 3)));
writeBytes(bytes);
}
writeFillerRawBitsUs(_config.gap3_us());
writeRawBits(_config.data_marker(), 64);
{
Bytes bytes;
ByteWriter bw(bytes);
bw.append(sectorData->data);
bw.write_8(~sumBytes(bytes.slice(0, sectorData->data.size())));
writeBytes(bytes);
}
}
private:
const TartuEncoderProto& _config;
double _clockRateUs;
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
};
std::unique_ptr<Encoder> createTartuEncoder(const EncoderProto& config)
{
return std::unique_ptr<Encoder>(new TartuEncoder(config));
}

View File

@@ -1,7 +0,0 @@
#ifndef TARTU_H
#define TARTU_H
extern std::unique_ptr<Decoder> createTartuDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createTartuEncoder(const EncoderProto& config);
#endif

View File

@@ -1,27 +0,0 @@
syntax = "proto2";
import "lib/config/common.proto";
message TartuDecoderProto {}
message TartuEncoderProto {
optional double clock_period_us = 1
[ default = 2.0, (help) = "clock rate on the real device (for MFM)" ];
optional double target_rotational_period_ms = 2
[ default=200, (help) = "rotational period of target disk" ];
optional double gap1_us = 3
[ default = 1200,
(help) = "size of gap 1 (the post-index gap)" ];
optional double gap3_us = 4
[ default = 150,
(help) = "size of gap 3 (the pre-data gap)" ];
optional double gap4_us = 5
[ default = 180,
(help) = "size of gap 4 (the post-data or format gap)" ];
optional uint64 header_marker = 6
[ default = 0xaaaaaaaa44895554,
(help) = "64-bit raw bit pattern of header record marker" ];
optional uint64 data_marker = 7
[ default = 0xaaaaaaaa44895545,
(help) = "64-bit raw bit pattern of data record marker" ];
}

View File

@@ -1,14 +1,13 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#include "arch/tids990/tids990.h" #include "tids990/tids990.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h" #include "sector.h"
#include "lib/data/sector.h"
#include <string.h> #include <string.h>
#include "fmt/format.h" #include <fmt/format.h>
/* The Texas Instruments DS990 uses MFM with a scheme similar to a simplified /* The Texas Instruments DS990 uses MFM with a scheme similar to a simplified
* version of the IBM record scheme (it's actually easier to parse than IBM). * version of the IBM record scheme (it's actually easier to parse than IBM).
@@ -24,78 +23,76 @@
* When shifted out of phase, the special 0xa1 byte becomes an illegal * When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data. * encoding (you can't do 10 00). So this can't be spoofed by user data.
*/ */
const uint16_t SECTOR_ID = 0x550a;
const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244); const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244);
/* /*
* Data record: * Data record:
* data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550b * data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550c
* mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45 * mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45
* special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245 * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245
* ^^ * ^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal * When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data. * encoding (you can't do 10 00). So this can't be spoofed by user data.
*/ */
const uint16_t DATA_ID = 0x550b;
const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245); const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
class Tids990Decoder : public Decoder class Tids990Decoder : public AbstractDecoder
{ {
public: public:
Tids990Decoder(const DecoderProto& config): Decoder(config) {} Tids990Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE * 16); auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE); auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
ByteReader br(bytes); ByteReader br(bytes);
if (br.read_be16() != SECTOR_ID) uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
return;
uint16_t gotChecksum = br.seek(2);
crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE - 3)); _sector->logicalSide = br.read_8() >> 3;
_sector->logicalTrack = br.read_8();
br.read_8(); /* number of sectors per track */
_sector->logicalSector = br.read_8();
br.read_be16(); /* sector size */
uint16_t wantChecksum = br.read_be16();
_sector->logicalSide = br.read_8() >> 3; if (wantChecksum == gotChecksum)
_sector->logicalTrack = br.read_8(); _sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
br.read_8(); /* number of sectors per track */ }
_sector->logicalSector = br.read_8();
br.read_be16(); /* sector size */
uint16_t wantChecksum = br.read_be16();
if (wantChecksum == gotChecksum) void decodeDataRecord()
_sector->status = {
Sector::DATA_MISSING; /* correct but unintuitive */ auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
} auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
void decodeDataRecord() override ByteReader br(bytes);
{ uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE * 16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
ByteReader br(bytes); br.seek(2);
if (br.read_be16() != DATA_ID) _sector->data = br.read(TIDS990_PAYLOAD_SIZE);
return; uint16_t wantChecksum = br.read_be16();
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
uint16_t gotChecksum = }
crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE - 3));
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
uint16_t wantChecksum = br.read_be16();
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
}; };
std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createTids990Decoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new Tids990Decoder(config)); return std::unique_ptr<AbstractDecoder>(new Tids990Decoder(config));
} }

View File

@@ -1,150 +1,168 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/encoders/encoders.h" #include "encoders/encoders.h"
#include "tids990.h" #include "tids990.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "arch/tids990/tids990.pb.h" #include "arch/tids990/tids990.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#include "fmt/format.h" #include <fmt/format.h>
static int charToInt(char c) static int charToInt(char c)
{ {
if (isdigit(c)) if (isdigit(c))
return c - '0'; return c - '0';
return 10 + tolower(c) - 'a'; return 10 + tolower(c) - 'a';
} }
static uint8_t decodeUint16(uint16_t raw) static uint8_t decodeUint16(uint16_t raw)
{ {
Bytes b; Bytes b;
ByteWriter bw(b); ByteWriter bw(b);
bw.write_be16(raw); bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0]; return decodeFmMfm(b.toBits())[0];
} }
class Tids990Encoder : public Encoder class Tids990Encoder : public AbstractEncoder
{ {
public: public:
Tids990Encoder(const EncoderProto& config): Tids990Encoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.tids990()) _config(config.tids990())
{ {}
}
private: private:
void writeRawBits(uint32_t data, int width) void writeRawBits(uint32_t data, int width)
{ {
_cursor += width; _cursor += width;
_lastBit = data & 1; _lastBit = data & 1;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = _cursor - i - 1; unsigned pos = _cursor - i - 1;
if (pos < _bits.size()) if (pos < _bits.size())
_bits[pos] = data & 1; _bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
void writeBytes(const Bytes& bytes) void writeBytes(const Bytes& bytes)
{ {
encodeMfm(_bits, _cursor, bytes, _lastBit); encodeMfm(_bits, _cursor, bytes, _lastBit);
} }
void writeBytes(int count, uint8_t byte) void writeBytes(int count, uint8_t byte)
{ {
Bytes bytes = {byte}; Bytes bytes = { byte };
for (int i = 0; i < count; i++) for (int i=0; i<count; i++)
writeBytes(bytes); writeBytes(bytes);
} }
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
double clockRateUs = _config.clock_period_us() / 2.0;
int bitsPerRevolution =
(_config.rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t am1Unencoded = decodeUint16(_config.am1_byte()); for (char sectorChar : _config.sector_skew())
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
writeBytes(_config.gap1_bytes(), 0x55);
bool first = true;
for (const auto& sectorData : sectors)
{ {
if (!first) int sectorId = charToInt(sectorChar);
writeBytes(_config.gap3_bytes(), 0x55); const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
first = false; if (sector)
sectors.push_back(sector);
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
{
Bytes header;
ByteWriter bw(header);
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(_config.sector_count());
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeRawBits(_config.am1_byte(), 16);
writeBytes(header.slice(1));
}
writeBytes(_config.gap2_bytes(), 0x55);
{
Bytes data;
ByteWriter bw(data);
writeBytes(12, 0x55);
bw.write_8(am2Unencoded);
bw += sectorData->data;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
writeRawBits(_config.am2_byte(), 16);
writeBytes(data.slice(1));
}
} }
if (_cursor >= _bits.size()) return sectors;
error("track data overrun"); }
while (_cursor < _bits.size())
writeBytes(1, 0x55);
auto fluxmap = std::make_unique<Fluxmap>(); std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
fluxmap->appendBits(_bits, const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
calculatePhysicalClockPeriod( {
clockRateUs * 1e3, _config.rotational_period_ms() * 1e6)); double clockRateUs = 1e3 / _config.clock_rate_khz() / 2.0;
return fluxmap; int bitsPerRevolution = (_config.track_length_ms() * 1000.0) / clockRateUs;
} _bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
writeBytes(_config.gap1_bytes(), 0x55);
bool first = true;
for (char sectorChar : _config.sector_skew())
{
int sectorId = charToInt(sectorChar);
if (!first)
writeBytes(_config.gap3_bytes(), 0x55);
first = false;
const auto& sectorData = image.get(physicalTrack, physicalSide, sectorId);
if (!sectorData)
Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId);
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
{
Bytes header;
ByteWriter bw(header);
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(_config.sector_count());
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeRawBits(_config.am1_byte(), 16);
writeBytes(header.slice(1));
}
writeBytes(_config.gap2_bytes(), 0x55);
{
Bytes data;
ByteWriter bw(data);
writeBytes(12, 0x55);
bw.write_8(am2Unencoded);
bw += sectorData->data;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
writeRawBits(_config.am2_byte(), 16);
writeBytes(data.slice(1));
}
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeBytes(1, 0x55);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);
return fluxmap;
}
private: private:
const Tids990EncoderProto& _config; const Tids990EncoderProto& _config;
std::vector<bool> _bits; std::vector<bool> _bits;
unsigned _cursor; unsigned _cursor;
bool _lastBit; bool _lastBit;
}; };
std::unique_ptr<Encoder> createTids990Encoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createTids990Encoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new Tids990Encoder(config)); return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
} }

View File

@@ -1,18 +1,18 @@
#ifndef TIDS990_H #ifndef TIDS990_H
#define TIDS990_H #define TIDS990_H
#define TIDS990_PAYLOAD_SIZE 288 /* bytes */ #define TIDS990_PAYLOAD_SIZE 288 /* bytes */
#define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */ #define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */
#define TIDS990_DATA_RECORD_SIZE (TIDS990_PAYLOAD_SIZE + 4) /* bytes */ #define TIDS990_DATA_RECORD_SIZE (TIDS990_PAYLOAD_SIZE + 4) /* bytes */
class Encoder; class AbstractEncoder;
class Decoder; class AbstractDecoder;
class DecoderProto; class DecoderProto;
class EncoderProto; class EncoderProto;
extern std::unique_ptr<Decoder> createTids990Decoder( extern std::unique_ptr<AbstractDecoder> createTids990Decoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createTids990Encoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createTids990Encoder(
const EncoderProto& config);
#endif #endif

View File

@@ -1,15 +1,14 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
message Tids990DecoderProto {} message Tids990DecoderProto {}
message Tids990EncoderProto { message Tids990EncoderProto {
optional double rotational_period_ms = 1 [ default = 166, optional double track_length_ms = 1 [ default = 166,
(help) = "length of a track" ]; (help) = "length of a track" ];
optional int32 sector_count = 2 [ default = 26, optional int32 sector_count = 2 [ default = 26,
(help) = "number of sectors per track" ]; (help) = "number of sectors per track" ];
optional double clock_period_us = 3 [ default = 2, optional double clock_rate_khz = 3 [ default = 500,
(help) = "clock rate of data to write" ]; (help) = "clock rate of data to write" ];
optional int32 am1_byte = 4 [ default = 0x2244, optional int32 am1_byte = 4 [ default = 0x2244,
(help) = "16-bit RAW bit pattern to use for the AM1 ID byte" ]; (help) = "16-bit RAW bit pattern to use for the AM1 ID byte" ];
@@ -21,5 +20,7 @@ message Tids990EncoderProto {
(help) = "size of gap 2 (the post-ID gap)" ]; (help) = "size of gap 2 (the post-ID gap)" ];
optional int32 gap3_bytes = 8 [ default = 51, optional int32 gap3_bytes = 8 [ default = 51,
(help) = "size of gap 3 (the post-data or format gap)" ]; (help) = "size of gap 3 (the post-data or format gap)" ];
optional string sector_skew = 9 [ default = "1mhc72nid83oje94pkfa50lgb6",
(help) = "order to emit sectors" ];
} }

View File

@@ -1,31 +1,28 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "victor9k.h" #include "victor9k.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
const FluxPattern SECTOR_RECORD_PATTERN(32, VICTOR9K_SECTOR_RECORD); const FluxPattern SECTOR_RECORD_PATTERN(32, VICTOR9K_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(32, VICTOR9K_DATA_RECORD); const FluxPattern DATA_RECORD_PATTERN(32, VICTOR9K_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN( const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr) static int decode_data_gcr(uint8_t gcr)
{ {
switch (gcr) switch (gcr)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case gcr: \ case gcr: return data;
return data; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
@@ -40,11 +37,11 @@ static Bytes decode(const std::vector<bool>& bits)
while (ii != bits.end()) while (ii != bits.end())
{ {
uint8_t inputfifo = 0; uint8_t inputfifo = 0;
for (size_t i = 0; i < 5; i++) for (size_t i=0; i<5; i++)
{ {
if (ii == bits.end()) if (ii == bits.end())
break; break;
inputfifo = (inputfifo << 1) | *ii++; inputfifo = (inputfifo<<1) | *ii++;
} }
uint8_t decoded = decode_data_gcr(inputfifo); uint8_t decoded = decode_data_gcr(inputfifo);
@@ -55,65 +52,73 @@ static Bytes decode(const std::vector<bool>& bits)
return output; return output;
} }
class Victor9kDecoder : public Decoder class Victor9kDecoder : public AbstractDecoder
{ {
public: public:
Victor9kDecoder(const DecoderProto& config): Decoder(config) {} Victor9kDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
return seekToPattern(ANY_RECORD_PATTERN); const FluxMatcher* matcher = nullptr;
} _sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return DATA_RECORD;
return UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
/* Check the ID. */ /* Skip the sync marker bit. */
readRawBits(22);
if (readRaw32() != VICTOR9K_SECTOR_RECORD) /* Read header. */
return;
/* Read header. */ auto bytes = decode(readRawBits(4*10)).slice(0, 4);
auto bytes = decode(readRawBits(3 * 10)).slice(0, 3); uint8_t rawTrack = bytes[1];
_sector->logicalSector = bytes[2];
uint8_t gotChecksum = bytes[3];
uint8_t rawTrack = bytes[0]; _sector->logicalTrack = rawTrack & 0x7f;
_sector->logicalSector = bytes[1]; _sector->logicalSide = rawTrack >> 7;
uint8_t gotChecksum = bytes[2]; uint8_t wantChecksum = bytes[1] + bytes[2];
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1))
return;
if (wantChecksum == gotChecksum)
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
_sector->logicalTrack = rawTrack & 0x7f; void decodeDataRecord()
_sector->logicalSide = rawTrack >> 7; {
uint8_t wantChecksum = bytes[0] + bytes[1]; /* Skip the sync marker bit. */
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || readRawBits(22);
(_sector->logicalSide > 1))
return;
if (wantChecksum == gotChecksum) /* Read data. */
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord() override auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+5)*10))
{ .slice(0, VICTOR9K_SECTOR_LENGTH+5);
/* Check the ID. */ ByteReader br(bytes);
if (readRaw32() != VICTOR9K_DATA_RECORD) /* Check that this is actually a data record. */
return;
if (br.read_8() != 8)
return;
/* Read data. */ _sector->data = br.read(VICTOR9K_SECTOR_LENGTH);
uint16_t gotChecksum = sumBytes(_sector->data);
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH + 4) * 10)) uint16_t wantChecksum = br.read_le16();
.slice(0, VICTOR9K_SECTOR_LENGTH + 4); _sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
ByteReader br(bytes); }
_sector->data = br.read(VICTOR9K_SECTOR_LENGTH);
uint16_t gotChecksum = sumBytes(_sector->data);
uint16_t wantChecksum = br.read_le16();
_sector->status =
(gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
}; };
std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createVictor9kDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new Victor9kDecoder(config)); return std::unique_ptr<AbstractDecoder>(new Victor9kDecoder(config));
} }

View File

@@ -1,129 +1,109 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/core/utils.h" #include "decoders/decoders.h"
#include "lib/decoders/decoders.h" #include "encoders/encoders.h"
#include "lib/encoders/encoders.h"
#include "victor9k.h" #include "victor9k.h"
#include "lib/core/crc.h" #include "crc.h"
#include "lib/data/sector.h" #include "sector.h"
#include "lib/data/image.h" #include "writer.h"
#include "image.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "arch/victor9k/victor9k.pb.h" #include "arch/victor9k/victor9k.pb.h"
#include "lib/encoders/encoders.pb.h" #include "lib/encoders/encoders.pb.h"
#include "lib/data/layout.h"
#include <ctype.h> #include <ctype.h>
#include "lib/core/bytes.h" #include "bytes.h"
static bool lastBit; static bool lastBit;
static void write_zero_bits( static void write_zero_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
std::vector<bool>& bits, unsigned& cursor, unsigned count)
{ {
while (count--) while (count--)
{ {
if (cursor < bits.size()) if (cursor < bits.size())
lastBit = bits[cursor++] = 0; lastBit = bits[cursor++] = 0;
} }
} }
static void write_one_bits( static void write_one_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
std::vector<bool>& bits, unsigned& cursor, unsigned count)
{ {
while (count--) while (count--)
{ {
if (cursor < bits.size()) if (cursor < bits.size())
lastBit = bits[cursor++] = 1; lastBit = bits[cursor++] = 1;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{ {
for (bool bit : src) for (bool bit : src)
{ {
if (cursor < bits.size()) if (cursor < bits.size())
lastBit = bits[cursor++] = bit; lastBit = bits[cursor++] = bit;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{ {
cursor += width; cursor += width;
lastBit = data & 1; lastBit = data & 1;
for (int i = 0; i < width; i++) for (int i=0; i<width; i++)
{ {
unsigned pos = cursor - i - 1; unsigned pos = cursor - i - 1;
if (pos < bits.size()) if (pos < bits.size())
bits[pos] = data & 1; bits[pos] = data & 1;
data >>= 1; data >>= 1;
} }
} }
static void write_bits( static void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{ {
ByteReader br(bytes); ByteReader br(bytes);
BitReader bitr(br); BitReader bitr(br);
while (!bitr.eof()) while (!bitr.eof())
{ {
if (cursor < bits.size()) if (cursor < bits.size())
bits[cursor++] = bitr.get(); bits[cursor++] = bitr.get();
} }
} }
static int encode_data_gcr(uint8_t data) static int encode_data_gcr(uint8_t data)
{ {
switch (data & 0x0f) switch (data & 0x0f)
{ {
#define GCR_ENTRY(gcr, data) \ #define GCR_ENTRY(gcr, data) \
case data: \ case data: return gcr;
return gcr; #include "data_gcr.h"
#include "data_gcr.h" #undef GCR_ENTRY
#undef GCR_ENTRY
} }
return -1; return -1;
} }
static void write_byte(std::vector<bool>& bits, unsigned& cursor, uint8_t b) static void write_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{
write_bits(bits, cursor, encode_data_gcr(b >> 4), 5);
write_bits(bits, cursor, encode_data_gcr(b), 5);
}
static void write_bytes(
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{ {
for (uint8_t b : bytes) for (uint8_t b : bytes)
write_byte(bits, cursor, b); {
write_bits(bits, cursor, encode_data_gcr(b>>4), 5);
write_bits(bits, cursor, encode_data_gcr(b), 5);
}
} }
static void write_gap(std::vector<bool>& bits, unsigned& cursor, int length) static void write_sector(std::vector<bool>& bits, unsigned& cursor,
{ const Victor9kEncoderProto::TrackdataProto& trackdata,
for (int i = 0; i < length / 10; i++) const Sector& sector)
write_byte(bits, cursor, '0');
}
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const Victor9kEncoderProto::TrackdataProto& trackdata,
const Sector& sector)
{ {
write_one_bits(bits, cursor, trackdata.pre_header_sync_bits()); write_one_bits(bits, cursor, trackdata.pre_header_sync_bits());
write_bits(bits, cursor, VICTOR9K_SECTOR_RECORD, 10); write_bits(bits, cursor, VICTOR9K_SECTOR_RECORD, 10);
uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide << 7); uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide<<7);
uint8_t encodedSector = sector.logicalSector; uint8_t encodedSector = sector.logicalSector;
write_bytes(bits, write_bytes(bits, cursor, Bytes {
cursor, encodedTrack,
Bytes{ encodedSector,
encodedTrack, (uint8_t)(encodedTrack + encodedSector),
encodedSector, });
(uint8_t)(encodedTrack + encodedSector),
});
write_gap(bits, cursor, trackdata.post_header_gap_bits());
write_zero_bits(bits, cursor, trackdata.post_header_gap_bits());
write_one_bits(bits, cursor, trackdata.pre_data_sync_bits()); write_one_bits(bits, cursor, trackdata.pre_data_sync_bits());
write_bits(bits, cursor, VICTOR9K_DATA_RECORD, 10); write_bits(bits, cursor, VICTOR9K_DATA_RECORD, 10);
@@ -132,79 +112,89 @@ static void write_sector(std::vector<bool>& bits,
Bytes checksum(2); Bytes checksum(2);
checksum.writer().write_le16(sumBytes(sector.data)); checksum.writer().write_le16(sumBytes(sector.data));
write_bytes(bits, cursor, checksum); write_bytes(bits, cursor, checksum);
write_gap(bits, cursor, trackdata.post_data_gap_bits());
write_zero_bits(bits, cursor, trackdata.post_data_gap_bits());
} }
class Victor9kEncoder : public Encoder class Victor9kEncoder : public AbstractEncoder
{ {
public: public:
Victor9kEncoder(const EncoderProto& config): Victor9kEncoder(const EncoderProto& config):
Encoder(config), AbstractEncoder(config),
_config(config.victor9k()) _config(config.victor9k())
{ {}
}
private: private:
void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata,
unsigned track,
unsigned head)
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_min_track() && (track < f.min_track()))
continue;
if (f.has_max_track() && (track > f.max_track()))
continue;
if (f.has_head() && (head != f.head()))
continue;
trackdata.MergeFrom(f); void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
} {
} trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_min_cylinder() && (cylinder < f.min_cylinder()))
continue;
if (f.has_max_cylinder() && (cylinder > f.max_cylinder()))
continue;
if (f.has_head() && (head != f.head()))
continue;
trackdata.MergeFrom(f);
}
}
public: public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
const std::vector<std::shared_ptr<const Sector>>& sectors, {
const Image& image) override std::vector<std::shared_ptr<Sector>> sectors;
{
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) / Victor9kEncoderProto::TrackdataProto trackdata;
trackdata.clock_period_us(); getTrackFormat(trackdata, physicalTrack, physicalSide);
for (int i = 0; i < trackdata.sector_range().sector_count(); i++)
{
int sectorId = trackdata.sector_range().start_sector() + i;
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, physicalTrack, physicalSide);
unsigned bitsPerRevolution = trackdata.original_data_rate_khz() * trackdata.original_period_ms();
std::vector<bool> bits(bitsPerRevolution); std::vector<bool> bits(bitsPerRevolution);
nanoseconds_t clockPeriod = double clockRateUs = 166666.0 / bitsPerRevolution;
calculatePhysicalClockPeriod(trackdata.clock_period_us() * 1e3,
trackdata.rotational_period_ms() * 1e6);
unsigned cursor = 0; unsigned cursor = 0;
fillBitmapTo(bits, fillBitmapTo(bits, cursor, trackdata.post_index_gap_us() / clockRateUs, { true, false });
cursor,
trackdata.post_index_gap_us() * 1e3 / clockPeriod,
{true, false});
lastBit = false; lastBit = false;
for (const auto& sector : sectors) for (const auto& sector : sectors)
write_sector(bits, cursor, trackdata, *sector); write_sector(bits, cursor, trackdata, *sector);
if (cursor >= bits.size()) if (cursor >= bits.size())
error("track data overrun by {} bits", cursor - bits.size()); Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false}); fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockPeriod); fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap; return fluxmap;
} }
private: private:
const Victor9kEncoderProto& _config; const Victor9kEncoderProto& _config;
}; };
std::unique_ptr<Encoder> createVictor9kEncoder(const EncoderProto& config) std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config)
{ {
return std::unique_ptr<Encoder>(new Victor9kEncoder(config)); return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
} }
// vim: sw=4 ts=4 et // vim: sw=4 ts=4 et

View File

@@ -1,26 +1,22 @@
#ifndef VICTOR9K_H #ifndef VICTOR9K_H
#define VICTOR9K_H #define VICTOR9K_H
class Encoder; class AbstractEncoder;
class Decoder; class AbstractDecoder;
class EncoderProto; class EncoderProto;
class DecoderProto; class DecoderProto;
/* ... 1101 0101 0111 /* ... 1101 0101 0111
* ^^ ^^^^ ^^^^ ten bit IO byte */ * ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_SECTOR_RECORD 0xfffffd57 #define VICTOR9K_SECTOR_RECORD 0xfffffd57
#define VICTOR9K_HEADER_ID 0x7
/* ... 1101 0100 1001 /* ... 1101 0100 1001
* ^^ ^^^^ ^^^^ ten bit IO byte */ * ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_DATA_RECORD 0xfffffd49 #define VICTOR9K_DATA_RECORD 0xfffffd49
#define VICTOR9K_DATA_ID 0x8
#define VICTOR9K_SECTOR_LENGTH 512 #define VICTOR9K_SECTOR_LENGTH 512
extern std::unique_ptr<Decoder> createVictor9kDecoder( extern std::unique_ptr<AbstractDecoder> createVictor9kDecoder(const DecoderProto& config);
const DecoderProto& config); extern std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config);
extern std::unique_ptr<Encoder> createVictor9kEncoder(
const EncoderProto& config);
#endif #endif

View File

@@ -1,36 +1,32 @@
syntax = "proto2"; syntax = "proto2";
import "lib/config/common.proto"; import "lib/common.proto";
message Victor9kDecoderProto {} message Victor9kDecoderProto {}
// NEXT: 12 // NEXT: 12
message Victor9kEncoderProto message Victor9kEncoderProto {
{ message TrackdataProto {
message TrackdataProto message SectorRangeProto {
{ optional int32 start_sector = 1 [(help) = "first sector ID on track"];
optional int32 min_track = 1 optional int32 sector_count = 2 [(help) = "number of sectors on track"];
[ (help) = "minimum track this format applies to" ]; }
optional int32 max_track = 2
[ (help) = "maximum track this format applies to" ];
optional int32 head = 3
[ (help) = "which head this format applies to" ];
optional double rotational_period_ms = 4 optional int32 min_cylinder = 1 [(help) = "minimum cylinder this format applies to"];
[ (help) = "original rotational period of this track" ]; optional int32 max_cylinder = 2 [(help) = "maximum cylinder this format applies to"];
optional double clock_period_us = 5 optional int32 head = 3 [(help) = "which head this format applies to"];
[ (help) = "original data rate of this track" ];
optional double post_index_gap_us = 6
[ (help) = "size of post-index gap" ];
optional int32 pre_header_sync_bits = 10
[ (help) = "number of sync bits before the sector header" ];
optional int32 pre_data_sync_bits = 8
[ (help) = "number of sync bits before the sector data" ];
optional int32 post_data_gap_bits = 9
[ (help) = "size of gap between data and the next header" ];
optional int32 post_header_gap_bits = 11
[ (help) = "size of gap between header and the data" ];
}
repeated TrackdataProto trackdata = 1; optional double original_period_ms = 4 [(help) = "original rotational period of this cylinder"];
optional double original_data_rate_khz = 5 [(help) = "original data rate of this cylinder"];
optional double post_index_gap_us = 6 [(help) = "size of post-index gap"];
optional int32 pre_header_sync_bits = 10 [(help) = "number of sync bits before the sector header"];
optional int32 pre_data_sync_bits = 8 [(help) = "number of sync bits before the sector data"];
optional int32 post_data_gap_bits = 9 [(help) = "size of gap between data and the next header"];
optional int32 post_header_gap_bits = 11 [(help) = "size of gap between header and the data"];
optional SectorRangeProto sector_range = 7 [(help) = "write these sectors on each track"];
}
repeated TrackdataProto trackdata = 1;
} }

View File

@@ -1,56 +1,61 @@
#include "lib/core/globals.h" #include "globals.h"
#include "lib/data/fluxmap.h" #include "fluxmap.h"
#include "lib/data/fluxmapreader.h" #include "decoders/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "protocol.h" #include "protocol.h"
#include "lib/decoders/decoders.h" #include "decoders/decoders.h"
#include "lib/data/sector.h" #include "sector.h"
#include "zilogmcz.h" #include "zilogmcz.h"
#include "lib/core/bytes.h" #include "bytes.h"
#include "lib/core/crc.h" #include "crc.h"
#include "fmt/format.h" #include "fmt/format.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab); static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab);
class ZilogMczDecoder : public Decoder class ZilogMczDecoder : public AbstractDecoder
{ {
public: public:
ZilogMczDecoder(const DecoderProto& config): Decoder(config) {} ZilogMczDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override RecordType advanceToNextRecord()
{ {
seekToIndexMark(); const FluxMatcher* matcher = nullptr;
return seekToPattern(SECTOR_START_PATTERN); _fmr->seekToIndexMark();
} _sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
if (matcher == &SECTOR_START_PATTERN)
return SECTOR_RECORD;
return UNKNOWN_RECORD;
}
void decodeSectorRecord() override void decodeSectorRecord()
{ {
readRawBits(14); readRawBits(14);
auto rawbits = readRawBits(140 * 16); auto rawbits = readRawBits(140*16);
auto bytes = decodeFmMfm(rawbits).slice(0, 140); auto bytes = decodeFmMfm(rawbits).slice(0, 140);
ByteReader br(bytes); ByteReader br(bytes);
_sector->logicalSector = br.read_8() & 0x1f; _sector->logicalSector = br.read_8() & 0x1f;
_sector->logicalSide = 0; _sector->logicalSide = 0;
_sector->logicalTrack = br.read_8() & 0x7f; _sector->logicalTrack = br.read_8() & 0x7f;
if (_sector->logicalSector > 31) if (_sector->logicalSector > 31)
return; return;
if (_sector->logicalTrack > 80) if (_sector->logicalTrack > 80)
return; return;
_sector->data = br.read(132); _sector->data = br.read(132);
uint16_t wantChecksum = br.read_be16(); uint16_t wantChecksum = br.read_be16();
uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134)); uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134));
_sector->status = _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; }
}
}; };
std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config) std::unique_ptr<AbstractDecoder> createZilogMczDecoder(const DecoderProto& config)
{ {
return std::unique_ptr<Decoder>(new ZilogMczDecoder(config)); return std::unique_ptr<AbstractDecoder>(new ZilogMczDecoder(config));
} }

View File

@@ -1,7 +1,8 @@
#ifndef ZILOGMCZ_H #ifndef ZILOGMCZ_H
#define ZILOGMCZ_H #define ZILOGMCZ_H
extern std::unique_ptr<Decoder> createZilogMczDecoder( extern std::unique_ptr<AbstractDecoder> createZilogMczDecoder(const DecoderProto& config);
const DecoderProto& config);
#endif #endif

108
build.py
View File

@@ -1,108 +0,0 @@
from build.ab import export
from build.c import clibrary, cxxlibrary
from build.protobuf import proto, protocc
from build.pkg import package
from build.utils import test
from glob import glob
import config
import re
package(name="protobuf_lib", package="protobuf")
package(name="z_lib", package="zlib")
package(name="fmt_lib", package="fmt", fallback="dep/fmt")
package(name="sqlite3_lib", package="sqlite3")
clibrary(name="protocol", hdrs={"protocol.h": "./protocol.h"})
corpustests = []
if not glob("../fluxengine-testdata/data"):
print("fluxengine-testdata not found; skipping corpus tests")
else:
corpus = [
("acorndfs", "", "--200"),
("agat", "", ""),
("amiga", "", ""),
("apple2", "", "--140 40track_drive"),
("atarist", "", "--360"),
("atarist", "", "--370"),
("atarist", "", "--400"),
("atarist", "", "--410"),
("atarist", "", "--720"),
("atarist", "", "--740"),
("atarist", "", "--800"),
("atarist", "", "--820"),
("bk", "", ""),
("brother", "", "--120 40track_drive"),
("brother", "", "--240"),
(
"commodore",
"scripts/commodore1541_test.textpb",
"--171 40track_drive",
),
(
"commodore",
"scripts/commodore1541_test.textpb",
"--192 40track_drive",
),
("commodore", "", "--800"),
("commodore", "", "--1620"),
("hplif", "", "--264"),
("hplif", "", "--608"),
("hplif", "", "--616"),
("hplif", "", "--770"),
("ibm", "", "--1200"),
("ibm", "", "--1232"),
("ibm", "", "--1440"),
("ibm", "", "--1680"),
("ibm", "", "--180 40track_drive"),
("ibm", "", "--160 40track_drive"),
("ibm", "", "--320 40track_drive"),
("ibm", "", "--360 40track_drive"),
("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", "", "--780"),
("tids990", "", ""),
("victor9k", "", "--612"),
("victor9k", "", "--1224"),
]
for c in corpus:
name = re.sub(r"[^a-zA-Z0-9]", "_", "".join(c), 0)
corpustests += [
test(
name=f"corpustest_{name}_{format}",
ins=["src+fluxengine"],
deps=["scripts/encodedecodetest.sh"],
commands=[
"{deps[0]} "
+ c[0]
+ " "
+ format
+ " {ins[0]} '"
+ c[1]
+ "' '"
+ c[2]
+ "' $(dir {outs[0]}) > /dev/null"
],
label="CORPUSTEST",
)
for format in ["scp", "flux"]
]
export(
name="all",
items={
"fluxengine$(EXT)": "src+fluxengine",
"fluxengine-gui$(EXT)": "src/gui",
"brother120tool$(EXT)": "tools+brother120tool",
"brother240tool$(EXT)": "tools+brother240tool",
"upgrade-flux-file$(EXT)": "tools+upgrade-flux-file",
}
| ({"FluxEngine.pkg": "src/gui+fluxengine_pkg"} if config.osx else {}),
deps=["tests", "src/formats+docs", "scripts+mkdocindex"] + corpustests,
)

View File

@@ -1,19 +0,0 @@
import sys
from functools import partial
if len(sys.argv) != 3:
sys.exit("Usage: %s <file> <symbol>" % sys.argv[0])
filename = sys.argv[1]
symbol = sys.argv[2]
print("const uint8_t " + symbol + "[] = {")
n = 0
with open(filename, "rb") as in_file:
for c in iter(partial(in_file.read, 1), b""):
print("0x%02X," % ord(c), end="")
n += 1
if n % 16 == 0:
print()
print("};")
print("const size_t " + symbol + "_len = sizeof(" + symbol + ");")

View File

@@ -1,95 +0,0 @@
MAKENOT4 := $(if $(findstring 3.9999, $(lastword $(sort 3.9999 $(MAKE_VERSION)))),yes,no)
MAKE4.3 := $(if $(findstring 4.3, $(firstword $(sort 4.3 $(MAKE_VERSION)))),yes,no)
MAKE4.1 := $(if $(findstring no_no,$(MAKENOT4)_$(MAKE4.3)),yes,no)
ifeq ($(MAKENOT3),yes)
$(error You need GNU Make 4.x for this (if you're on OSX, use gmake).)
endif
OBJ ?= .obj
PYTHON ?= python3
CC ?= gcc
CXX ?= g++
AR ?= ar
CFLAGS ?= -g -Og
LDFLAGS ?= -g
PKG_CONFIG ?= pkg-config
HOST_PKG_CONFIG ?= $(PKG_CONFIG)
ECHO ?= echo
CP ?= cp
export PKG_CONFIG
export HOST_PKG_CONFIG
ifdef VERBOSE
hide =
else
ifdef V
hide =
else
hide = @
endif
endif
WINDOWS := no
OSX := no
LINUX := no
ifeq ($(OS),Windows_NT)
WINDOWS := yes
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
LINUX := yes
endif
ifeq ($(UNAME_S),Darwin)
OSX := yes
endif
endif
ifeq ($(OS), Windows_NT)
EXT ?= .exe
endif
EXT ?=
ifeq ($(PROGRESSINFO),)
# The first make invocation here has to have its output discarded or else it
# produces spurious 'Leaving directory' messages... don't know why.
rulecount := $(strip $(shell $(MAKE) --no-print-directory -q $(OBJ)/build.mk PROGRESSINFO=1 > /dev/null \
&& $(MAKE) --no-print-directory -n $(MAKECMDGOALS) PROGRESSINFO=XXXPROGRESSINFOXXX | grep XXXPROGRESSINFOXXX | wc -l))
ruleindex := 1
PROGRESSINFO = "[$(ruleindex)/$(rulecount)]$(eval ruleindex := $(shell expr $(ruleindex) + 1))"
endif
PKG_CONFIG_HASHES = $(OBJ)/.pkg-config-hashes/target-$(word 1, $(shell $(PKG_CONFIG) --list-all | md5sum))
HOST_PKG_CONFIG_HASHES = $(OBJ)/.pkg-config-hashes/host-$(word 1, $(shell $(HOST_PKG_CONFIG) --list-all | md5sum))
$(OBJ)/build.mk : $(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES)
$(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES) &:
$(hide) rm -rf $(OBJ)/.pkg-config-hashes
$(hide) mkdir -p $(OBJ)/.pkg-config-hashes
$(hide) touch $(PKG_CONFIG_HASHES) $(HOST_PKG_CONFIG_HASHES)
include $(OBJ)/build.mk
MAKEFLAGS += -r -j$(shell nproc)
.DELETE_ON_ERROR:
.PHONY: update-ab
update-ab:
@echo "Press RETURN to update ab from the repository, or CTRL+C to cancel." \
&& read a \
&& (curl -L https://github.com/davidgiven/ab/releases/download/dev/distribution.tar.xz | tar xvJf -) \
&& echo "Done."
.PHONY: clean
clean::
@echo CLEAN
$(hide) rm -rf $(OBJ)
export PYTHONHASHSEED = 1
build-files = $(shell find . -name 'build.py') $(wildcard build/*.py) $(wildcard config.py)
$(OBJ)/build.mk: Makefile $(build-files) build/ab.mk
@echo "AB"
@mkdir -p $(OBJ)
$(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py -o $@ build.py \
|| rm -f $@

View File

@@ -1,557 +0,0 @@
from os.path import *
from pathlib import Path
from typing import Iterable
import argparse
import builtins
from copy import copy
import functools
import importlib
import importlib.abc
import importlib.util
from importlib.machinery import (
SourceFileLoader,
PathFinder,
ModuleSpec,
)
import inspect
import string
import sys
import hashlib
verbose = False
quiet = False
cwdStack = [""]
targets = {}
unmaterialisedTargets = {} # dict, not set, to get consistent ordering
materialisingStack = []
defaultGlobals = {}
sys.path += ["."]
old_import = builtins.__import__
class PathFinderImpl(PathFinder):
def find_spec(self, fullname, path, target=None):
# The second test here is needed for Python 3.9.
if not path or not path[0]:
path = ["."]
if len(path) != 1:
return None
try:
path = relpath(path[0])
except ValueError:
return None
realpath = fullname.replace(".", "/")
buildpath = realpath + ".py"
if isfile(buildpath):
spec = importlib.util.spec_from_file_location(
name=fullname,
location=buildpath,
loader=BuildFileLoaderImpl(fullname=fullname, path=buildpath),
submodule_search_locations=[],
)
return spec
if isdir(realpath):
return ModuleSpec(fullname, None, origin=realpath, is_package=True)
return None
class BuildFileLoaderImpl(SourceFileLoader):
def exec_module(self, module):
sourcepath = relpath(module.__file__)
if not quiet:
print("loading", sourcepath)
cwdStack.append(dirname(sourcepath))
super(SourceFileLoader, self).exec_module(module)
cwdStack.pop()
sys.meta_path.insert(0, PathFinderImpl())
class ABException(BaseException):
pass
def error(message):
raise ABException(message)
def Rule(func):
sig = inspect.signature(func)
@functools.wraps(func)
def wrapper(*, name=None, replaces=None, **kwargs):
cwd = None
if "cwd" in kwargs:
cwd = kwargs["cwd"]
del kwargs["cwd"]
if not cwd:
if replaces:
cwd = replaces.cwd
else:
cwd = cwdStack[-1]
if name:
if name[0] != "+":
name = "+" + name
t = Target(cwd, join(cwd, name))
assert (
t.name not in targets
), f"target {t.name} has already been defined"
targets[t.name] = t
elif replaces:
t = replaces
else:
raise ABException("you must supply either 'name' or 'replaces'")
t.cwd = cwd
t.types = func.__annotations__
t.callback = func
t.traits.add(func.__name__)
if "args" in kwargs:
t.args |= kwargs["args"]
del kwargs["args"]
if "traits" in kwargs:
t.traits |= kwargs["traits"]
del kwargs["traits"]
t.binding = sig.bind(name=name, self=t, **kwargs)
t.binding.apply_defaults()
unmaterialisedTargets[t] = None
if replaces:
t.materialise(replacing=True)
return t
defaultGlobals[func.__name__] = wrapper
return wrapper
def _isiterable(xs):
return isinstance(xs, Iterable) and not isinstance(
xs, (str, bytes, bytearray)
)
class Target:
def __init__(self, cwd, name):
if verbose:
print("rule('%s', cwd='%s'" % (name, cwd))
self.name = name
self.localname = self.name.rsplit("+")[-1]
self.traits = set()
self.dir = join("$(OBJ)", name)
self.ins = []
self.outs = []
self.deps = []
self.materialised = False
self.args = {}
def __eq__(self, other):
return self.name is other.name
def __lt__(self, other):
return self.name < other.name
def __hash__(self):
return id(self)
def __repr__(self):
return f"Target('{self.name}')"
def templateexpand(selfi, s):
class Formatter(string.Formatter):
def get_field(self, name, a1, a2):
return (
eval(name, selfi.callback.__globals__, selfi.args),
False,
)
def format_field(self, value, format_spec):
if not value:
return ""
if type(value) == str:
return value
if _isiterable(value):
value = list(value)
if type(value) != list:
value = [value]
return " ".join(
[selfi.templateexpand(f) for f in filenamesof(value)]
)
return Formatter().format(s)
def materialise(self, replacing=False):
if self not in unmaterialisedTargets:
return
if not replacing and self in materialisingStack:
print("Found dependency cycle:")
for i in materialisingStack:
print(f" {i.name}")
print(f" {self.name}")
sys.exit(1)
materialisingStack.append(self)
# Perform type conversion to the declared rule parameter types.
try:
for k, v in self.binding.arguments.items():
if k != "kwargs":
t = self.types.get(k, None)
if t:
v = t.convert(v, self)
self.args[k] = copy(v)
else:
for kk, vv in v.items():
t = self.types.get(kk, None)
if t:
vv = t.convert(v, self)
self.args[kk] = copy(vv)
self.args["name"] = self.name
self.args["dir"] = self.dir
self.args["self"] = self
# Actually call the callback.
cwdStack.append(self.cwd)
if "kwargs" in self.binding.arguments.keys():
# If the caller wants kwargs, return all arguments except the standard ones.
cbargs = {
k: v for k, v in self.args.items() if k not in {"dir"}
}
else:
# Otherwise, just call the callback with the ones it asks for.
cbargs = {}
for k in self.binding.arguments.keys():
if k != "kwargs":
try:
cbargs[k] = self.args[k]
except KeyError:
error(
f"invocation of {self} failed because {k} isn't an argument"
)
self.callback(**cbargs)
cwdStack.pop()
except BaseException as e:
print(f"Error materialising {self}: {self.callback}")
print(f"Arguments: {self.args}")
raise e
if self.outs is None:
raise ABException(f"{self.name} didn't set self.outs")
if self in unmaterialisedTargets:
del unmaterialisedTargets[self]
materialisingStack.pop()
self.materialised = True
def convert(value, target):
if not value:
return None
return target.targetof(value)
def targetof(self, value):
if isinstance(value, str) and (value[0] == "="):
value = join(self.dir, value[1:])
return targetof(value, self.cwd)
def _filetarget(value, cwd):
if value in targets:
return targets[value]
t = Target(cwd, value)
t.outs = [value]
targets[value] = t
return t
def targetof(value, cwd=None):
if not cwd:
cwd = cwdStack[-1]
if isinstance(value, Path):
value = value.as_posix()
if isinstance(value, Target):
t = value
else:
assert (
value[0] != "="
), "can only use = for targets associated with another target"
if value.startswith("."):
# Check for local rule.
if value.startswith(".+"):
value = normpath(join(cwd, value[1:]))
# Check for local path.
elif value.startswith("./"):
value = normpath(join(cwd, value))
# Explicit directories are always raw files.
elif value.endswith("/"):
return _filetarget(value, cwd)
# Anything starting with a variable expansion is always a raw file.
elif value.startswith("$"):
return _filetarget(value, cwd)
# If this is not a rule lookup...
if "+" not in value:
# ...and if the value is pointing at a directory without a trailing /,
# it's a shorthand rule lookup.
if isdir(value):
value = value + "+" + basename(value)
# Otherwise it's an absolute file.
else:
return _filetarget(value, cwd)
# At this point we have the fully qualified name of a rule.
(path, target) = value.rsplit("+", 1)
value = join(path, "+" + target)
if value not in targets:
# Load the new build file.
path = join(path, "build.py")
try:
loadbuildfile(path)
except ModuleNotFoundError:
error(
f"no such build file '{path}' while trying to resolve '{value}'"
)
assert (
value in targets
), f"build file at '{path}' doesn't contain '+{target}' when trying to resolve '{value}'"
t = targets[value]
t.materialise()
return t
class Targets:
def convert(value, target):
if not value:
return []
assert _isiterable(value), "cannot convert non-list to Targets"
return [target.targetof(x) for x in flatten(value)]
class TargetsMap:
def convert(value, target):
if not value:
return {}
output = {k: target.targetof(v) for k, v in value.items()}
for k, v in output.items():
assert (
len(filenamesof([v])) == 1
), f"targets of a TargetsMap used as an argument of {target} with key '{k}' must contain precisely one output file, but was {filenamesof([v])}"
return output
def loadbuildfile(filename):
filename = filename.replace("/", ".").removesuffix(".py")
builtins.__import__(filename)
def flatten(items):
def generate(xs):
for x in xs:
if _isiterable(x):
yield from generate(x)
else:
yield x
return list(generate(items))
def targetnamesof(items):
assert _isiterable(items), "argument of filenamesof is not a collection"
return [t.name for t in items]
def filenamesof(items):
assert _isiterable(items), "argument of filenamesof is not a collection"
def generate(xs):
for x in xs:
if isinstance(x, Target):
yield from generate(x.outs)
else:
yield x
return list(generate(items))
def filenameof(x):
xs = filenamesof(x.outs)
assert (
len(xs) == 1
), f"tried to use filenameof() on {x} which does not have exactly one output: {x.outs}"
return xs[0]
def emit(*args, into=None):
s = " ".join(args) + "\n"
if into is not None:
into += [s]
else:
outputFp.write(s)
def emit_rule(name, ins, outs, cmds=[], label=None):
fins = filenamesof(ins)
fouts = filenamesof(outs)
nonobjs = [f for f in fouts if not f.startswith("$(OBJ)")]
emit("")
lines = []
if nonobjs:
emit("clean::", into=lines)
emit("\t$(hide) rm -f", *nonobjs, into=lines)
emit(".PHONY:", name, into=lines)
if outs:
emit(name, ":", *fouts, into=lines)
if len(fouts) == 1:
emit(*fouts, ":", *fins, "\x01", into=lines)
else:
emit("ifeq ($(MAKE4.3),yes)", into=lines)
emit(*fouts, "&:", *fins, "\x01", into=lines)
emit("else", into=lines)
emit(*(fouts[1:]), ":", fouts[0], into=lines)
emit(fouts[0], ":", *fins, "\x01", into=lines)
emit("endif", into=lines)
if label:
emit("\t$(hide)", "$(ECHO) $(PROGRESSINFO)", label, into=lines)
for c in cmds:
emit("\t$(hide)", c, into=lines)
else:
assert len(cmds) == 0, "rules with no outputs cannot have commands"
emit(name, ":", *fins, into=lines)
cmd = "".join(lines)
hash = hashlib.sha1(bytes(cmd, "utf-8")).hexdigest()
outputFp.write(cmd.replace("\x01", f"$(OBJ)/.hashes/{hash}"))
if outs:
emit(f"$(OBJ)/.hashes/{hash}:")
emit(
f"\t$(hide) mkdir -p $(OBJ)/.hashes && touch $(OBJ)/.hashes/{hash}"
)
emit("")
@Rule
def simplerule(
self,
name,
ins: Targets = [],
outs: Targets = [],
deps: Targets = [],
commands=[],
label="RULE",
):
self.ins = ins
self.outs = outs
self.deps = deps
dirs = []
cs = []
for out in filenamesof(outs):
dir = dirname(out)
if dir and dir not in dirs:
dirs += [dir]
cs = [("mkdir -p %s" % dir) for dir in dirs]
for c in commands:
cs += [self.templateexpand(c)]
emit_rule(
name=self.name,
ins=ins + deps,
outs=outs,
label=self.templateexpand("{label} {name}") if label else None,
cmds=cs,
)
@Rule
def export(self, name=None, items: TargetsMap = {}, deps: Targets = []):
ins = []
outs = []
for dest, src in items.items():
dest = self.targetof(dest)
outs += [dest]
destf = filenameof(dest)
srcs = filenamesof([src])
assert (
len(srcs) == 1
), "a dependency of an exported file must have exactly one output file"
subrule = simplerule(
name=f"{self.localname}/{destf}",
cwd=self.cwd,
ins=[srcs[0]],
outs=[destf],
commands=["$(CP) %s %s" % (srcs[0], destf)],
label="",
)
subrule.materialise()
simplerule(
replaces=self,
ins=outs + deps,
outs=["=sentinel"],
commands=["touch {outs[0]}"],
label="EXPORT",
)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("-o", "--output")
parser.add_argument("files", nargs="+")
args = parser.parse_args()
global verbose
verbose = args.verbose
global quiet
quiet = args.quiet
global outputFp
outputFp = open(args.output, "wt")
for k in ["Rule"]:
defaultGlobals[k] = globals()[k]
global __name__
sys.modules["build.ab"] = sys.modules[__name__]
__name__ = "build.ab"
for f in args.files:
loadbuildfile(f)
while unmaterialisedTargets:
t = next(iter(unmaterialisedTargets))
t.materialise()
emit("AB_LOADED = 1\n")
main()

View File

@@ -1,335 +0,0 @@
from build.ab import (
Rule,
Targets,
TargetsMap,
filenameof,
filenamesof,
flatten,
simplerule,
emit,
)
from build.utils import filenamesmatchingof, stripext, collectattrs
from os.path import *
emit(
"""
ifeq ($(OSX),no)
STARTGROUP ?= -Wl,--start-group
ENDGROUP ?= -Wl,--end-group
endif
"""
)
def _combine(list1, list2):
r = list(list1)
for i in list2:
if i not in r:
r.append(i)
return r
def _indirect(deps, name):
r = []
for d in deps:
r = _combine(r, d.args.get(name, [d]))
return r
def cfileimpl(self, name, srcs, deps, suffix, commands, label, cflags):
outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix
hdr_deps = _indirect(deps, "cheader_deps")
cflags = collectattrs(
targets=hdr_deps, name="caller_cflags", initial=cflags
)
t = simplerule(
replaces=self,
ins=srcs,
deps=sorted(_indirect(hdr_deps, "cheader_files")),
outs=[outleaf],
label=label,
commands=commands,
args={"cflags": cflags},
)
@Rule
def cfile(
self,
name,
srcs: Targets = None,
deps: Targets = None,
cflags=[],
suffix=".o",
commands=["$(CC) -c -o {outs[0]} {ins[0]} $(CFLAGS) {cflags}"],
label="CC",
):
cfileimpl(self, name, srcs, deps, suffix, commands, label, cflags)
@Rule
def cxxfile(
self,
name,
srcs: Targets = None,
deps: Targets = None,
cflags=[],
suffix=".o",
commands=["$(CXX) -c -o {outs[0]} {ins[0]} $(CFLAGS) {cflags}"],
label="CXX",
):
cfileimpl(self, name, srcs, deps, suffix, commands, label, cflags)
def findsources(name, srcs, deps, cflags, filerule, cwd):
for f in filenamesof(srcs):
if f.endswith(".h") or f.endswith(".hh"):
cflags = cflags + [f"-I{dirname(f)}"]
objs = []
for s in flatten(srcs):
objs += [
filerule(
name=join(name, f.removeprefix("$(OBJ)/")),
srcs=[f],
deps=deps,
cflags=sorted(set(cflags)),
cwd=cwd,
)
for f in filenamesof([s])
if f.endswith(".c")
or f.endswith(".cc")
or f.endswith(".cpp")
or f.endswith(".S")
or f.endswith(".s")
]
if any(f.endswith(".o") for f in filenamesof([s])):
objs += [s]
return objs
def libraryimpl(
self,
name,
srcs,
deps,
hdrs,
caller_cflags,
caller_ldflags,
cflags,
ldflags,
commands,
label,
filerule,
):
hdr_deps = _combine(_indirect(deps, "cheader_deps"), [self])
lib_deps = _combine(_indirect(deps, "clibrary_deps"), [self])
hr = None
hf = []
ar = None
if hdrs:
cs = []
ins = hdrs.values()
outs = []
i = 0
for dest, src in hdrs.items():
s = filenamesof([src])
assert (
len(s) == 1
), "the target of a header must return exactly one file"
cs += ["$(CP) {ins[" + str(i) + "]} {outs[" + str(i) + "]}"]
outs += ["=" + dest]
i = i + 1
hr = simplerule(
name=f"{self.localname}_hdr",
ins=ins,
outs=outs,
commands=cs,
label="CHEADERS",
)
hr.materialise()
hf = [f"-I{hr.dir}"]
if srcs:
objs = findsources(
self.localname,
srcs,
deps + ([hr] if hr else []),
cflags + hf,
filerule,
self.cwd,
)
ar = simplerule(
name=f"{self.localname}_lib",
ins=objs,
outs=[f"={self.localname}.a"],
label=label,
commands=commands,
)
ar.materialise()
self.outs = ([hr] if hr else []) + ([ar] if ar else [])
self.deps = self.outs
self.args["cheader_deps"] = hdr_deps
self.args["clibrary_deps"] = lib_deps
self.args["cheader_files"] = [hr] if hr else []
self.args["clibrary_files"] = [ar] if ar else []
self.args["caller_cflags"] = caller_cflags + hf
self.args["caller_ldflags"] = caller_ldflags
@Rule
def clibrary(
self,
name,
srcs: Targets = None,
deps: Targets = None,
hdrs: TargetsMap = None,
caller_cflags=[],
caller_ldflags=[],
cflags=[],
ldflags=[],
commands=["rm -f {outs[0]} && $(AR) cqs {outs[0]} {ins}"],
label="LIB",
cfilerule=cfile,
):
libraryimpl(
self,
name,
srcs,
deps,
hdrs,
caller_cflags,
caller_ldflags,
cflags,
ldflags,
commands,
label,
cfilerule,
)
@Rule
def cxxlibrary(
self,
name,
srcs: Targets = None,
deps: Targets = None,
hdrs: TargetsMap = None,
caller_cflags=[],
caller_ldflags=[],
cflags=[],
ldflags=[],
commands=["rm -f {outs[0]} && $(AR) cqs {outs[0]} {ins}"],
label="CXXLIB",
cxxfilerule=cxxfile,
):
libraryimpl(
self,
name,
srcs,
deps,
hdrs,
caller_cflags,
caller_ldflags,
cflags,
ldflags,
commands,
label,
cxxfilerule,
)
def programimpl(
self,
name,
srcs,
deps,
cflags,
ldflags,
commands,
label,
filerule,
):
cfiles = findsources(self.localname, srcs, deps, cflags, filerule, self.cwd)
lib_deps = []
for d in deps:
lib_deps = _combine(lib_deps, d.args.get("clibrary_deps", {d}))
libs = filenamesmatchingof(lib_deps, "*.a")
ldflags = collectattrs(
targets=lib_deps, name="caller_ldflags", initial=ldflags
)
simplerule(
replaces=self,
ins=cfiles + libs,
outs=[f"={self.localname}$(EXT)"],
deps=_indirect(lib_deps, "clibrary_files"),
label=label,
commands=commands,
args={
"ldflags": collectattrs(
targets=lib_deps, name="caller_ldflags", initial=ldflags
)
},
)
@Rule
def cprogram(
self,
name,
srcs: Targets = None,
deps: Targets = None,
cflags=[],
ldflags=[],
commands=[
"$(CC) -o {outs[0]} $(STARTGROUP) {ins} {ldflags} $(LDFLAGS) $(ENDGROUP)"
],
label="CLINK",
cfilerule=cfile,
):
programimpl(
self,
name,
srcs,
deps,
cflags,
ldflags,
commands,
label,
cfilerule,
)
@Rule
def cxxprogram(
self,
name,
srcs: Targets = None,
deps: Targets = None,
cflags=[],
ldflags=[],
commands=[
"$(CXX) -o {outs[0]} $(STARTGROUP) {ins} {ldflags} $(LDFLAGS) $(ENDGROUP)"
],
label="CXXLINK",
cxxfilerule=cxxfile,
):
programimpl(
self,
name,
srcs,
deps,
cflags,
ldflags,
commands,
label,
cxxfilerule,
)

View File

@@ -1,71 +0,0 @@
from build.ab import Rule, emit, Target, filenamesof
from types import SimpleNamespace
import os
import subprocess
class _PkgConfig:
package_present = set()
package_properties = {}
pkgconfig = None
def __init__(self, cmd):
assert cmd, "no pkg-config environment variable supplied"
self.pkgconfig = cmd
r = subprocess.run(f"{cmd} --list-all", shell=True, capture_output=True)
ps = r.stdout.decode("utf-8")
self.package_present = {l.split(" ", 1)[0] for l in ps.splitlines()}
def has_package(self, name):
return name in self.package_present
def get_property(self, name, flag):
p = f"{name}.{flag}"
if p not in self.package_properties:
r = subprocess.run(
f"{self.pkgconfig} {flag} {name}",
shell=True,
capture_output=True,
)
self.package_properties[p] = r.stdout.decode("utf-8").strip()
return self.package_properties[p]
TargetPkgConfig = _PkgConfig(os.getenv("PKG_CONFIG"))
def _package(self, name, package, fallback, pkgconfig):
if pkgconfig.has_package(package):
cflags = pkgconfig.get_property(package, "--cflags")
ldflags = pkgconfig.get_property(package, "--libs")
if cflags:
self.args["caller_cflags"] = [cflags]
if ldflags:
self.args["caller_ldflags"] = [ldflags]
self.traits.add("clibrary")
self.traits.add("cheaders")
return
assert (
fallback
), f"Required package '{package}' not installed when materialising target '{name}'"
if "cheader_deps" in fallback.args:
self.args["cheader_deps"] = fallback.args["cheader_deps"]
if "clibrary_deps" in fallback.args:
self.args["clibrary_deps"] = fallback.args["clibrary_deps"]
if "cheader_files" in fallback.args:
self.args["cheader_files"] = fallback.args["cheader_files"]
if "clibrary_files" in fallback.args:
self.args["clibrary_files"] = fallback.args["clibrary_files"]
self.ins = fallback.ins
self.outs = fallback.outs
self.deps = fallback.deps
self.traits = fallback.traits
@Rule
def package(self, name, package=None, fallback: Target = None):
_package(self, name, package, fallback, TargetPkgConfig)

View File

@@ -1,173 +0,0 @@
from build.ab import Rule, Targets, emit, simplerule, filenamesof
from build.utils import filenamesmatchingof, collectattrs
from os.path import join, abspath, dirname, relpath
import build.pkg # to get the protobuf package check
emit(
"""
PROTOC ?= protoc
"""
)
assert build.pkg.TargetPkgConfig.has_package(
"protobuf"
), "required package 'protobuf' not installed"
def _getprotodeps(deps):
r = set()
for d in deps:
r.update(d.args.get("protodeps", {d}))
return sorted(r)
@Rule
def proto(self, name, srcs: Targets = [], deps: Targets = []):
protodeps = _getprotodeps(deps)
descriptorlist = ":".join(
[
relpath(f, start=self.dir)
for f in filenamesmatchingof(protodeps, "*.descriptor")
]
)
dirs = sorted({"{dir}/" + dirname(f) for f in filenamesof(srcs)})
simplerule(
replaces=self,
ins=srcs,
outs=[f"={self.localname}.descriptor"],
deps=protodeps,
commands=(
["mkdir -p " + (" ".join(dirs))]
+ [f"$(CP) {f} {{dir}}/{f}" for f in filenamesof(srcs)]
+ [
"cd {dir} && "
+ (
" ".join(
[
"$(PROTOC)",
"--proto_path=.",
"--include_source_info",
f"--descriptor_set_out={self.localname}.descriptor",
]
+ (
[f"--descriptor_set_in={descriptorlist}"]
if descriptorlist
else []
)
+ ["{ins}"]
)
)
]
),
label="PROTO",
args={
"protosrcs": filenamesof(srcs),
"protodeps": set(protodeps) | {self},
},
)
@Rule
def protocc(self, name, srcs: Targets = [], deps: Targets = []):
outs = []
protos = []
allsrcs = collectattrs(targets=srcs, name="protosrcs")
assert allsrcs, "no sources provided"
for f in filenamesmatchingof(allsrcs, "*.proto"):
cc = f.replace(".proto", ".pb.cc")
h = f.replace(".proto", ".pb.h")
protos += [f]
outs += ["=" + cc, "=" + h]
protodeps = _getprotodeps(deps + srcs)
descriptorlist = ":".join(
[
relpath(f, start=self.dir)
for f in filenamesmatchingof(protodeps, "*.descriptor")
]
)
r = simplerule(
name=f"{self.localname}_srcs",
cwd=self.cwd,
ins=srcs,
outs=outs,
deps=protodeps,
commands=[
"cd {dir} && "
+ (
" ".join(
[
"$(PROTOC)",
"--proto_path=.",
"--cpp_out=.",
f"--descriptor_set_in={descriptorlist}",
]
+ protos
)
)
],
label="PROTOCC",
)
headers = {f[1:]: join(r.dir, f[1:]) for f in outs if f.endswith(".pb.h")}
from build.c import cxxlibrary
cxxlibrary(
replaces=self,
srcs=[r],
deps=deps,
hdrs=headers,
)
@Rule
def protojava(self, name, srcs: Targets = [], deps: Targets = []):
outs = []
allsrcs = collectattrs(targets=srcs, name="protosrcs")
assert allsrcs, "no sources provided"
protos = []
for f in filenamesmatchingof(allsrcs, "*.proto"):
protos += [f]
srcs += [f]
descriptorlist = ":".join(
[abspath(f) for f in filenamesmatchingof(srcs + deps, "*.descriptor")]
)
r = simplerule(
name=f"{self.localname}_srcs",
cwd=self.cwd,
ins=protos,
outs=[f"={self.localname}.srcjar"],
deps=srcs + deps,
commands=[
"mkdir -p {dir}/srcs",
"cd {dir} && "
+ (
" ".join(
[
"$(PROTOC)",
"--proto_path=.",
"--java_out=.",
f"--descriptor_set_in={descriptorlist}",
]
+ protos
)
),
"$(JAR) cf {outs[0]} -C {dir}/srcs .",
],
traits={"srcjar"},
label="PROTOJAVA",
)
from build.java import javalibrary
javalibrary(
replaces=self,
deps=[r] + deps,
)

View File

@@ -1,93 +0,0 @@
from build.ab import (
Rule,
Target,
Targets,
filenameof,
filenamesof,
cwdStack,
error,
simplerule,
)
from os.path import relpath, splitext, join, basename, isfile
from glob import iglob
import fnmatch
import itertools
def filenamesmatchingof(xs, pattern):
return fnmatch.filter(filenamesof(xs), pattern)
def stripext(path):
return splitext(path)[0]
def targetswithtraitsof(xs, trait):
return [t for t in xs if trait in t.traits]
def collectattrs(*, targets, name, initial=[]):
s = set(initial)
for a in [t.args.get(name, []) for t in targets]:
s.update(a)
return sorted(s)
def itemsof(pattern, root=None, cwd=None):
if not cwd:
cwd = cwdStack[-1]
if not root:
root = "."
pattern = join(cwd, pattern)
root = join(cwd, root)
result = {}
for f in iglob(pattern, recursive=True):
try:
if isfile(f):
result[relpath(f, root)] = f
except ValueError:
error(f"file '{f}' is not in root '{root}'")
return result
@Rule
def objectify(self, name, src: Target, symbol):
simplerule(
replaces=self,
ins=["build/_objectify.py", src],
outs=[f"={basename(filenameof(src))}.h"],
commands=["$(PYTHON) {ins[0]} {ins[1]} " + symbol + " > {outs}"],
label="OBJECTIFY",
)
@Rule
def test(
self,
name,
command: Target = None,
commands=None,
ins: Targets = None,
deps: Targets = None,
label="TEST",
):
if command:
simplerule(
replaces=self,
ins=[command],
outs=["=sentinel"],
commands=["{ins[0]}", "touch {outs}"],
deps=deps,
label=label,
)
else:
simplerule(
replaces=self,
ins=ins,
outs=["=sentinel"],
commands=commands + ["touch {outs}"],
deps=deps,
label=label,
)

View File

@@ -1,37 +0,0 @@
from build.ab import (
Rule,
simplerule,
TargetsMap,
filenameof,
emit,
)
emit(
"""
ZIP ?= zip
ZIPNOTE ?= zipnote
"""
)
@Rule
def zip(
self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP"
):
cs = ["rm -f {outs[0]}"]
ins = []
for k, v in items.items():
cs += [
"cat %s | $(ZIP) -q %s {outs[0]} -" % (filenameof(v), flags),
"printf '@ -\\n@=%s\\n' | $(ZIPNOTE) -w {outs[0]}" % k,
]
ins += [v]
simplerule(
replaces=self,
ins=ins,
outs=[f"={self.localname}." + extension],
commands=cs,
label=label,
)

View File

@@ -1,11 +0,0 @@
import platform
import os
if os.getenv("BUILDTYPE") == "windows":
windows = True
osx = False
unix = False
else:
windows = False
osx = platform.system() == "Darwin"
unix = True

45
dep/adflib/.gitignore vendored
View File

@@ -1,45 +0,0 @@
# Object files
*.o
*.lo
# Libraries
.libs
*.lib
*.a
*.la
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
# generated by autogen.sh
INSTALL
Makefile.in
aclocal.m4
autom4te.cache
compile
config.guess
config.h.in
config.sub
configure
depcomp
install-sh
ltmain.sh
missing
# generated by configure
.deps
Makefile
adflib.pc
config.h
config.log
config.status
libtool
stamp-h1

View File

@@ -1,13 +0,0 @@
The main developper is
Laurent Clévy (laurent.clevy@club-internet.fr)
Contributors are:
Bjarne Viksoe
(C++ wrapper, lot of bug fixes)
Gary Harris
(bug fixes and W32 support)
Dan Sutherland
(bug fixes and W32 support)
See CHANGES.txt for detailed contributions.

View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -1,128 +0,0 @@
The ADFlib is a free, portable and open implementation of the Amiga filesystem.
initial release in 1999
It supports :
- floppy dumps
- multiple partitions harddisk dumps
- UAE hardfiles
- WinNT devices with the 'native driver' written by Dan Sutherland
- mount/unmount/create a device (real one or a dump file),
- mount/unmount/create a volume (partition),
- create/open/close/delete/rename/undel a file,
- read/write bytes from/to a file,
- create/delete/rename/move/undel a directory,
- get directory contents, change current directory, get parent directory
- use dir cache to get directory contents.
It is written in portable C, and support the WinNT platform to access
real drives.
See also :
https://packages.debian.org/source/sid/unadf
https://www.cvedetails.com/cve/CVE-2016-1243/
https://www.cvedetails.com/cve/CVE-2016-1244/
---
unADF is a unzip like for .ADF files :
unadf [-lrcsp -v n] dumpname.adf [files-with-path] [-d extractdir]
-l : lists root directory contents
-r : lists directory tree contents
-c : use dircache data (must be used with -l)
-s : display entries logical block pointer (must be used with -l)
-m : display file comments, if exists (must be used with -l)
-v n : mount volume #n instead of default #0 volume
-p : send extracted files to pipe (unadf -p dump.adf Pics/pic1.gif | xv -)
-d dir : extract to 'dir' directory
Credits:
--------
main design and code Laurent Clevy
Bug fixes and C++ wrapper Bjarke Viksoe (adfwrapper.h)
WinNT native driver Dan Sutherland and Gary Harris
New versions and contact e-mail can be found at :
http://lclevy.free.fr/adflib
COMPILATION
-----------
The following commands should automatically detect the system
configuration and build the library and examples/unadf,
the ADF extractor binary.
./autogen.sh
./configure
make
FEATURES NEEDING FURTHER TESTS
------------------------------
* Native driver
The NATIV_DIR variable is used to choose the (only one) target platform
of the native driver. The default is :
NATIV_DIR = ./Generic
This one do not give access to any real device. The other one available is
Win32, to access real devices under WinNT.
* Win32DLL
The 'prefix.h' is used to create the Win32 DLL version of the library.
If the WIN32DLL variable is defined in the library code, public functions
are preceded by the '__declspec(dllexport)' directive. If this same
variable is defined, the '__declspec(dllimport)' is put before the functions
prototypes in the 'adflib.h' library include file.
FILES
-----
AUTHORS Contributors
README The file you are reading
TODO Future improvements and bugfixes
CHANGES Detailed changes
src/ main library files
src/win32/ WinNT native driver
src/generic/ native files templates
boot/ Bootblocks that might by used to put on floppy disks
doc/ The library developpers documentation
doc/FAQ/ The Amiga Filesystem explained
examples/ unadf.c
Possible bugs
-------------
- in dircache updates
- when a volume is becoming full
- lost memory releases
Please report any bugs or mistakes in the documentation !
Have fun anyway !

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