├── cdrom
├── .gitignore
├── Cargo.toml
├── Cargo.lock
└── src
│ └── lib.rs
├── testdata
├── onetrack
│ ├── ccd
│ │ ├── basic_image.cue
│ │ ├── basic_image.img
│ │ ├── basic_image.sub
│ │ └── basic_image.ccd
│ └── bincue
│ │ ├── basic_image.cue
│ │ └── basic_image.bin
└── dataplusaudio
│ ├── ccd
│ ├── disc.bin
│ ├── disc.sub
│ ├── disc.cue
│ └── disc.ccd
│ └── bincue
│ ├── disc.bin
│ └── disc.cue
├── oranda.json
├── .gitignore
├── Cargo.toml
├── dist-workspace.toml
├── .github
└── workflows
│ ├── ci.yml
│ ├── web.yml
│ └── release.yml
├── README.md
├── CHANGELOG.md
├── src
└── main.rs
├── Cargo.lock
└── LICENSE
/cdrom/.gitignore:
--------------------------------------------------------------------------------
1 | /.cargo
2 | /target
3 |
--------------------------------------------------------------------------------
/testdata/onetrack/ccd/basic_image.cue:
--------------------------------------------------------------------------------
1 | FILE "basic_image.img" BINARY
2 | TRACK 01 MODE1/2352
3 | INDEX 01 00:00:00
4 |
--------------------------------------------------------------------------------
/testdata/dataplusaudio/ccd/disc.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/dataplusaudio/ccd/disc.bin
--------------------------------------------------------------------------------
/testdata/dataplusaudio/ccd/disc.sub:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/dataplusaudio/ccd/disc.sub
--------------------------------------------------------------------------------
/testdata/onetrack/bincue/basic_image.cue:
--------------------------------------------------------------------------------
1 | FILE "basic_image.bin" BINARY
2 | TRACK 01 MODE1/2352
3 | INDEX 01 00:00:00
4 |
--------------------------------------------------------------------------------
/testdata/onetrack/ccd/basic_image.img:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/onetrack/ccd/basic_image.img
--------------------------------------------------------------------------------
/testdata/onetrack/ccd/basic_image.sub:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/onetrack/ccd/basic_image.sub
--------------------------------------------------------------------------------
/testdata/dataplusaudio/bincue/disc.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/dataplusaudio/bincue/disc.bin
--------------------------------------------------------------------------------
/testdata/onetrack/bincue/basic_image.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mistydemeo/cue2ccd/main/testdata/onetrack/bincue/basic_image.bin
--------------------------------------------------------------------------------
/cdrom/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cdrom"
3 | version = "0.3.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | cdrom_crc = "0.1.0"
8 | cue = "3.0.1"
9 |
--------------------------------------------------------------------------------
/oranda.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "path_prefix": "cue2ccd"
4 | },
5 | "styles": {
6 | "theme": "axolight"
7 | },
8 | "components": {
9 | "artifacts": {
10 | "cargo_dist": true
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/testdata/dataplusaudio/bincue/disc.cue:
--------------------------------------------------------------------------------
1 | FILE "disc.bin" BINARY
2 | TRACK 01 MODE1/2352
3 | INDEX 01 00:00:00
4 | TRACK 02 AUDIO
5 | INDEX 00 00:04:16
6 | INDEX 01 00:06:16
7 | TRACK 03 AUDIO
8 | INDEX 00 00:07:16
9 | INDEX 01 00:09:16
10 |
--------------------------------------------------------------------------------
/testdata/dataplusaudio/ccd/disc.cue:
--------------------------------------------------------------------------------
1 | FILE "disc.bin" BINARY
2 | TRACK 01 MODE1/2352
3 | INDEX 01 00:00:00
4 | TRACK 02 AUDIO
5 | INDEX 00 00:04:16
6 | INDEX 01 00:06:16
7 | TRACK 03 AUDIO
8 | INDEX 00 00:07:16
9 | INDEX 01 00:09:16
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.cargo
2 | /target
3 | /testdata/onetrack/bincue/*.ccd
4 | /testdata/onetrack/bincue/*.img
5 | /testdata/onetrack/bincue/*.sub
6 | /testdata/dataplusaudio/bincue/*.ccd
7 | /testdata/dataplusaudio/bincue/*.img
8 | /testdata/dataplusaudio/bincue/*.sub
9 | # uncommitted local test data
10 | /*.7z
11 | /real_image
12 | /realtest
13 |
14 | # oranda
15 | /public
16 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cue2ccd"
3 | version = "1.1.0"
4 | edition = "2021"
5 | homepage = "https://www.mistys-internet.website/cue2ccd/"
6 | description = "Tool to convert BIN/CUE disc images to CCD/IMG/SUB"
7 | repository = "https://github.com/mistydemeo/cue2ccd"
8 |
9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 |
11 | [dependencies]
12 | clap = { version = "4.3.4", features = ["derive"] }
13 | miette = { version = "5.6.0", features = ["fancy"] }
14 | thiserror = "1.0.40"
15 |
16 | [dependencies.cdrom]
17 | path = "cdrom"
18 | version = "0.3.0"
19 |
20 | # The profile that 'cargo dist' will build with
21 | [profile.dist]
22 | inherits = "release"
23 | lto = "thin"
24 |
--------------------------------------------------------------------------------
/dist-workspace.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["cargo:."]
3 |
4 | # Config for 'dist'
5 | [dist]
6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax)
7 | cargo-dist-version = "0.30.2"
8 | # CI backends to support
9 | ci = "github"
10 | # The installers to generate for each app
11 | installers = ["shell", "powershell", "homebrew"]
12 | # A GitHub repo to push Homebrew formulas to
13 | tap = "mistydemeo/homebrew-formulae"
14 | # Target platforms to build apps for (Rust target-triple syntax)
15 | targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-gnu"]
16 | # Publish jobs to run in CI
17 | publish-jobs = ["homebrew"]
18 | # Path that installers should place binaries in
19 | install-path = "CARGO_HOME"
20 | # Whether to install an updater program
21 | install-updater = false
22 |
23 | [dist.dependencies.chocolatey]
24 | winflexbison = '*'
25 |
26 | [dist.github-custom-runners]
27 | global = "ubuntu-22.04"
28 | x86_64-unknown-linux-gnu = "ubuntu-22.04"
29 |
--------------------------------------------------------------------------------
/testdata/onetrack/ccd/basic_image.ccd:
--------------------------------------------------------------------------------
1 | [CloneCD]
2 | Version=3
3 |
4 | [Disc]
5 | TocEntries=4
6 | Sessions=1
7 | DataTracksScrambled=0
8 | CDTextLength=0
9 |
10 | [Session 1]
11 | PreGapMode=1
12 | PreGapSubC=0
13 |
14 | [Entry 0]
15 | Session=1
16 | Point=0xa0
17 | ADR=0x01
18 | Control=0x04
19 | TrackNo=0
20 | AMin=0
21 | ASec=0
22 | AFrame=0
23 | ALBA=-150
24 | Zero=0
25 | PMin=1
26 | PSec=0
27 | PFrame=0
28 | PLBA=4350
29 |
30 | [Entry 1]
31 | Session=1
32 | Point=0xa1
33 | ADR=0x01
34 | Control=0x04
35 | TrackNo=0
36 | AMin=0
37 | ASec=0
38 | AFrame=0
39 | ALBA=-150
40 | Zero=0
41 | PMin=1
42 | PSec=0
43 | PFrame=0
44 | PLBA=4350
45 |
46 | [Entry 2]
47 | Session=1
48 | Point=0xa2
49 | ADR=0x01
50 | Control=0x04
51 | TrackNo=0
52 | AMin=0
53 | ASec=0
54 | AFrame=0
55 | ALBA=-150
56 | Zero=0
57 | PMin=0
58 | PSec=6
59 | PFrame=16
60 | PLBA=316
61 |
62 | [Entry 3]
63 | Session=1
64 | Point=0x01
65 | ADR=0x01
66 | Control=0x04
67 | TrackNo=0
68 | AMin=0
69 | ASec=0
70 | AFrame=0
71 | ALBA=-150
72 | Zero=0
73 | PMin=0
74 | PSec=2
75 | PFrame=0
76 | PLBA=0
77 |
78 | [TRACK 1]
79 | MODE=1
80 | INDEX 1=0
81 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # The "Normal" CI for tests and linters and whatnot
2 | name: Rust CI
3 |
4 | # Ci should be run on...
5 | on:
6 | # Every pull request (will need approval for new contributors)
7 | pull_request:
8 | # Every push to...
9 | push:
10 | branches:
11 | # The main branch
12 | - main
13 |
14 | # We want all these checks to fail if they spit out warnings
15 | env:
16 | RUSTFLAGS: -Dwarnings
17 |
18 | jobs:
19 | # Check that rustfmt is a no-op
20 | fmt:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v3
24 | - uses: dtolnay/rust-toolchain@master
25 | with:
26 | toolchain: stable
27 | components: rustfmt
28 | - run: cargo fmt --all -- --check
29 |
30 | # Check that clippy is appeased
31 | clippy:
32 | runs-on: ubuntu-latest
33 | steps:
34 | - uses: actions/checkout@v3
35 | - uses: dtolnay/rust-toolchain@master
36 | with:
37 | toolchain: stable
38 | components: clippy
39 | - uses: swatinem/rust-cache@v2
40 | - uses: actions-rs/clippy-check@v1
41 | env:
42 | PWD: ${{ env.GITHUB_WORKSPACE }}
43 | with:
44 | token: ${{ secrets.GITHUB_TOKEN }}
45 | args: --workspace --tests --examples
46 |
47 | tests:
48 | runs-on: ubuntu-latest
49 | steps:
50 | - uses: actions/checkout@v3
51 | - uses: dtolnay/rust-toolchain@master
52 | with:
53 | toolchain: stable
54 | - uses: swatinem/rust-cache@v2
55 | - name: Tests (cdrom)
56 | run: |
57 | cd cdrom
58 | # libcue isn't thread-safe
59 | cargo test -- --test-threads=1
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | cue2ccd
4 | =======
5 |
6 |
7 |
8 | cue2ccd is a tool to convert BIN/CUE CD-ROM disc images into CloneCD CCD/IMG/SUB disc images. It's useful for software and devices that only support CloneCD format like Rhea/Phoebe optical drive emulators since BIN/CUE disc images are more common on the internet.
9 |
10 | Usage
11 | -----
12 |
13 | Using cue2ccd is straightforward: just run `cue2ccd path_to_your_disc.cue`. It will produce the `.img`, `.ccd` and `.sub` files you need in the same directory
14 | as your original image, ready for use. If you prefer the generated files to be placed in a separate directory, you can specify the output path with the `--output-path` option.
15 |
16 | Limitations
17 | -----------
18 |
19 | * cue2ccd only supports raw disc images; it doesn't support ISO files or cuesheets containing ISOs or WAV files.
20 |
21 | Building
22 | --------
23 |
24 | To build from source, just run `cargo build` or `cargo run`. Windows users will first need to install flex and bison; this can be done using chocolatey by running [`choco install winflexbison`](https://community.chocolatey.org/packages/winflexbison).
25 |
26 | Support
27 | -------
28 |
29 | Support is available on the [GitHub issue tracker](https://github.com/mistydemeo/cue2ccd/issues). I'm also happy to talk about feature requests.
30 |
31 | ## License
32 |
33 | cue2ccd is licensed under the GPL 2.0, which is the same license used by libcue.
34 |
35 |
36 |
37 | Contributing
38 | ------------
39 |
40 | Help is always appreciated! You can use issues to discuss anything you'd like to work on, and pull requests are always welcome to fix bugs or add new features.
41 |
42 |
43 |
--------------------------------------------------------------------------------
/testdata/dataplusaudio/ccd/disc.ccd:
--------------------------------------------------------------------------------
1 | [CloneCD]
2 | Version=3
3 |
4 | [Disc]
5 | TocEntries=6
6 | Sessions=1
7 | DataTracksScrambled=0
8 | CDTextLength=0
9 |
10 | [Session 1]
11 | PreGapMode=1
12 | PreGapSubC=0
13 |
14 | [Entry 0]
15 | Session=1
16 | Point=0xa0
17 | ADR=0x01
18 | Control=0x04
19 | TrackNo=0
20 | AMin=0
21 | ASec=0
22 | AFrame=0
23 | ALBA=-150
24 | Zero=0
25 | PMin=1
26 | PSec=0
27 | PFrame=0
28 | PLBA=4350
29 |
30 | [Entry 1]
31 | Session=1
32 | Point=0xa1
33 | ADR=0x01
34 | Control=0x00
35 | TrackNo=0
36 | AMin=0
37 | ASec=0
38 | AFrame=0
39 | ALBA=-150
40 | Zero=0
41 | PMin=3
42 | PSec=0
43 | PFrame=0
44 | PLBA=13350
45 |
46 | [Entry 2]
47 | Session=1
48 | Point=0xa2
49 | ADR=0x01
50 | Control=0x00
51 | TrackNo=0
52 | AMin=0
53 | ASec=0
54 | AFrame=0
55 | ALBA=-150
56 | Zero=0
57 | PMin=0
58 | PSec=12
59 | PFrame=16
60 | PLBA=766
61 |
62 | [Entry 3]
63 | Session=1
64 | Point=0x01
65 | ADR=0x01
66 | Control=0x04
67 | TrackNo=0
68 | AMin=0
69 | ASec=0
70 | AFrame=0
71 | ALBA=-150
72 | Zero=0
73 | PMin=0
74 | PSec=2
75 | PFrame=0
76 | PLBA=0
77 |
78 | [Entry 4]
79 | Session=1
80 | Point=0x02
81 | ADR=0x01
82 | Control=0x00
83 | TrackNo=0
84 | AMin=0
85 | ASec=0
86 | AFrame=0
87 | ALBA=-150
88 | Zero=0
89 | PMin=0
90 | PSec=8
91 | PFrame=16
92 | PLBA=466
93 |
94 | [Entry 5]
95 | Session=1
96 | Point=0x03
97 | ADR=0x01
98 | Control=0x00
99 | TrackNo=0
100 | AMin=0
101 | ASec=0
102 | AFrame=0
103 | ALBA=-150
104 | Zero=0
105 | PMin=0
106 | PSec=11
107 | PFrame=16
108 | PLBA=691
109 |
110 | [TRACK 1]
111 | MODE=1
112 | INDEX 1=0
113 | [TRACK 2]
114 | MODE=0
115 | INDEX 0=316
116 | INDEX 1=466
117 | [TRACK 3]
118 | MODE=0
119 | INDEX 0=541
120 | INDEX 1=691
121 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Version 1.1.0 (2025-11-08)
2 |
3 | This release contains a brand-new feature: subchannel-based protection emulation! If you specfiy a specific protection scheme when running cue2ccd, it will generate the subchannel data in the right nonstandard way the protection scheme expects. This can be useful for people who have created BIN/CUE copies of protected CDs and have lost the original subchannel data they need for it to work.
4 |
5 | In this first release, DiscGuard v1 and DiscGuard v2 are supported; future versions may add support for libcrypt and SecuROM.
6 |
7 | This feature was contributed by @HeroponRikiBestest.
8 |
9 | # Version 1.0.3 (2025-05-26)
10 |
11 | This release fixes two minor bugs with subchannel generation. These caused certain bits of timing information to be off by a single sector. It's unlikely this caused any discs not to work, but it may have caused very minor audio sync issues for certain discs.
12 |
13 | * Fixes an off-by-one error in the index location in the subchannel. (@HeroponRikiBestest - #30)
14 | * Fixes an error in the position where the P subchannel should end. (@mistydemeo - #32)
15 |
16 | # Version 1.0.2 (2025-05-20)
17 |
18 | This fixes several bugs:
19 |
20 | * Bad `.img` files were being generated for single-file images containing multiple tracks. (#21)
21 | * Removes a "syntax error" message when parsing most cue sheets. There wasn't an issue with the cue sheets themselves; this was a bug in the cue sheet parser cue2ccd uses.
22 |
23 | # Version 1.0.1 (2024-12-19)
24 |
25 | This fixes a few bugs from the initial release:
26 |
27 | * Subcode data was being generated incorrectly for split images.
28 | * Output filenames were generated incorrectly for cue sheets containing periods in their names.
29 |
30 | # Version 1.0.0 (2024-12-14)
31 |
32 | This is the initial release.
33 |
--------------------------------------------------------------------------------
/cdrom/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "cc"
7 | version = "1.2.1"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
10 | dependencies = [
11 | "shlex",
12 | ]
13 |
14 | [[package]]
15 | name = "cdrom"
16 | version = "0.3.0"
17 | dependencies = [
18 | "cdrom_crc",
19 | "cue",
20 | ]
21 |
22 | [[package]]
23 | name = "cdrom_crc"
24 | version = "0.1.0"
25 | source = "registry+https://github.com/rust-lang/crates.io-index"
26 | checksum = "b79de1ea79db6578ee25a7bcc8fddc58f958e6063aed54f426907d9d9f97895e"
27 |
28 | [[package]]
29 | name = "cmake"
30 | version = "0.1.51"
31 | source = "registry+https://github.com/rust-lang/crates.io-index"
32 | checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
33 | dependencies = [
34 | "cc",
35 | ]
36 |
37 | [[package]]
38 | name = "cue"
39 | version = "3.0.1"
40 | source = "registry+https://github.com/rust-lang/crates.io-index"
41 | checksum = "690eedca1eb72586e5359d32c43e8e7497aeed1c469b8b07b4eb8500056d3287"
42 | dependencies = [
43 | "cue-sys",
44 | "libc",
45 | ]
46 |
47 | [[package]]
48 | name = "cue-sys"
49 | version = "2.0.1"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | checksum = "aacab32605a00e807f2fe1f8c5ecfab91da08d34269bcd7319ca60c41a4a806f"
52 | dependencies = [
53 | "cmake",
54 | "libc",
55 | ]
56 |
57 | [[package]]
58 | name = "libc"
59 | version = "0.2.146"
60 | source = "registry+https://github.com/rust-lang/crates.io-index"
61 | checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
62 |
63 | [[package]]
64 | name = "shlex"
65 | version = "1.3.0"
66 | source = "registry+https://github.com/rust-lang/crates.io-index"
67 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
68 |
--------------------------------------------------------------------------------
/.github/workflows/web.yml:
--------------------------------------------------------------------------------
1 | # Workflow to build your docs with oranda (and mdbook)
2 | # and deploy them to Github Pages
3 | name: Web
4 |
5 | # We're going to push to the gh-pages branch, so we need that permission
6 | permissions:
7 | contents: write
8 |
9 | # What situations do we want to build docs in?
10 | # All of these work independently and can be removed / commented out
11 | # if you don't want oranda/mdbook running in that situation
12 | on:
13 | # Check that a PR didn't break docs!
14 | #
15 | # Note that the "Deploy to Github Pages" step won't run in this mode,
16 | # so this won't have any side-effects. But it will tell you if a PR
17 | # completely broke oranda/mdbook. Sadly we don't provide previews (yet)!
18 | pull_request:
19 |
20 | # Whenever something gets pushed to main, update the docs!
21 | # This is great for getting docs changes live without cutting a full release.
22 | #
23 | # Note that if you're using cargo-dist, this will "race" the Release workflow
24 | # that actually builds the Github Release that oranda tries to read (and
25 | # this will almost certainly complete first). As a result you will publish
26 | # docs for the latest commit but the oranda landing page won't know about
27 | # the latest release. The workflow_run trigger below will properly wait for
28 | # cargo-dist, and so this half-published state will only last for ~10 minutes.
29 | #
30 | # If you only want docs to update with releases, disable this, or change it to
31 | # a "release" branch. You can, of course, also manually trigger a workflow run
32 | # when you want the docs to update.
33 | push:
34 | branches:
35 | - main
36 |
37 | # Whenever a workflow called "Release" completes, update the docs!
38 | #
39 | # If you're using cargo-dist, this is recommended, as it will ensure that
40 | # oranda always sees the latest release right when it's available. Note
41 | # however that Github's UI is wonky when you use workflow_run, and won't
42 | # show this workflow as part of any commit. You have to go to the "actions"
43 | # tab for your repo to see this one running (the gh-pages deploy will also
44 | # only show up there).
45 | workflow_run:
46 | workflows: [ "Release" ]
47 | types:
48 | - completed
49 |
50 | # Alright, let's do it!
51 | jobs:
52 | web:
53 | name: Build and deploy site and docs
54 | runs-on: ubuntu-latest
55 | steps:
56 | # Setup
57 | - uses: actions/checkout@v3
58 | with:
59 | fetch-depth: 0
60 | - uses: dtolnay/rust-toolchain@stable
61 | - uses: swatinem/rust-cache@v2
62 |
63 | # If you use any mdbook plugins, here's the place to install them!
64 |
65 | # Install and run oranda (and mdbook)!
66 | #
67 | # This will write all output to ./public/ (including copying mdbook's output to there).
68 | - name: Install and run oranda
69 | run: |
70 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/download/v0.6.1/oranda-installer.sh | sh
71 | oranda build
72 |
73 | # Deploy to our gh-pages branch (creating it if it doesn't exist).
74 | # The "public" dir that oranda made above will become the root dir
75 | # of this branch.
76 | #
77 | # Note that once the gh-pages branch exists, you must
78 | # go into repo's settings > pages and set "deploy from branch: gh-pages".
79 | # The other defaults work fine.
80 | - name: Deploy to Github Pages
81 | uses: JamesIves/github-pages-deploy-action@v4.4.1
82 | # ONLY if we're on main (so no PRs or feature branches allowed!)
83 | if: ${{ github.ref == 'refs/heads/main' }}
84 | with:
85 | branch: gh-pages
86 | # Gotta tell the action where to find oranda's output
87 | folder: public
88 | token: ${{ secrets.GITHUB_TOKEN }}
89 | single-commit: true
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::fs::File;
3 | use std::io::Write;
4 | use std::path::Path;
5 |
6 | use cdrom::cue::cd::CD;
7 | use cdrom::cue::track::{Track, TrackMode};
8 | use cdrom::Disc;
9 | use cdrom::DiscProtection;
10 | use clap::{Parser, ValueEnum};
11 | use miette::{Diagnostic, Result};
12 | use thiserror::Error;
13 |
14 | #[derive(Error, Debug, Diagnostic)]
15 | enum Cue2CCDError {
16 | #[error("Couldn't find one or more files specified in the cuesheet.")]
17 | #[diagnostic(help("Missing files: {}", missing_files.join(", ")))]
18 | MissingFilesError { missing_files: Vec },
19 |
20 | #[error("Unable to determine the directory {filename} is in!")]
21 | NoParentError { filename: String },
22 |
23 | #[error("Unable to determine the filename portion of {filename}!")]
24 | NoFilenameError { filename: String },
25 |
26 | // Thrown if SBI file exists but doesn't have the correct SBI header
27 | #[error("Invalid SBI file!")]
28 | InvalidSBIError {},
29 |
30 | #[error("LSD does not match specified protection!")]
31 | InvalidProtectionLSDError {},
32 |
33 | #[error("SBI does not match specified protection!")]
34 | InvalidProtectionSBIError {},
35 |
36 | #[error("This tool only supports raw disc images")]
37 | #[diagnostic(help("cuesheets containing .wav files are not compatible."))]
38 | WaveFile {},
39 |
40 | #[error("This tool only supports raw disc images")]
41 | #[diagnostic(help("cuesheets containing ISOs or other non-raw data are not compatible."))]
42 | CookedData {},
43 |
44 | #[error(transparent)]
45 | IO(#[from] std::io::Error),
46 |
47 | #[error(transparent)]
48 | Cue(#[from] std::ffi::NulError),
49 | }
50 |
51 | #[derive(Clone, Debug, ValueEnum)]
52 | enum ProtectionType {
53 | #[clap(name = "discguard")]
54 | DiscGuard,
55 | }
56 |
57 | #[derive(Parser, Debug)]
58 | #[command(
59 | author,
60 | version,
61 | about,
62 | long_about = "Generate CCD and SUB files from BIN/CUE"
63 | )]
64 | struct Args {
65 | filename: String,
66 | #[arg(long, default_value_t = false)]
67 | skip_img_copy: bool,
68 | #[arg(long)]
69 | output_path: Option,
70 | #[arg(long, value_enum)]
71 | pub protection_type: Option,
72 | }
73 |
74 | fn validate_mode(tracks: &[Track]) -> Result<(), Cue2CCDError> {
75 | for track in tracks {
76 | if track.get_filename().ends_with(".wav") {
77 | return Err(Cue2CCDError::WaveFile {});
78 | }
79 | match track.get_mode() {
80 | TrackMode::Mode1 | TrackMode::Mode2 | TrackMode::Mode2Form1 | TrackMode::Mode2Form2 => {
81 | return Err(Cue2CCDError::CookedData {});
82 | }
83 | _ => (),
84 | }
85 | }
86 | Ok(())
87 | }
88 |
89 | /// Fetches unique tracks from the list of tracks.
90 | /// If the same track appears multiple times in a row,
91 | /// returns only a single copy.
92 | fn get_unique_tracks(tracks: &[Track]) -> Vec {
93 | let mut files = vec![];
94 |
95 | for track in tracks.iter() {
96 | let filename = track.get_filename();
97 | if files.last() == Some(&filename) {
98 | continue;
99 | }
100 | files.push(filename);
101 | }
102 |
103 | files
104 | }
105 |
106 | // LSD File Format:
107 | // The file consists of subQ data, specifically consisting of the actual AMSF that the current subQ
108 | // was read from, followed by all 12 bytes of subQ data. LSD is definitively better as a file
109 | // format for storing subchannel data discrepancies as opposed to SBI, which forces you to
110 | // generate the CRC16 yourself (something that is a huge problem for SecuROM and LibCrypt if
111 | // you're aiming for accuracy) and ideally should always be preferred if possible.
112 | fn generate_lsd_data(raw_lsd_data: Vec) -> Result>, Cue2CCDError> {
113 | // LSD files have never been defined in the cuesheet, and programs (mainly just PS1
114 | // emulators so far) that make use of them simply check if there's an LSD file with the
115 | // same basename next to the .cue. If one exists, they use it, otherwise they don't.
116 | // It seems best to keep in line with this behavior
117 |
118 | let mut hash_map: HashMap> = HashMap::new();
119 | // should always be multiple of 15
120 | for chunk in raw_lsd_data.chunks(15) {
121 | let mut q = vec![0; 12];
122 | // These don't really need to be muts, but, they should always be getting set in the
123 | // enumeration, and it makes things easier to not have to pass them as options
124 | let mut m: i64 = 0;
125 | let mut s: i64 = 0;
126 | let mut f: i64 = 0;
127 | for (byte_index, &item) in chunk.iter().enumerate() {
128 | match byte_index {
129 | 0 => m = item as i64,
130 | 1 => s = item as i64,
131 | 2 => f = item as i64,
132 | _ => q[byte_index - 3] = item,
133 | }
134 | }
135 | hash_map.insert(cdrom::amsf_to_asec(m, s, f), q);
136 | }
137 | Ok(hash_map)
138 | }
139 |
140 | // SBI File Format:
141 | // Starts with header 0x53 0x42 0x49 0x00 ('S' 'B' 'I' '0x00')
142 | // The entire rest of the file consists of subQ data, specifically consisting of the actual
143 | // AMSF that the current subQ was read from, followed by a dummy 0x01 byte, followed by the first
144 | // 10 bytes of that subQ (so, everything but the CRC16). The exclusion of the CRC16 is obviously
145 | // annoying, *especially* for SecuROM and LibCrypt. LSD is a better file format, but at the
146 | // moment, redump will only generate LSD files for PS1 discs, and we do not have the power to
147 | // change the website; so, until a successor website exists, SBI support is necessary. It's
148 | // also still preferred by a lot of people and emulators for PS1 for some reason, despite
149 | // being worse than LSD.
150 | fn generate_sbi_data(raw_sbi_data: Vec) -> Result>, Cue2CCDError> {
151 | // SBI files have never been defined in the cuesheet, and programs (mainly just PS1
152 | // emulators so far) that make use of them simply check if there's an SBI file with the
153 | // same basename next to the .cue. If one exists, they use it, otherwise they don't.
154 | // It seems best to keep in line with this behavior
155 |
156 | let (header, data) = raw_sbi_data.split_at(4);
157 | let mut hash_map: HashMap> = HashMap::new();
158 | if header != [83, 66, 73, 00] {
159 | // Checks for required [S][B][I][0x00] header
160 | return Err(Cue2CCDError::InvalidSBIError {});
161 | }
162 | // should always be multiple of 14
163 | for chunk in data.chunks(14) {
164 | let mut q = vec![0; 10];
165 | // These don't really need to be muts, but, they should always be getting set in the
166 | // enumeration, and it makes things easier to not have to pass them as options
167 | let mut m: i64 = 0;
168 | let mut s: i64 = 0;
169 | let mut f: i64 = 0;
170 | for (byte_index, &item) in chunk.iter().enumerate() {
171 | match byte_index {
172 | 0 => m = item as i64,
173 | 1 => s = item as i64,
174 | 2 => f = item as i64,
175 | // Index 3 excluded to ignore dummy 0x01 byte
176 | 3 => (),
177 | _ => q[byte_index - 4] = item,
178 | }
179 | }
180 | // Unlike LSD, SBI is missing the CRC16, so we have to do that
181 | // ourselves.
182 | let crc = cdrom::crc16(&q, cdrom::CRC16_INITIAL_CRC);
183 | q.push(((crc >> 8) & 0xFF) as u8);
184 | q.push((crc & 0xFF) as u8);
185 |
186 | hash_map.insert(cdrom::amsf_to_asec(m, s, f), q);
187 | }
188 | Ok(hash_map)
189 | }
190 |
191 | fn main() -> Result<(), miette::Report> {
192 | work()?;
193 | Ok(())
194 | }
195 |
196 | fn work() -> Result<(), Cue2CCDError> {
197 | let args = Args::parse();
198 |
199 | let Some(root) = Path::new(&args.filename).parent() else {
200 | return Err(Cue2CCDError::NoParentError {
201 | filename: args.filename,
202 | });
203 | };
204 | let Some(basename) = Path::new(&args.filename).file_name() else {
205 | return Err(Cue2CCDError::NoFilenameError {
206 | filename: args.filename,
207 | });
208 | };
209 | let path;
210 | let output_path;
211 | if let Some(p) = args.output_path {
212 | path = p;
213 | output_path = Path::new(&path);
214 | } else {
215 | output_path = root;
216 | }
217 | // Provides a pattern to build output filenames from
218 | let output_stem = output_path.join(basename);
219 |
220 | let cue_sheet = std::fs::read_to_string(&args.filename)?;
221 |
222 | let cd = CD::parse(cue_sheet)?;
223 |
224 | let tracks = cd.tracks();
225 |
226 | // We validate that the track modes are compatible. BIN/CUE can be
227 | // a variety of different formats, including WAVE files and "cooked"
228 | // tracks with no error correction metadata. We need all raw files in
229 | // order to be able to merge into a CloneCD image.
230 | // In the future, it may be nice to support actually converting tracks
231 | // into the supported format, but right now that's out of scope.
232 | validate_mode(&tracks)?;
233 |
234 | let files = get_unique_tracks(&tracks);
235 | let missing_files = files
236 | .iter()
237 | .filter(|f| !root.join(f).is_file())
238 | .cloned()
239 | .collect::>();
240 | if !missing_files.is_empty() {
241 | return Err(Cue2CCDError::MissingFilesError { missing_files });
242 | }
243 | let mut preconstructed_q_subcodes: HashMap> = Default::default();
244 |
245 | let mut chosen_protection_type: Option = None;
246 | // TODO: #1 - see about making lsd/sbi extension checks not case sensitive
247 | // TODO: #2 - verify expected SBI/LSD sizes?
248 | // TODO: #3 - choose protection based off of lsd/sbi size if lsd/sbi is present and a
249 | // TODO: protection wasn't chosen? This can't be done universally, but it can be done for a
250 | // TODO: lot of stuff. That could also be an issue for anyone who wants to provide an LSD/SBI
251 | // TODO: for a non-protection related reason and happens to hit one of the exact sizes/contents
252 | // TODO: needed, but that is a use case that does not currently exist.
253 | if Path::new(&output_stem.with_extension("lsd")).exists() {
254 | // LSD files are very small, so it seems best to read the whole thing in first?
255 | let temp_hashmap = generate_lsd_data(std::fs::read(Path::new(
256 | &output_stem.with_extension("lsd"),
257 | ))?)?;
258 | let len = temp_hashmap.len();
259 | if len == 76 {
260 | chosen_protection_type = Some(DiscProtection::DiscGuardScheme2);
261 | } else if len == 600 {
262 | chosen_protection_type = Some(DiscProtection::DiscGuardScheme1);
263 | } else if matches!(args.protection_type, Some(ProtectionType::DiscGuard)) {
264 | return Err(Cue2CCDError::InvalidProtectionLSDError {});
265 | }
266 | preconstructed_q_subcodes = temp_hashmap;
267 | } else if Path::new(&output_stem.with_extension("sbi")).exists() {
268 | // SBI files are very small, so it seems best to read the whole thing in first?
269 | let temp_hashmap = generate_sbi_data(std::fs::read(Path::new(
270 | &output_stem.with_extension("sbi"),
271 | ))?)?;
272 | let len = temp_hashmap.len();
273 | if len == 76 {
274 | chosen_protection_type = Some(DiscProtection::DiscGuardScheme2);
275 | } else if len == 600 {
276 | chosen_protection_type = Some(DiscProtection::DiscGuardScheme1);
277 | } else if matches!(args.protection_type, Some(ProtectionType::DiscGuard)) {
278 | return Err(Cue2CCDError::InvalidProtectionSBIError {});
279 | }
280 | preconstructed_q_subcodes = temp_hashmap;
281 | } else if matches!(args.protection_type, Some(ProtectionType::DiscGuard)) {
282 | chosen_protection_type = Some(DiscProtection::DiscGuardScheme2);
283 | }
284 |
285 | let sub_target = output_stem.with_extension("sub");
286 | let mut sub_write = File::create(sub_target)?;
287 |
288 | let disc = Disc::from_cuesheet(cd, root);
289 | for sector in disc.sectors() {
290 | sub_write.write_all(
291 | §or.generate_subchannel(&chosen_protection_type, &preconstructed_q_subcodes),
292 | )?;
293 | }
294 |
295 | let ccd_target = output_stem.with_extension("ccd");
296 | let mut ccd_write = File::create(ccd_target)?;
297 | disc.write_ccd(&mut ccd_write)?;
298 |
299 | if !args.skip_img_copy {
300 | let img_target = output_stem.with_extension("img");
301 | if img_target.exists() {
302 | eprintln!(
303 | "A .img file at path {} already exists; skipping copy",
304 | img_target.as_path().display()
305 | );
306 | } else {
307 | let mut out_file = std::fs::OpenOptions::new()
308 | .create(true)
309 | .append(true)
310 | .open(&img_target)?;
311 | for fname in files {
312 | let mut in_file = File::open(root.join(&fname))?;
313 | std::io::copy(&mut in_file, &mut out_file)?;
314 | out_file.flush()?;
315 | }
316 | }
317 | }
318 |
319 | eprintln!(
320 | "Conversion complete! Created {}",
321 | output_stem.with_extension("ccd").display()
322 | );
323 |
324 | Ok(())
325 | }
326 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
2 | #
3 | # Copyright 2022-2024, axodotdev
4 | # SPDX-License-Identifier: MIT or Apache-2.0
5 | #
6 | # CI that:
7 | #
8 | # * checks for a Git Tag that looks like a release
9 | # * builds artifacts with dist (archives, installers, hashes)
10 | # * uploads those artifacts to temporary workflow zip
11 | # * on success, uploads the artifacts to a GitHub Release
12 | #
13 | # Note that the GitHub Release will be created with a generated
14 | # title/body based on your changelogs.
15 |
16 | name: Release
17 | permissions:
18 | "contents": "write"
19 |
20 | # This task will run whenever you push a git tag that looks like a version
21 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
22 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
23 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
24 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch).
25 | #
26 | # If PACKAGE_NAME is specified, then the announcement will be for that
27 | # package (erroring out if it doesn't have the given version or isn't dist-able).
28 | #
29 | # If PACKAGE_NAME isn't specified, then the announcement will be for all
30 | # (dist-able) packages in the workspace with that version (this mode is
31 | # intended for workspaces with only one dist-able package, or with all dist-able
32 | # packages versioned/released in lockstep).
33 | #
34 | # If you push multiple tags at once, separate instances of this workflow will
35 | # spin up, creating an independent announcement for each one. However, GitHub
36 | # will hard limit this to 3 tags per commit, as it will assume more tags is a
37 | # mistake.
38 | #
39 | # If there's a prerelease-style suffix to the version, then the release(s)
40 | # will be marked as a prerelease.
41 | on:
42 | pull_request:
43 | push:
44 | tags:
45 | - '**[0-9]+.[0-9]+.[0-9]+*'
46 |
47 | jobs:
48 | # Run 'dist plan' (or host) to determine what tasks we need to do
49 | plan:
50 | runs-on: "ubuntu-22.04"
51 | outputs:
52 | val: ${{ steps.plan.outputs.manifest }}
53 | tag: ${{ !github.event.pull_request && github.ref_name || '' }}
54 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
55 | publishing: ${{ !github.event.pull_request }}
56 | env:
57 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58 | steps:
59 | - uses: actions/checkout@v4
60 | with:
61 | persist-credentials: false
62 | submodules: recursive
63 | - name: Install dist
64 | # we specify bash to get pipefail; it guards against the `curl` command
65 | # failing. otherwise `sh` won't catch that `curl` returned non-0
66 | shell: bash
67 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
68 | - name: Cache dist
69 | uses: actions/upload-artifact@v4
70 | with:
71 | name: cargo-dist-cache
72 | path: ~/.cargo/bin/dist
73 | # sure would be cool if github gave us proper conditionals...
74 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
75 | # functionality based on whether this is a pull_request, and whether it's from a fork.
76 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
77 | # but also really annoying to build CI around when it needs secrets to work right.)
78 | - id: plan
79 | run: |
80 | dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
81 | echo "dist ran successfully"
82 | cat plan-dist-manifest.json
83 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
84 | - name: "Upload dist-manifest.json"
85 | uses: actions/upload-artifact@v4
86 | with:
87 | name: artifacts-plan-dist-manifest
88 | path: plan-dist-manifest.json
89 |
90 | # Build and packages all the platform-specific things
91 | build-local-artifacts:
92 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
93 | # Let the initial task tell us to not run (currently very blunt)
94 | needs:
95 | - plan
96 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
97 | strategy:
98 | fail-fast: false
99 | # Target platforms/runners are computed by dist in create-release.
100 | # Each member of the matrix has the following arguments:
101 | #
102 | # - runner: the github runner
103 | # - dist-args: cli flags to pass to dist
104 | # - install-dist: expression to run to install dist on the runner
105 | #
106 | # Typically there will be:
107 | # - 1 "global" task that builds universal installers
108 | # - N "local" tasks that build each platform's binaries and platform-specific installers
109 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
110 | runs-on: ${{ matrix.runner }}
111 | container: ${{ matrix.container && matrix.container.image || null }}
112 | env:
113 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
115 | steps:
116 | - name: enable windows longpaths
117 | run: |
118 | git config --global core.longpaths true
119 | - uses: actions/checkout@v4
120 | with:
121 | persist-credentials: false
122 | submodules: recursive
123 | - name: Install Rust non-interactively if not already installed
124 | if: ${{ matrix.container }}
125 | run: |
126 | if ! command -v cargo > /dev/null 2>&1; then
127 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
128 | echo "$HOME/.cargo/bin" >> $GITHUB_PATH
129 | fi
130 | - name: Install dist
131 | run: ${{ matrix.install_dist.run }}
132 | # Get the dist-manifest
133 | - name: Fetch local artifacts
134 | uses: actions/download-artifact@v4
135 | with:
136 | pattern: artifacts-*
137 | path: target/distrib/
138 | merge-multiple: true
139 | - name: Install dependencies
140 | run: |
141 | ${{ matrix.packages_install }}
142 | - name: Build artifacts
143 | run: |
144 | # Actually do builds and make zips and whatnot
145 | dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
146 | echo "dist ran successfully"
147 | - id: cargo-dist
148 | name: Post-build
149 | # We force bash here just because github makes it really hard to get values up
150 | # to "real" actions without writing to env-vars, and writing to env-vars has
151 | # inconsistent syntax between shell and powershell.
152 | shell: bash
153 | run: |
154 | # Parse out what we just built and upload it to scratch storage
155 | echo "paths<> "$GITHUB_OUTPUT"
156 | dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
157 | echo "EOF" >> "$GITHUB_OUTPUT"
158 |
159 | cp dist-manifest.json "$BUILD_MANIFEST_NAME"
160 | - name: "Upload artifacts"
161 | uses: actions/upload-artifact@v4
162 | with:
163 | name: artifacts-build-local-${{ join(matrix.targets, '_') }}
164 | path: |
165 | ${{ steps.cargo-dist.outputs.paths }}
166 | ${{ env.BUILD_MANIFEST_NAME }}
167 |
168 | # Build and package all the platform-agnostic(ish) things
169 | build-global-artifacts:
170 | needs:
171 | - plan
172 | - build-local-artifacts
173 | runs-on: "ubuntu-22.04"
174 | env:
175 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
177 | steps:
178 | - uses: actions/checkout@v4
179 | with:
180 | persist-credentials: false
181 | submodules: recursive
182 | - name: Install cached dist
183 | uses: actions/download-artifact@v4
184 | with:
185 | name: cargo-dist-cache
186 | path: ~/.cargo/bin/
187 | - run: chmod +x ~/.cargo/bin/dist
188 | # Get all the local artifacts for the global tasks to use (for e.g. checksums)
189 | - name: Fetch local artifacts
190 | uses: actions/download-artifact@v4
191 | with:
192 | pattern: artifacts-*
193 | path: target/distrib/
194 | merge-multiple: true
195 | - id: cargo-dist
196 | shell: bash
197 | run: |
198 | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
199 | echo "dist ran successfully"
200 |
201 | # Parse out what we just built and upload it to scratch storage
202 | echo "paths<> "$GITHUB_OUTPUT"
203 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
204 | echo "EOF" >> "$GITHUB_OUTPUT"
205 |
206 | cp dist-manifest.json "$BUILD_MANIFEST_NAME"
207 | - name: "Upload artifacts"
208 | uses: actions/upload-artifact@v4
209 | with:
210 | name: artifacts-build-global
211 | path: |
212 | ${{ steps.cargo-dist.outputs.paths }}
213 | ${{ env.BUILD_MANIFEST_NAME }}
214 | # Determines if we should publish/announce
215 | host:
216 | needs:
217 | - plan
218 | - build-local-artifacts
219 | - build-global-artifacts
220 | # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
221 | if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
222 | env:
223 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
224 | runs-on: "ubuntu-22.04"
225 | outputs:
226 | val: ${{ steps.host.outputs.manifest }}
227 | steps:
228 | - uses: actions/checkout@v4
229 | with:
230 | persist-credentials: false
231 | submodules: recursive
232 | - name: Install cached dist
233 | uses: actions/download-artifact@v4
234 | with:
235 | name: cargo-dist-cache
236 | path: ~/.cargo/bin/
237 | - run: chmod +x ~/.cargo/bin/dist
238 | # Fetch artifacts from scratch-storage
239 | - name: Fetch artifacts
240 | uses: actions/download-artifact@v4
241 | with:
242 | pattern: artifacts-*
243 | path: target/distrib/
244 | merge-multiple: true
245 | - id: host
246 | shell: bash
247 | run: |
248 | dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
249 | echo "artifacts uploaded and released successfully"
250 | cat dist-manifest.json
251 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
252 | - name: "Upload dist-manifest.json"
253 | uses: actions/upload-artifact@v4
254 | with:
255 | # Overwrite the previous copy
256 | name: artifacts-dist-manifest
257 | path: dist-manifest.json
258 | # Create a GitHub Release while uploading all files to it
259 | - name: "Download GitHub Artifacts"
260 | uses: actions/download-artifact@v4
261 | with:
262 | pattern: artifacts-*
263 | path: artifacts
264 | merge-multiple: true
265 | - name: Cleanup
266 | run: |
267 | # Remove the granular manifests
268 | rm -f artifacts/*-dist-manifest.json
269 | - name: Create GitHub Release
270 | env:
271 | PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
272 | ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
273 | ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
274 | RELEASE_COMMIT: "${{ github.sha }}"
275 | run: |
276 | # Write and read notes from a file to avoid quoting breaking things
277 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
278 |
279 | gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
280 |
281 | publish-homebrew-formula:
282 | needs:
283 | - plan
284 | - host
285 | runs-on: "ubuntu-22.04"
286 | env:
287 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
288 | PLAN: ${{ needs.plan.outputs.val }}
289 | GITHUB_USER: "axo bot"
290 | GITHUB_EMAIL: "admin+bot@axo.dev"
291 | if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
292 | steps:
293 | - uses: actions/checkout@v4
294 | with:
295 | persist-credentials: true
296 | repository: "mistydemeo/homebrew-formulae"
297 | token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
298 | # So we have access to the formula
299 | - name: Fetch homebrew formulae
300 | uses: actions/download-artifact@v4
301 | with:
302 | pattern: artifacts-*
303 | path: Formula/
304 | merge-multiple: true
305 | # This is extra complex because you can make your Formula name not match your app name
306 | # so we need to find releases with a *.rb file, and publish with that filename.
307 | - name: Commit formula files
308 | run: |
309 | git config --global user.name "${GITHUB_USER}"
310 | git config --global user.email "${GITHUB_EMAIL}"
311 |
312 | for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do
313 | filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output)
314 | name=$(echo "$filename" | sed "s/\.rb$//")
315 | version=$(echo "$release" | jq .app_version --raw-output)
316 |
317 | export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
318 | brew update
319 | # We avoid reformatting user-provided data such as the app description and homepage.
320 | brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true
321 |
322 | git add "Formula/${filename}"
323 | git commit -m "${name} ${version}"
324 | done
325 | git push
326 |
327 | announce:
328 | needs:
329 | - plan
330 | - host
331 | - publish-homebrew-formula
332 | # use "always() && ..." to allow us to wait for all publish jobs while
333 | # still allowing individual publish jobs to skip themselves (for prereleases).
334 | # "host" however must run to completion, no skipping allowed!
335 | if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') }}
336 | runs-on: "ubuntu-22.04"
337 | env:
338 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
339 | steps:
340 | - uses: actions/checkout@v4
341 | with:
342 | persist-credentials: false
343 | submodules: recursive
344 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.20.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler"
16 | version = "1.0.2"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 |
20 | [[package]]
21 | name = "anstream"
22 | version = "0.3.2"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
25 | dependencies = [
26 | "anstyle",
27 | "anstyle-parse",
28 | "anstyle-query",
29 | "anstyle-wincon",
30 | "colorchoice",
31 | "is-terminal",
32 | "utf8parse",
33 | ]
34 |
35 | [[package]]
36 | name = "anstyle"
37 | version = "1.0.0"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
40 |
41 | [[package]]
42 | name = "anstyle-parse"
43 | version = "0.2.0"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
46 | dependencies = [
47 | "utf8parse",
48 | ]
49 |
50 | [[package]]
51 | name = "anstyle-query"
52 | version = "1.0.0"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
55 | dependencies = [
56 | "windows-sys",
57 | ]
58 |
59 | [[package]]
60 | name = "anstyle-wincon"
61 | version = "1.0.1"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
64 | dependencies = [
65 | "anstyle",
66 | "windows-sys",
67 | ]
68 |
69 | [[package]]
70 | name = "backtrace"
71 | version = "0.3.68"
72 | source = "registry+https://github.com/rust-lang/crates.io-index"
73 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
74 | dependencies = [
75 | "addr2line",
76 | "cc",
77 | "cfg-if",
78 | "libc",
79 | "miniz_oxide",
80 | "object",
81 | "rustc-demangle",
82 | ]
83 |
84 | [[package]]
85 | name = "backtrace-ext"
86 | version = "0.2.1"
87 | source = "registry+https://github.com/rust-lang/crates.io-index"
88 | checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
89 | dependencies = [
90 | "backtrace",
91 | ]
92 |
93 | [[package]]
94 | name = "bitflags"
95 | version = "1.3.2"
96 | source = "registry+https://github.com/rust-lang/crates.io-index"
97 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
98 |
99 | [[package]]
100 | name = "cc"
101 | version = "1.2.1"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
104 | dependencies = [
105 | "shlex",
106 | ]
107 |
108 | [[package]]
109 | name = "cdrom"
110 | version = "0.3.0"
111 | dependencies = [
112 | "cdrom_crc",
113 | "cue",
114 | ]
115 |
116 | [[package]]
117 | name = "cdrom_crc"
118 | version = "0.1.0"
119 | source = "registry+https://github.com/rust-lang/crates.io-index"
120 | checksum = "b79de1ea79db6578ee25a7bcc8fddc58f958e6063aed54f426907d9d9f97895e"
121 |
122 | [[package]]
123 | name = "cfg-if"
124 | version = "1.0.0"
125 | source = "registry+https://github.com/rust-lang/crates.io-index"
126 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
127 |
128 | [[package]]
129 | name = "clap"
130 | version = "4.3.4"
131 | source = "registry+https://github.com/rust-lang/crates.io-index"
132 | checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed"
133 | dependencies = [
134 | "clap_builder",
135 | "clap_derive",
136 | "once_cell",
137 | ]
138 |
139 | [[package]]
140 | name = "clap_builder"
141 | version = "4.3.4"
142 | source = "registry+https://github.com/rust-lang/crates.io-index"
143 | checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636"
144 | dependencies = [
145 | "anstream",
146 | "anstyle",
147 | "bitflags",
148 | "clap_lex",
149 | "strsim",
150 | ]
151 |
152 | [[package]]
153 | name = "clap_derive"
154 | version = "4.3.2"
155 | source = "registry+https://github.com/rust-lang/crates.io-index"
156 | checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
157 | dependencies = [
158 | "heck",
159 | "proc-macro2",
160 | "quote",
161 | "syn",
162 | ]
163 |
164 | [[package]]
165 | name = "clap_lex"
166 | version = "0.5.0"
167 | source = "registry+https://github.com/rust-lang/crates.io-index"
168 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
169 |
170 | [[package]]
171 | name = "cmake"
172 | version = "0.1.51"
173 | source = "registry+https://github.com/rust-lang/crates.io-index"
174 | checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
175 | dependencies = [
176 | "cc",
177 | ]
178 |
179 | [[package]]
180 | name = "colorchoice"
181 | version = "1.0.0"
182 | source = "registry+https://github.com/rust-lang/crates.io-index"
183 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
184 |
185 | [[package]]
186 | name = "cue"
187 | version = "3.0.1"
188 | source = "registry+https://github.com/rust-lang/crates.io-index"
189 | checksum = "690eedca1eb72586e5359d32c43e8e7497aeed1c469b8b07b4eb8500056d3287"
190 | dependencies = [
191 | "cue-sys",
192 | "libc",
193 | ]
194 |
195 | [[package]]
196 | name = "cue-sys"
197 | version = "2.0.1"
198 | source = "registry+https://github.com/rust-lang/crates.io-index"
199 | checksum = "aacab32605a00e807f2fe1f8c5ecfab91da08d34269bcd7319ca60c41a4a806f"
200 | dependencies = [
201 | "cmake",
202 | "libc",
203 | ]
204 |
205 | [[package]]
206 | name = "cue2ccd"
207 | version = "1.1.0"
208 | dependencies = [
209 | "cdrom",
210 | "clap",
211 | "miette",
212 | "thiserror",
213 | ]
214 |
215 | [[package]]
216 | name = "errno"
217 | version = "0.3.1"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
220 | dependencies = [
221 | "errno-dragonfly",
222 | "libc",
223 | "windows-sys",
224 | ]
225 |
226 | [[package]]
227 | name = "errno-dragonfly"
228 | version = "0.1.2"
229 | source = "registry+https://github.com/rust-lang/crates.io-index"
230 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
231 | dependencies = [
232 | "cc",
233 | "libc",
234 | ]
235 |
236 | [[package]]
237 | name = "gimli"
238 | version = "0.27.3"
239 | source = "registry+https://github.com/rust-lang/crates.io-index"
240 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
241 |
242 | [[package]]
243 | name = "heck"
244 | version = "0.4.1"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
247 |
248 | [[package]]
249 | name = "hermit-abi"
250 | version = "0.3.1"
251 | source = "registry+https://github.com/rust-lang/crates.io-index"
252 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
253 |
254 | [[package]]
255 | name = "io-lifetimes"
256 | version = "1.0.11"
257 | source = "registry+https://github.com/rust-lang/crates.io-index"
258 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
259 | dependencies = [
260 | "hermit-abi",
261 | "libc",
262 | "windows-sys",
263 | ]
264 |
265 | [[package]]
266 | name = "is-terminal"
267 | version = "0.4.7"
268 | source = "registry+https://github.com/rust-lang/crates.io-index"
269 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
270 | dependencies = [
271 | "hermit-abi",
272 | "io-lifetimes",
273 | "rustix",
274 | "windows-sys",
275 | ]
276 |
277 | [[package]]
278 | name = "is_ci"
279 | version = "1.1.1"
280 | source = "registry+https://github.com/rust-lang/crates.io-index"
281 | checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
282 |
283 | [[package]]
284 | name = "libc"
285 | version = "0.2.147"
286 | source = "registry+https://github.com/rust-lang/crates.io-index"
287 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
288 |
289 | [[package]]
290 | name = "linux-raw-sys"
291 | version = "0.3.8"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
294 |
295 | [[package]]
296 | name = "memchr"
297 | version = "2.5.0"
298 | source = "registry+https://github.com/rust-lang/crates.io-index"
299 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
300 |
301 | [[package]]
302 | name = "miette"
303 | version = "5.10.0"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
306 | dependencies = [
307 | "backtrace",
308 | "backtrace-ext",
309 | "is-terminal",
310 | "miette-derive",
311 | "once_cell",
312 | "owo-colors",
313 | "supports-color",
314 | "supports-hyperlinks",
315 | "supports-unicode",
316 | "terminal_size",
317 | "textwrap",
318 | "thiserror",
319 | "unicode-width",
320 | ]
321 |
322 | [[package]]
323 | name = "miette-derive"
324 | version = "5.10.0"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
327 | dependencies = [
328 | "proc-macro2",
329 | "quote",
330 | "syn",
331 | ]
332 |
333 | [[package]]
334 | name = "miniz_oxide"
335 | version = "0.7.1"
336 | source = "registry+https://github.com/rust-lang/crates.io-index"
337 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
338 | dependencies = [
339 | "adler",
340 | ]
341 |
342 | [[package]]
343 | name = "object"
344 | version = "0.31.1"
345 | source = "registry+https://github.com/rust-lang/crates.io-index"
346 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
347 | dependencies = [
348 | "memchr",
349 | ]
350 |
351 | [[package]]
352 | name = "once_cell"
353 | version = "1.18.0"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
356 |
357 | [[package]]
358 | name = "owo-colors"
359 | version = "3.5.0"
360 | source = "registry+https://github.com/rust-lang/crates.io-index"
361 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
362 |
363 | [[package]]
364 | name = "proc-macro2"
365 | version = "1.0.66"
366 | source = "registry+https://github.com/rust-lang/crates.io-index"
367 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
368 | dependencies = [
369 | "unicode-ident",
370 | ]
371 |
372 | [[package]]
373 | name = "quote"
374 | version = "1.0.32"
375 | source = "registry+https://github.com/rust-lang/crates.io-index"
376 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
377 | dependencies = [
378 | "proc-macro2",
379 | ]
380 |
381 | [[package]]
382 | name = "rustc-demangle"
383 | version = "0.1.23"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
386 |
387 | [[package]]
388 | name = "rustix"
389 | version = "0.37.20"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
392 | dependencies = [
393 | "bitflags",
394 | "errno",
395 | "io-lifetimes",
396 | "libc",
397 | "linux-raw-sys",
398 | "windows-sys",
399 | ]
400 |
401 | [[package]]
402 | name = "shlex"
403 | version = "1.3.0"
404 | source = "registry+https://github.com/rust-lang/crates.io-index"
405 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
406 |
407 | [[package]]
408 | name = "smawk"
409 | version = "0.3.1"
410 | source = "registry+https://github.com/rust-lang/crates.io-index"
411 | checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
412 |
413 | [[package]]
414 | name = "strsim"
415 | version = "0.10.0"
416 | source = "registry+https://github.com/rust-lang/crates.io-index"
417 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
418 |
419 | [[package]]
420 | name = "supports-color"
421 | version = "2.0.0"
422 | source = "registry+https://github.com/rust-lang/crates.io-index"
423 | checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
424 | dependencies = [
425 | "is-terminal",
426 | "is_ci",
427 | ]
428 |
429 | [[package]]
430 | name = "supports-hyperlinks"
431 | version = "2.1.0"
432 | source = "registry+https://github.com/rust-lang/crates.io-index"
433 | checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
434 | dependencies = [
435 | "is-terminal",
436 | ]
437 |
438 | [[package]]
439 | name = "supports-unicode"
440 | version = "2.0.0"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
443 | dependencies = [
444 | "is-terminal",
445 | ]
446 |
447 | [[package]]
448 | name = "syn"
449 | version = "2.0.28"
450 | source = "registry+https://github.com/rust-lang/crates.io-index"
451 | checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
452 | dependencies = [
453 | "proc-macro2",
454 | "quote",
455 | "unicode-ident",
456 | ]
457 |
458 | [[package]]
459 | name = "terminal_size"
460 | version = "0.1.17"
461 | source = "registry+https://github.com/rust-lang/crates.io-index"
462 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
463 | dependencies = [
464 | "libc",
465 | "winapi",
466 | ]
467 |
468 | [[package]]
469 | name = "textwrap"
470 | version = "0.15.2"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
473 | dependencies = [
474 | "smawk",
475 | "unicode-linebreak",
476 | "unicode-width",
477 | ]
478 |
479 | [[package]]
480 | name = "thiserror"
481 | version = "1.0.44"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
484 | dependencies = [
485 | "thiserror-impl",
486 | ]
487 |
488 | [[package]]
489 | name = "thiserror-impl"
490 | version = "1.0.44"
491 | source = "registry+https://github.com/rust-lang/crates.io-index"
492 | checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
493 | dependencies = [
494 | "proc-macro2",
495 | "quote",
496 | "syn",
497 | ]
498 |
499 | [[package]]
500 | name = "unicode-ident"
501 | version = "1.0.9"
502 | source = "registry+https://github.com/rust-lang/crates.io-index"
503 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
504 |
505 | [[package]]
506 | name = "unicode-linebreak"
507 | version = "0.1.5"
508 | source = "registry+https://github.com/rust-lang/crates.io-index"
509 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
510 |
511 | [[package]]
512 | name = "unicode-width"
513 | version = "0.1.10"
514 | source = "registry+https://github.com/rust-lang/crates.io-index"
515 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
516 |
517 | [[package]]
518 | name = "utf8parse"
519 | version = "0.2.1"
520 | source = "registry+https://github.com/rust-lang/crates.io-index"
521 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
522 |
523 | [[package]]
524 | name = "winapi"
525 | version = "0.3.9"
526 | source = "registry+https://github.com/rust-lang/crates.io-index"
527 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
528 | dependencies = [
529 | "winapi-i686-pc-windows-gnu",
530 | "winapi-x86_64-pc-windows-gnu",
531 | ]
532 |
533 | [[package]]
534 | name = "winapi-i686-pc-windows-gnu"
535 | version = "0.4.0"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
538 |
539 | [[package]]
540 | name = "winapi-x86_64-pc-windows-gnu"
541 | version = "0.4.0"
542 | source = "registry+https://github.com/rust-lang/crates.io-index"
543 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
544 |
545 | [[package]]
546 | name = "windows-sys"
547 | version = "0.48.0"
548 | source = "registry+https://github.com/rust-lang/crates.io-index"
549 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
550 | dependencies = [
551 | "windows-targets",
552 | ]
553 |
554 | [[package]]
555 | name = "windows-targets"
556 | version = "0.48.0"
557 | source = "registry+https://github.com/rust-lang/crates.io-index"
558 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
559 | dependencies = [
560 | "windows_aarch64_gnullvm",
561 | "windows_aarch64_msvc",
562 | "windows_i686_gnu",
563 | "windows_i686_msvc",
564 | "windows_x86_64_gnu",
565 | "windows_x86_64_gnullvm",
566 | "windows_x86_64_msvc",
567 | ]
568 |
569 | [[package]]
570 | name = "windows_aarch64_gnullvm"
571 | version = "0.48.0"
572 | source = "registry+https://github.com/rust-lang/crates.io-index"
573 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
574 |
575 | [[package]]
576 | name = "windows_aarch64_msvc"
577 | version = "0.48.0"
578 | source = "registry+https://github.com/rust-lang/crates.io-index"
579 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
580 |
581 | [[package]]
582 | name = "windows_i686_gnu"
583 | version = "0.48.0"
584 | source = "registry+https://github.com/rust-lang/crates.io-index"
585 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
586 |
587 | [[package]]
588 | name = "windows_i686_msvc"
589 | version = "0.48.0"
590 | source = "registry+https://github.com/rust-lang/crates.io-index"
591 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
592 |
593 | [[package]]
594 | name = "windows_x86_64_gnu"
595 | version = "0.48.0"
596 | source = "registry+https://github.com/rust-lang/crates.io-index"
597 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
598 |
599 | [[package]]
600 | name = "windows_x86_64_gnullvm"
601 | version = "0.48.0"
602 | source = "registry+https://github.com/rust-lang/crates.io-index"
603 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
604 |
605 | [[package]]
606 | name = "windows_x86_64_msvc"
607 | version = "0.48.0"
608 | source = "registry+https://github.com/rust-lang/crates.io-index"
609 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
610 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
--------------------------------------------------------------------------------
/cdrom/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::fs::File;
3 | use std::io::{self, Write};
4 | use std::path::Path;
5 |
6 | pub use cdrom_crc::{crc16, CRC16_INITIAL_CRC};
7 | pub use cue;
8 | use cue::cd::CD;
9 | use cue::track;
10 |
11 | fn lba_to_msf(lba: i64) -> (i64, i64, i64) {
12 | (lba / 4500, (lba / 75) % 60, lba % 75)
13 | }
14 |
15 | // Converts Absolute MSF to Absolute Sector number
16 | pub fn amsf_to_asec(m: i64, s: i64, f: i64) -> i64 {
17 | let mut absolute_sector: i64 = 0;
18 | absolute_sector += 4500 * (((m / 16) * 10) + (m % 16));
19 | absolute_sector += 75 * (((s / 16) * 10) + (s % 16));
20 | absolute_sector += ((f / 16) * 10) + (f % 16);
21 |
22 | absolute_sector
23 | }
24 |
25 | pub struct Disc {
26 | pub tracks: Vec