Configure Tauri CI build
38
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Release App
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build Tauri App
|
||||
uses: tauri-apps/tauri-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tagName: v__VERSION__
|
||||
releaseName: 'Cipher Master v__VERSION__'
|
||||
releaseBody: 'See the assets below to download the app for your platform.'
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
751
Cargo.lock
generated
Normal file
@@ -0,0 +1,751 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cipher_game"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
13
Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "cipher_game"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
ureq = { version = "2.9", features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = "2.0"
|
||||
BIN
app-icon.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
28
app-icon.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
||||
<!-- Square Background -->
|
||||
<rect width="1024" height="1024" rx="180" fill="#181825"/>
|
||||
<rect x="32" y="32" width="960" height="960" rx="150" fill="none" stroke="#313244" stroke-width="8"/>
|
||||
|
||||
<!-- Outer Cryptographic Ring -->
|
||||
<circle cx="512" cy="512" r="380" fill="none" stroke="#89b4fa" stroke-width="6" stroke-dasharray="24 16" opacity="0.6"/>
|
||||
<circle cx="512" cy="512" r="340" fill="none" stroke="#cba6f7" stroke-width="4" stroke-dasharray="12 12" opacity="0.4"/>
|
||||
<circle cx="512" cy="512" r="290" fill="#1e1e2e" stroke="#fab387" stroke-width="12"/>
|
||||
|
||||
<!-- Futhark Runic Accent Characters around the Ring -->
|
||||
<text x="512" y="180" font-family="sans-serif" font-size="36" fill="#89b4fa" text-anchor="middle" opacity="0.7">ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ</text>
|
||||
<text x="512" y="870" font-family="sans-serif" font-size="36" fill="#89b4fa" text-anchor="middle" opacity="0.7">ᚺ ᚾ ᛁ ᛃ ᛇ ᛈ</text>
|
||||
|
||||
<!-- Central Glowing Teiwaz Rune (ᛏ) -->
|
||||
<g fill="none" stroke="#f9e2af" stroke-width="48" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Main Vertical Shaft -->
|
||||
<path d="M 512 280 L 512 740"/>
|
||||
<!-- Arrowhead Wings -->
|
||||
<path d="M 370 410 L 512 280 L 654 410"/>
|
||||
</g>
|
||||
|
||||
<!-- Inner Rune Glow Line -->
|
||||
<g fill="none" stroke="#ffffff" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" opacity="0.8">
|
||||
<path d="M 512 295 L 512 725"/>
|
||||
<path d="M 385 410 L 512 295 L 639 410"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
120
public/index.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Cipher Master</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #1e1e2e;
|
||||
color: #cdd6f4;
|
||||
padding: 2rem;
|
||||
margin: 0;
|
||||
}
|
||||
h1 { color: #89b4fa; margin-top: 0; }
|
||||
.form-group { margin-bottom: 1.2rem; }
|
||||
label { display: block; margin-bottom: 0.4rem; color: #a6adc8; }
|
||||
select, button {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #45475a;
|
||||
background: #313244;
|
||||
color: #cdd6f4;
|
||||
font-size: 1rem;
|
||||
}
|
||||
button {
|
||||
background: #89b4fa;
|
||||
color: #11111b;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
border: none;
|
||||
}
|
||||
button:hover { background: #b4befe; }
|
||||
#output {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: #181825;
|
||||
border-radius: 6px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ᛏ Cipher Master Engine</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="cipher">Cipher Type:</label>
|
||||
<select id="cipher">
|
||||
<option value="CustomFuthark">Elder Futhark Runes</option>
|
||||
<option value="Caesar">Caesar Shift</option>
|
||||
<option value="Atbash">Atbash Cipher</option>
|
||||
<option value="Substitution">Monoalphabetic Substitution</option>
|
||||
<option value="Vigenere">Vigenère Cipher</option>
|
||||
<option value="RailFence">Rail Fence (Zig-Zag)</option>
|
||||
<option value="Affine">Affine Cipher</option>
|
||||
<option value="A1Z26">A1Z26 Numeric</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="difficulty">Difficulty:</label>
|
||||
<select id="difficulty">
|
||||
<option value="Easy">Easy (Preserves spaces & punctuation)</option>
|
||||
<option value="Medium">Medium (Uppercase, no punctuation)</option>
|
||||
<option value="Hard">Hard (Grouped 5-character blocks)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="length">Text Length:</label>
|
||||
<select id="length">
|
||||
<option value="1">Short (~5-8 words)</option>
|
||||
<option value="2">Medium (~15-20 words)</option>
|
||||
<option value="3">Long (~30+ words)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onclick="generateCipher()">Generate Challenge Desktop Files</button>
|
||||
|
||||
<div id="output">Outputs will appear here...</div>
|
||||
|
||||
<script>
|
||||
function getInvoke() {
|
||||
if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) {
|
||||
return window.__TAURI__.core.invoke;
|
||||
} else if (window.__TAURI__ && window.__TAURI__.invoke) {
|
||||
return window.__TAURI__.invoke;
|
||||
} else if (window.__TAURI_INTERNALS__ && window.__TAURI_INTERNALS__.invoke) {
|
||||
return window.__TAURI_INTERNALS__.invoke;
|
||||
}
|
||||
throw new Error("Tauri IPC bindings not found.");
|
||||
}
|
||||
|
||||
async function generateCipher() {
|
||||
const cipher = document.getElementById("cipher").value;
|
||||
const difficulty = document.getElementById("difficulty").value;
|
||||
const lengthChoice = parseInt(document.getElementById("length").value);
|
||||
const output = document.getElementById("output");
|
||||
|
||||
output.innerText = "Querying local AI & generating cipher files...";
|
||||
|
||||
try {
|
||||
const invokeFn = getInvoke();
|
||||
const response = await invokeFn("generate_cipher_challenge", {
|
||||
cipher_type: cipher,
|
||||
difficulty: difficulty,
|
||||
length_choice: lengthChoice
|
||||
});
|
||||
output.innerText = response;
|
||||
} catch (err) {
|
||||
output.innerText = "Error: " + (err.message || err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
4691
src-tauri/Cargo.lock
generated
Normal file
20
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.0", features = [] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rand = "0.8"
|
||||
ureq = { version = "2.9", features = ["json"] }
|
||||
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
11
src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 65 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 889 B |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
413
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,413 @@
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
enum CipherType {
|
||||
CustomFuthark,
|
||||
Caesar,
|
||||
Atbash,
|
||||
Substitution,
|
||||
Vigenere,
|
||||
RailFence,
|
||||
Affine,
|
||||
A1Z26,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
enum Difficulty {
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OllamaRequest<'a> {
|
||||
model: &'a str,
|
||||
prompt: &'a str,
|
||||
stream: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OllamaResponse {
|
||||
response: String,
|
||||
}
|
||||
|
||||
#[tauri::command(rename_all = "snake_case")]
|
||||
fn generate_cipher_challenge(
|
||||
cipher_type: String,
|
||||
difficulty: String,
|
||||
length_choice: usize,
|
||||
) -> Result<String, String> {
|
||||
let cipher = match cipher_type.as_str() {
|
||||
"CustomFuthark" => CipherType::CustomFuthark,
|
||||
"Caesar" => CipherType::Caesar,
|
||||
"Atbash" => CipherType::Atbash,
|
||||
"Substitution" => CipherType::Substitution,
|
||||
"Vigenere" => CipherType::Vigenere,
|
||||
"RailFence" => CipherType::RailFence,
|
||||
"Affine" => CipherType::Affine,
|
||||
_ => CipherType::A1Z26,
|
||||
};
|
||||
|
||||
let diff = match difficulty.as_str() {
|
||||
"Easy" => Difficulty::Easy,
|
||||
"Medium" => Difficulty::Medium,
|
||||
_ => Difficulty::Hard,
|
||||
};
|
||||
|
||||
let raw_plaintext = get_ai_quote(length_choice);
|
||||
let formatted_plaintext = format_text_for_difficulty(&raw_plaintext, diff);
|
||||
let (ciphertext, secret_key, content_hint) = encrypt(&formatted_plaintext, cipher, diff);
|
||||
|
||||
let desktop_path = get_desktop_path();
|
||||
let challenge_file = desktop_path.join("cipher_challenge.txt");
|
||||
let hint_file = desktop_path.join("cipher_hint.txt");
|
||||
let solution_file = desktop_path.join("cipher_solution.txt");
|
||||
|
||||
let mut challenge_out = File::create(&challenge_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
challenge_out,
|
||||
"=== CIPHER CHALLENGE ===\nCipher: {:?}\nDifficulty: {:?}\n\n{}\n",
|
||||
cipher, diff, ciphertext
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut hint_out = File::create(&hint_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
hint_out,
|
||||
"=== CIPHER SOLUTION HINTS & RIDDLE ===\nCipher Type: {:?}\n\nANSWER HINTS:\n{}\n",
|
||||
cipher, content_hint
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut solution_out = File::create(&solution_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
solution_out,
|
||||
"=== CIPHER SOLUTION & KEY ===\nCipher Type: {:?}\nKey/Parameters: {}\n\nORIGINAL PLAINTEXT:\n{}\n",
|
||||
cipher, secret_key, raw_plaintext
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(format!(
|
||||
"SUCCESS!\nChallenge text:\n{}\n\nFiles saved to Desktop:\n- {}\n- {}\n- {}",
|
||||
ciphertext,
|
||||
challenge_file.display(),
|
||||
hint_file.display(),
|
||||
solution_file.display()
|
||||
))
|
||||
}
|
||||
|
||||
fn get_ai_quote(length_choice: usize) -> String {
|
||||
let word_count = match length_choice {
|
||||
1 => "5 to 8",
|
||||
2 => "12 to 18",
|
||||
_ => "25 to 35",
|
||||
};
|
||||
|
||||
let prompt = format!(
|
||||
"Generate a single inspiring or philosophical quote between {} words. Return ONLY the quote text without quotes, explanation, or attribution.",
|
||||
word_count
|
||||
);
|
||||
|
||||
let req = OllamaRequest {
|
||||
model: "llama3.2:1b",
|
||||
prompt: &prompt,
|
||||
stream: false,
|
||||
};
|
||||
|
||||
match ureq::post("http://localhost:11434/api/generate")
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.send_json(&req)
|
||||
{
|
||||
Ok(res) => {
|
||||
if let Ok(ollama_res) = res.into_json::<OllamaResponse>() {
|
||||
let cleaned = ollama_res
|
||||
.response
|
||||
.trim()
|
||||
.replace('"', "")
|
||||
.replace('\n', " ")
|
||||
.to_uppercase();
|
||||
if !cleaned.is_empty() {
|
||||
return cleaned;
|
||||
}
|
||||
}
|
||||
fallback_quote(length_choice)
|
||||
}
|
||||
Err(_) => fallback_quote(length_choice),
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_quote(length_choice: usize) -> String {
|
||||
let topics = ["VIKINGS", "KNOWLEDGE", "FORTUNE", "STRATEGY", "FREEDOM"];
|
||||
let actions = ["SAILED ACROSS THE UNKNOWN", "DISCOVERED HIDDEN TRUTHS", "FAVORED THE BRAVE MINDS", "PROTECTED LIBERTY WITH SECRETS"];
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let base = format!("{} {}", topics.choose(&mut rng).unwrap(), actions.choose(&mut rng).unwrap());
|
||||
if length_choice > 1 {
|
||||
format!("{} AND UNLOCKED THE SECRETS OF THE ANCIENT RUNES", base)
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_path() -> PathBuf {
|
||||
env::var_os("USERPROFILE")
|
||||
.or_else(|| env::var_os("HOME"))
|
||||
.map(PathBuf::from)
|
||||
.map(|p| p.join("Desktop"))
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
fn format_text_for_difficulty(text: &str, diff: Difficulty) -> String {
|
||||
match diff {
|
||||
Difficulty::Easy => text.to_string(),
|
||||
Difficulty::Medium => text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric() || c.is_whitespace())
|
||||
.collect(),
|
||||
Difficulty::Hard => {
|
||||
let cleaned: String = text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.collect();
|
||||
cleaned
|
||||
.as_bytes()
|
||||
.chunks(5)
|
||||
.map(|chunk| std::str::from_utf8(chunk).unwrap())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_custom_futhark_map() -> HashMap<char, char> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert('A', 'ᚨ'); map.insert('B', 'ᛒ'); map.insert('C', 'ᚲ');
|
||||
map.insert('D', 'ᛞ'); map.insert('E', 'ᛖ'); map.insert('F', 'ᚠ');
|
||||
map.insert('G', 'ᚷ'); map.insert('H', 'ᚺ'); map.insert('I', 'ᛁ');
|
||||
map.insert('J', 'ᛃ'); map.insert('K', 'ᚲ'); map.insert('L', 'ᛚ');
|
||||
map.insert('M', 'ᛗ'); map.insert('N', 'ᚾ'); map.insert('O', 'ᛟ');
|
||||
map.insert('P', 'ᛉ'); map.insert('Q', 'ᚲ'); map.insert('R', 'ᚱ');
|
||||
map.insert('S', 'ᛋ'); map.insert('T', 'ᛏ'); map.insert('U', 'ᚢ');
|
||||
map.insert('V', 'ᚹ'); map.insert('W', 'ᚹ'); map.insert('X', 'ᚦ');
|
||||
map.insert('Y', 'ᛇ'); map.insert('Z', 'ᛉ');
|
||||
map
|
||||
}
|
||||
|
||||
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let (ciphertext, key_info) = match cipher {
|
||||
CipherType::CustomFuthark => {
|
||||
let map = get_custom_futhark_map();
|
||||
let encrypted = text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.map(|c| *map.get(&c).unwrap_or(&c))
|
||||
.collect();
|
||||
(encrypted, "Elder Futhark Key Map".to_string())
|
||||
}
|
||||
CipherType::Caesar => {
|
||||
let shift = rng.gen_range(1..25) as u8;
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
(((c as u8 - base + shift) % 26) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Caesar Shift = {}", shift))
|
||||
}
|
||||
CipherType::Atbash => {
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
((25 - (c as u8 - base)) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, "Atbash (Reversed Alphabet)".to_string())
|
||||
}
|
||||
CipherType::Substitution => {
|
||||
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||
let mut shuffled = alphabet.clone();
|
||||
shuffled.shuffle(&mut rng);
|
||||
let map: HashMap<char, char> = alphabet.drain(..).zip(shuffled.into_iter()).collect();
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let upper = c.to_ascii_uppercase();
|
||||
let mapped = map[&upper];
|
||||
if c.is_ascii_lowercase() { mapped.to_ascii_lowercase() } else { mapped }
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
||||
(encrypted, format!("Substitution Table: [{}]", key_str))
|
||||
}
|
||||
CipherType::Vigenere => {
|
||||
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
||||
let key = *keys.choose(&mut rng).unwrap();
|
||||
let key_bytes = key.as_bytes();
|
||||
let mut key_idx = 0;
|
||||
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
let k = (key_bytes[key_idx % key_bytes.len()] - b'A') as u8;
|
||||
key_idx += 1;
|
||||
(((c as u8 - base + k) % 26) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Vigenere Passphrase: {}", key))
|
||||
}
|
||||
CipherType::RailFence => {
|
||||
let rails = rng.gen_range(2..=4);
|
||||
let mut fence: Vec<Vec<char>> = vec![Vec::new(); rails];
|
||||
let mut rail = 0;
|
||||
let mut direction = 1;
|
||||
|
||||
for c in text.chars() {
|
||||
fence[rail].push(c);
|
||||
if rail == 0 { direction = 1; }
|
||||
else if rail == rails - 1 { direction = -1; }
|
||||
rail = (rail as i32 + direction) as usize;
|
||||
}
|
||||
|
||||
let encrypted = fence.into_iter().flatten().collect();
|
||||
(encrypted, format!("Rail Fence Depth = {} Rails", rails))
|
||||
}
|
||||
CipherType::Affine => {
|
||||
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
||||
let a = *a_options.choose(&mut rng).unwrap();
|
||||
let b = rng.gen_range(1..20);
|
||||
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
let x = (c as u8 - base) as usize;
|
||||
let enc = ((a * x + b) % 26) as u8;
|
||||
(base + enc) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b))
|
||||
}
|
||||
CipherType::A1Z26 => {
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.filter_map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
Some(format!("-{}", c as u8 - base + 1))
|
||||
} else if c.is_whitespace() {
|
||||
Some(" ".to_string())
|
||||
} else { None }
|
||||
})
|
||||
.collect::<String>()
|
||||
.trim_start_matches('-')
|
||||
.to_string();
|
||||
(encrypted, "A1Z26 Position Mapping".to_string())
|
||||
}
|
||||
};
|
||||
|
||||
let content_hint = generate_content_hints(text, diff);
|
||||
(ciphertext, key_info, content_hint)
|
||||
}
|
||||
|
||||
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
||||
let cleaned: Vec<char> = plaintext
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.map(|c| c.to_ascii_uppercase())
|
||||
.collect();
|
||||
|
||||
let total_letters = cleaned.len();
|
||||
if total_letters == 0 {
|
||||
return "No alphabetic letters found in text.".to_string();
|
||||
}
|
||||
|
||||
let mut counts: HashMap<char, usize> = HashMap::new();
|
||||
for &c in &cleaned {
|
||||
*counts.entry(c).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
let mut sorted_counts: Vec<(char, usize)> = counts.into_iter().collect();
|
||||
sorted_counts.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
let top_letter = sorted_counts[0].0;
|
||||
let top_count = sorted_counts[0].1;
|
||||
|
||||
let words: Vec<&str> = plaintext.split_whitespace().collect();
|
||||
let word_count = words.len();
|
||||
|
||||
let riddle = format!(
|
||||
"RIDDLE HINT:\n\"Look for an idea involving the key word '{}' hidden beneath.\"",
|
||||
words.get(words.len() / 2).unwrap_or(&"TRUTH")
|
||||
);
|
||||
|
||||
match diff {
|
||||
Difficulty::Easy => {
|
||||
let vowels: HashSet<char> = ['A', 'E', 'I', 'O', 'U'].iter().cloned().collect();
|
||||
let vowel_count: usize = cleaned.iter().filter(|c| vowels.contains(c)).count();
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Word Count: {}\n\
|
||||
- Most Common Letter in Solution: '{}' (Appears {} times)\n\
|
||||
- Total Vowels in Solution: {}\n\
|
||||
- First Word Length: {} letters | Last Word Length: {} letters",
|
||||
riddle,
|
||||
word_count,
|
||||
top_letter,
|
||||
top_count,
|
||||
vowel_count,
|
||||
words.first().unwrap_or(&"").len(),
|
||||
words.last().unwrap_or(&"").len(),
|
||||
)
|
||||
}
|
||||
Difficulty::Medium => {
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Total Characters: {}\n\
|
||||
- Word Count: {}\n\
|
||||
- Most Common Character in Solution: '{}' ({}% of all characters)",
|
||||
riddle,
|
||||
total_letters,
|
||||
word_count,
|
||||
top_letter,
|
||||
(top_count * 100) / total_letters
|
||||
)
|
||||
}
|
||||
Difficulty::Hard => {
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Total Characters: {}\n\
|
||||
- Top Character Frequency Ratio: {}/{}",
|
||||
riddle, total_letters, top_count, total_letters
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![generate_cipher_challenge])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
5
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
34
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Cipher Master",
|
||||
"version": "1.0.0",
|
||||
"identifier": "com.xvoet.ciphermaster",
|
||||
"build": {
|
||||
"frontendDist": "../public"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Cipher Master",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
414
src/main.rs
Normal file
@@ -0,0 +1,414 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
enum CipherType {
|
||||
CustomFuthark,
|
||||
Caesar,
|
||||
Atbash,
|
||||
Substitution,
|
||||
Vigenere,
|
||||
RailFence,
|
||||
Affine,
|
||||
A1Z26,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
enum Difficulty {
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OllamaRequest<'a> {
|
||||
model: &'a str,
|
||||
prompt: &'a str,
|
||||
stream: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OllamaResponse {
|
||||
response: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn generate_cipher_challenge(
|
||||
cipher_type: String,
|
||||
difficulty: String,
|
||||
length_choice: usize,
|
||||
) -> Result<String, String> {
|
||||
let cipher = match cipher_type.as_str() {
|
||||
"CustomFuthark" => CipherType::CustomFuthark,
|
||||
"Caesar" => CipherType::Caesar,
|
||||
"Atbash" => CipherType::Atbash,
|
||||
"Substitution" => CipherType::Substitution,
|
||||
"Vigenere" => CipherType::Vigenere,
|
||||
"RailFence" => CipherType::RailFence,
|
||||
"Affine" => CipherType::Affine,
|
||||
_ => CipherType::A1Z26,
|
||||
};
|
||||
|
||||
let diff = match difficulty.as_str() {
|
||||
"Easy" => Difficulty::Easy,
|
||||
"Medium" => Difficulty::Medium,
|
||||
_ => Difficulty::Hard,
|
||||
};
|
||||
|
||||
let raw_plaintext = get_ai_quote(length_choice);
|
||||
let formatted_plaintext = format_text_for_difficulty(&raw_plaintext, diff);
|
||||
let (ciphertext, secret_key, content_hint) = encrypt(&formatted_plaintext, cipher, diff);
|
||||
|
||||
let desktop_path = get_desktop_path();
|
||||
let challenge_file = desktop_path.join("cipher_challenge.txt");
|
||||
let hint_file = desktop_path.join("cipher_hint.txt");
|
||||
let solution_file = desktop_path.join("cipher_solution.txt");
|
||||
|
||||
let mut challenge_out = File::create(&challenge_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
challenge_out,
|
||||
"=== CIPHER CHALLENGE ===\nCipher: {:?}\nDifficulty: {:?}\n\n{}\n",
|
||||
cipher, diff, ciphertext
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut hint_out = File::create(&hint_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
hint_out,
|
||||
"=== CIPHER SOLUTION HINTS & RIDDLE ===\nCipher Type: {:?}\n\nANSWER HINTS:\n{}\n",
|
||||
cipher, content_hint
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut solution_out = File::create(&solution_file).map_err(|e| e.to_string())?;
|
||||
write!(
|
||||
solution_out,
|
||||
"=== CIPHER SOLUTION & KEY ===\nCipher Type: {:?}\nKey/Parameters: {}\n\nORIGINAL PLAINTEXT:\n{}\n",
|
||||
cipher, secret_key, raw_plaintext
|
||||
).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(format!(
|
||||
"SUCCESS!\nChallenge text:\n{}\n\nFiles saved to Desktop:\n- {}\n- {}\n- {}",
|
||||
ciphertext,
|
||||
challenge_file.display(),
|
||||
hint_file.display(),
|
||||
solution_file.display()
|
||||
))
|
||||
}
|
||||
|
||||
fn get_ai_quote(length_choice: usize) -> String {
|
||||
let word_count = match length_choice {
|
||||
1 => "5 to 8",
|
||||
2 => "12 to 18",
|
||||
_ => "25 to 35",
|
||||
};
|
||||
|
||||
let prompt = format!(
|
||||
"Generate a single inspiring or philosophical quote between {} words. Return ONLY the quote text without quotes, explanation, or attribution.",
|
||||
word_count
|
||||
);
|
||||
|
||||
let req = OllamaRequest {
|
||||
model: "llama3.2:1b",
|
||||
prompt: &prompt,
|
||||
stream: false,
|
||||
};
|
||||
|
||||
match ureq::post("http://localhost:11434/api/generate")
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.send_json(&req)
|
||||
{
|
||||
Ok(res) => {
|
||||
if let Ok(ollama_res) = res.into_json::<OllamaResponse>() {
|
||||
let cleaned = ollama_res
|
||||
.response
|
||||
.trim()
|
||||
.replace('"', "")
|
||||
.replace('\n', " ")
|
||||
.to_uppercase();
|
||||
if !cleaned.is_empty() {
|
||||
return cleaned;
|
||||
}
|
||||
}
|
||||
fallback_quote(length_choice)
|
||||
}
|
||||
Err(_) => fallback_quote(length_choice),
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_quote(length_choice: usize) -> String {
|
||||
let topics = ["VIKINGS", "KNOWLEDGE", "FORTUNE", "STRATEGY", "FREEDOM"];
|
||||
let actions = ["SAILED ACROSS THE UNKNOWN", "DISCOVERED HIDDEN TRUTHS", "FAVORED THE BRAVE MINDS", "PROTECTED LIBERTY WITH SECRETS"];
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let base = format!("{} {}", topics.choose(&mut rng).unwrap(), actions.choose(&mut rng).unwrap());
|
||||
if length_choice > 1 {
|
||||
format!("{} AND UNLOCKED THE SECRETS OF THE ANCIENT RUNES", base)
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_path() -> PathBuf {
|
||||
env::var_os("USERPROFILE")
|
||||
.or_else(|| env::var_os("HOME"))
|
||||
.map(PathBuf::from)
|
||||
.map(|p| p.join("Desktop"))
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
fn format_text_for_difficulty(text: &str, diff: Difficulty) -> String {
|
||||
match diff {
|
||||
Difficulty::Easy => text.to_string(),
|
||||
Difficulty::Medium => text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric() || c.is_whitespace())
|
||||
.collect(),
|
||||
Difficulty::Hard => {
|
||||
let cleaned: String = text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.collect();
|
||||
cleaned
|
||||
.as_bytes()
|
||||
.chunks(5)
|
||||
.map(|chunk| std::str::from_utf8(chunk).unwrap())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_custom_futhark_map() -> HashMap<char, char> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert('A', 'ᚨ'); map.insert('B', 'ᛒ'); map.insert('C', 'ᚲ');
|
||||
map.insert('D', 'ᛞ'); map.insert('E', 'ᛖ'); map.insert('F', 'ᚠ');
|
||||
map.insert('G', 'ᚷ'); map.insert('H', 'ᚺ'); map.insert('I', 'ᛁ');
|
||||
map.insert('J', 'ᛃ'); map.insert('K', 'ᚲ'); map.insert('L', 'ᛚ');
|
||||
map.insert('M', 'ᛗ'); map.insert('N', 'ᚾ'); map.insert('O', 'ᛟ');
|
||||
map.insert('P', 'ᛉ'); map.insert('Q', 'ᚲ'); map.insert('R', 'ᚱ');
|
||||
map.insert('S', 'ᛋ'); map.insert('T', 'ᛏ'); map.insert('U', 'ᚢ');
|
||||
map.insert('V', 'ᚹ'); map.insert('W', 'ᚹ'); map.insert('X', 'ᚦ');
|
||||
map.insert('Y', 'ᛇ'); map.insert('Z', 'ᛉ');
|
||||
map
|
||||
}
|
||||
|
||||
fn encrypt(text: &str, cipher: CipherType, diff: Difficulty) -> (String, String, String) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let (ciphertext, key_info) = match cipher {
|
||||
CipherType::CustomFuthark => {
|
||||
let map = get_custom_futhark_map();
|
||||
let encrypted = text
|
||||
.to_uppercase()
|
||||
.chars()
|
||||
.map(|c| *map.get(&c).unwrap_or(&c))
|
||||
.collect();
|
||||
(encrypted, "Elder Futhark Key Map".to_string())
|
||||
}
|
||||
CipherType::Caesar => {
|
||||
let shift = rng.gen_range(1..25) as u8;
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
(((c as u8 - base + shift) % 26) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Caesar Shift = {}", shift))
|
||||
}
|
||||
CipherType::Atbash => {
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
((25 - (c as u8 - base)) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, "Atbash (Reversed Alphabet)".to_string())
|
||||
}
|
||||
CipherType::Substitution => {
|
||||
let mut alphabet: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||
let mut shuffled = alphabet.clone();
|
||||
shuffled.shuffle(&mut rng);
|
||||
let map: HashMap<char, char> = alphabet.drain(..).zip(shuffled.into_iter()).collect();
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let upper = c.to_ascii_uppercase();
|
||||
let mapped = map[&upper];
|
||||
if c.is_ascii_lowercase() { mapped.to_ascii_lowercase() } else { mapped }
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
let key_str = map.iter().map(|(k, v)| format!("{}->{}", k, v)).collect::<Vec<String>>().join(", ");
|
||||
(encrypted, format!("Substitution Table: [{}]", key_str))
|
||||
}
|
||||
CipherType::Vigenere => {
|
||||
let keys = vec!["ODIN", "THOR", "VALHALLA", "RUNE", "RUST", "CRYPT"];
|
||||
let key = *keys.choose(&mut rng).unwrap();
|
||||
let key_bytes = key.as_bytes();
|
||||
let mut key_idx = 0;
|
||||
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
let k = (key_bytes[key_idx % key_bytes.len()] - b'A') as u8;
|
||||
key_idx += 1;
|
||||
(((c as u8 - base + k) % 26) + base) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Vigenere Passphrase: {}", key))
|
||||
}
|
||||
CipherType::RailFence => {
|
||||
let rails = rng.gen_range(2..=4);
|
||||
let mut fence: Vec<Vec<char>> = vec![Vec::new(); rails];
|
||||
let mut rail = 0;
|
||||
let mut direction = 1;
|
||||
|
||||
for c in text.chars() {
|
||||
fence[rail].push(c);
|
||||
if rail == 0 { direction = 1; }
|
||||
else if rail == rails - 1 { direction = -1; }
|
||||
rail = (rail as i32 + direction) as usize;
|
||||
}
|
||||
|
||||
let encrypted = fence.into_iter().flatten().collect();
|
||||
(encrypted, format!("Rail Fence Depth = {} Rails", rails))
|
||||
}
|
||||
CipherType::Affine => {
|
||||
let a_options = vec![3, 5, 7, 9, 11, 15, 17, 19, 21, 23];
|
||||
let a = *a_options.choose(&mut rng).unwrap();
|
||||
let b = rng.gen_range(1..20);
|
||||
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
let x = (c as u8 - base) as usize;
|
||||
let enc = ((a * x + b) % 26) as u8;
|
||||
(base + enc) as char
|
||||
} else { c }
|
||||
})
|
||||
.collect();
|
||||
(encrypted, format!("Affine Cipher: E(x) = ({}x + {}) mod 26", a, b))
|
||||
}
|
||||
CipherType::A1Z26 => {
|
||||
let encrypted = text
|
||||
.chars()
|
||||
.filter_map(|c| {
|
||||
if c.is_ascii_alphabetic() {
|
||||
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
|
||||
Some(format!("-{}", c as u8 - base + 1))
|
||||
} else if c.is_whitespace() {
|
||||
Some(" ".to_string())
|
||||
} else { None }
|
||||
})
|
||||
.collect::<String>()
|
||||
.trim_start_matches('-')
|
||||
.to_string();
|
||||
(encrypted, "A1Z26 Position Mapping".to_string())
|
||||
}
|
||||
};
|
||||
|
||||
let content_hint = generate_content_hints(text, diff);
|
||||
(ciphertext, key_info, content_hint)
|
||||
}
|
||||
|
||||
fn generate_content_hints(plaintext: &str, diff: Difficulty) -> String {
|
||||
let cleaned: Vec<char> = plaintext
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.map(|c| c.to_ascii_uppercase())
|
||||
.collect();
|
||||
|
||||
let total_letters = cleaned.len();
|
||||
if total_letters == 0 {
|
||||
return "No alphabetic letters found in text.".to_string();
|
||||
}
|
||||
|
||||
let mut counts: HashMap<char, usize> = HashMap::new();
|
||||
for &c in &cleaned {
|
||||
*counts.entry(c).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
let mut sorted_counts: Vec<(char, usize)> = counts.into_iter().collect();
|
||||
sorted_counts.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
let top_letter = sorted_counts[0].0;
|
||||
let top_count = sorted_counts[0].1;
|
||||
|
||||
let words: Vec<&str> = plaintext.split_whitespace().collect();
|
||||
let word_count = words.len();
|
||||
|
||||
let riddle = format!(
|
||||
"RIDDLE HINT:\n\"Look for an idea involving the key word '{}' hidden beneath.\"",
|
||||
words.get(words.len() / 2).unwrap_or(&"TRUTH")
|
||||
);
|
||||
|
||||
match diff {
|
||||
Difficulty::Easy => {
|
||||
let vowels: HashSet<char> = ['A', 'E', 'I', 'O', 'U'].iter().cloned().collect();
|
||||
let vowel_count: usize = cleaned.iter().filter(|c| vowels.contains(c)).count();
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Word Count: {}\n\
|
||||
- Most Common Letter in Solution: '{}' (Appears {} times)\n\
|
||||
- Total Vowels in Solution: {}\n\
|
||||
- First Word Length: {} letters | Last Word Length: {} letters",
|
||||
riddle,
|
||||
word_count,
|
||||
top_letter,
|
||||
top_count,
|
||||
vowel_count,
|
||||
words.first().unwrap_or(&"").len(),
|
||||
words.last().unwrap_or(&"").len(),
|
||||
)
|
||||
}
|
||||
Difficulty::Medium => {
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Total Characters: {}\n\
|
||||
- Word Count: {}\n\
|
||||
- Most Common Character in Solution: '{}' ({}% of all characters)",
|
||||
riddle,
|
||||
total_letters,
|
||||
word_count,
|
||||
top_letter,
|
||||
(top_count * 100) / total_letters
|
||||
)
|
||||
}
|
||||
Difficulty::Hard => {
|
||||
format!(
|
||||
"{}\n\nSTRUCTURAL HINTS:\n\
|
||||
- Total Characters: {}\n\
|
||||
- Top Character Frequency Ratio: {}/{}",
|
||||
riddle, total_letters, top_count, total_letters
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![generate_cipher_challenge])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||